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
104 * -- LVM_ENABLEGROUPVIEW
105 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
106 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
107 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
108 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
109 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
110 * -- LVM_GETINSERTMARKRECT
111 * -- LVM_GETNUMBEROFWORKAREAS
112 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
113 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
114 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
115 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
116 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
117 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
118 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
119 * -- LVM_INSERTGROUPSORTED
120 * -- LVM_INSERTMARKHITTEST
121 * -- LVM_ISGROUPVIEWENABLED
123 * -- LVM_MOVEITEMTOGROUP
125 * -- LVM_SETTILEWIDTH
129 * -- ListView_GetHoverTime, ListView_SetHoverTime
130 * -- ListView_GetISearchString
131 * -- ListView_GetNumberOfWorkAreas
132 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
137 * Known differences in message stream from native control (not known if
138 * these differences cause problems):
139 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
140 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
141 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
142 * processing for "USEDOUBLECLICKTIME".
146 #include "wine/port.h"
161 #include "commctrl.h"
162 #include "comctl32.h"
165 #include "wine/debug.h"
166 #include "wine/unicode.h"
168 WINE_DEFAULT_DEBUG_CHANNEL(listview);
170 typedef struct tagCOLUMN_INFO
172 RECT rcHeader; /* tracks the header's rectangle */
173 INT fmt; /* same as LVCOLUMN.fmt */
177 typedef struct tagITEMHDR
181 } ITEMHDR, *LPITEMHDR;
183 typedef struct tagSUBITEM_INFO
189 typedef struct tagITEM_ID ITEM_ID;
191 typedef struct tagITEM_INFO
202 UINT id; /* item id */
203 HDPA item; /* link to item data */
206 typedef struct tagRANGE
212 typedef struct tagRANGES
217 typedef struct tagITERATOR
226 typedef struct tagDELAYED_ITEM_EDIT
232 typedef struct tagLISTVIEW_INFO
236 RECT rcList; /* This rectangle is really the window
237 * client rectangle possibly reduced by the
238 * horizontal scroll bar and/or header - see
239 * LISTVIEW_UpdateSize. This rectangle offset
240 * by the LISTVIEW_GetOrigin value is in
241 * client coordinates */
243 /* notification window */
246 BOOL bDoChangeNotify; /* send change notification messages? */
253 INT nItemCount; /* the number of items in the list */
254 HDPA hdpaItems; /* array ITEM_INFO pointers */
255 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
256 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
257 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
258 RANGES selectionRanges;
259 INT nSelectionMark; /* item to start next multiselection from */
261 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
264 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
265 BOOL colRectsDirty; /* trigger column rectangles requery from header */
268 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
273 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
277 DWORD dwStyle; /* the cached window GWL_STYLE */
278 DWORD dwLvExStyle; /* extended listview style */
279 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
285 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
288 HIMAGELIST himlNormal;
289 HIMAGELIST himlSmall;
290 HIMAGELIST himlState;
294 POINT currIconPos; /* this is the position next icon will be placed */
298 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
300 /* marquee selection */
301 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
303 RECT marqueeRect; /* absolute coordinates of marquee selection */
304 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
305 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
308 BOOL bFocus; /* control has focus */
310 RECT rcFocus; /* focus bounds */
321 INT ntmHeight; /* Some cached metrics of the font used */
322 INT ntmMaxCharWidth; /* by the listview to draw items */
325 /* mouse operation */
328 POINT ptClickPos; /* point where the user clicked */
329 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
333 /* keyboard operation */
334 DWORD lastKeyPressTimestamp;
336 INT nSearchParamLength;
337 WCHAR szSearchParam[ MAX_PATH ];
340 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
341 BOOL bIsDrawing; /* Drawing in progress */
342 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
343 BOOL bRedraw; /* WM_SETREDRAW switch */
346 DWORD iVersion; /* CCM_[G,S]ETVERSION */
352 /* How many we debug buffer to allocate */
353 #define DEBUG_BUFFERS 20
354 /* The size of a single debug buffer */
355 #define DEBUG_BUFFER_SIZE 256
357 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
358 #define SB_INTERNAL -1
360 /* maximum size of a label */
361 #define DISP_TEXT_SIZE 260
363 /* padding for items in list and small icon display modes */
364 #define WIDTH_PADDING 12
366 /* padding for items in list, report and small icon display modes */
367 #define HEIGHT_PADDING 1
369 /* offset of items in report display mode */
370 #define REPORT_MARGINX 2
372 /* padding for icon in large icon display mode
373 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
374 * that HITTEST will see.
375 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
376 * ICON_TOP_PADDING - sum of the two above.
377 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
378 * LABEL_HOR_PADDING - between text and sides of box
379 * LABEL_VERT_PADDING - between bottom of text and end of box
381 * ICON_LR_PADDING - additional width above icon size.
382 * ICON_LR_HALF - half of the above value
384 #define ICON_TOP_PADDING_NOTHITABLE 2
385 #define ICON_TOP_PADDING_HITABLE 2
386 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
387 #define ICON_BOTTOM_PADDING 4
388 #define LABEL_HOR_PADDING 5
389 #define LABEL_VERT_PADDING 7
390 #define ICON_LR_PADDING 16
391 #define ICON_LR_HALF (ICON_LR_PADDING/2)
393 /* default label width for items in list and small icon display modes */
394 #define DEFAULT_LABEL_WIDTH 40
395 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
396 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
398 /* default column width for items in list display mode */
399 #define DEFAULT_COLUMN_WIDTH 128
401 /* Size of "line" scroll for V & H scrolls */
402 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
404 /* Padding between image and label */
405 #define IMAGE_PADDING 2
407 /* Padding behind the label */
408 #define TRAILING_LABEL_PADDING 12
409 #define TRAILING_HEADER_PADDING 11
411 /* Border for the icon caption */
412 #define CAPTION_BORDER 2
414 /* Standard DrawText flags */
415 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
416 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
417 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
419 /* Image index from state */
420 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
422 /* The time in milliseconds to reset the search in the list */
423 #define KEY_DELAY 450
425 /* Dump the LISTVIEW_INFO structure to the debug channel */
426 #define LISTVIEW_DUMP(iP) do { \
427 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
428 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
429 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
430 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
431 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
432 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
433 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
434 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
435 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
436 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
439 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
442 * forward declarations
444 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
445 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
446 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
447 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
448 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
449 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
450 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
451 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
452 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
453 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
454 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
455 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
456 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
457 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
458 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
459 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
460 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
461 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
462 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
463 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
465 /******** Text handling functions *************************************/
467 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
468 * text string. The string may be ANSI or Unicode, in which case
469 * the boolean isW tells us the type of the string.
471 * The name of the function tell what type of strings it expects:
472 * W: Unicode, T: ANSI/Unicode - function of isW
475 static inline BOOL is_text(LPCWSTR text)
477 return text != NULL && text != LPSTR_TEXTCALLBACKW;
480 static inline int textlenT(LPCWSTR text, BOOL isW)
482 return !is_text(text) ? 0 :
483 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
486 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
489 if (isSrcW) lstrcpynW(dest, src, max);
490 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
492 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
493 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
496 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
498 LPWSTR wstr = (LPWSTR)text;
500 if (!isW && is_text(text))
502 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
503 wstr = Alloc(len * sizeof(WCHAR));
504 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
506 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
510 static inline void textfreeT(LPWSTR wstr, BOOL isW)
512 if (!isW && is_text(wstr)) Free (wstr);
516 * dest is a pointer to a Unicode string
517 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
519 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
523 if (src == LPSTR_TEXTCALLBACKW)
525 if (is_text(*dest)) Free(*dest);
526 *dest = LPSTR_TEXTCALLBACKW;
530 LPWSTR pszText = textdupTtoW(src, isW);
531 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
532 bResult = Str_SetPtrW(dest, pszText);
533 textfreeT(pszText, isW);
539 * compares a Unicode to a Unicode/ANSI text string
541 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
543 if (!aw) return bt ? -1 : 0;
545 if (aw == LPSTR_TEXTCALLBACKW)
546 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
547 if (bt != LPSTR_TEXTCALLBACKW)
549 LPWSTR bw = textdupTtoW(bt, isW);
550 int r = bw ? lstrcmpW(aw, bw) : 1;
558 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
560 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
561 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
564 /******** Debugging functions *****************************************/
566 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
568 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
569 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
572 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
574 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
575 n = min(textlenT(text, isW), n);
576 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
579 static char* debug_getbuf(void)
581 static int index = 0;
582 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
583 return buffers[index++ % DEBUG_BUFFERS];
586 static inline const char* debugrange(const RANGE *lprng)
588 if (!lprng) return "(null)";
589 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
592 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
594 char* buf = debug_getbuf(), *text = buf;
595 int len, size = DEBUG_BUFFER_SIZE;
597 if (pScrollInfo == NULL) return "(null)";
598 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
599 if (len == -1) goto end; buf += len; size -= len;
600 if (pScrollInfo->fMask & SIF_RANGE)
601 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
603 if (len == -1) goto end; buf += len; size -= len;
604 if (pScrollInfo->fMask & SIF_PAGE)
605 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
607 if (len == -1) goto end; buf += len; size -= len;
608 if (pScrollInfo->fMask & SIF_POS)
609 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
611 if (len == -1) goto end; buf += len; size -= len;
612 if (pScrollInfo->fMask & SIF_TRACKPOS)
613 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
615 if (len == -1) goto end; buf += len;
618 buf = text + strlen(text);
620 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
624 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
626 if (!plvnm) return "(null)";
627 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
628 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
629 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
630 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
633 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
635 char* buf = debug_getbuf(), *text = buf;
636 int len, size = DEBUG_BUFFER_SIZE;
638 if (lpLVItem == NULL) return "(null)";
639 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
640 if (len == -1) goto end; buf += len; size -= len;
641 if (lpLVItem->mask & LVIF_STATE)
642 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
644 if (len == -1) goto end; buf += len; size -= len;
645 if (lpLVItem->mask & LVIF_TEXT)
646 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
648 if (len == -1) goto end; buf += len; size -= len;
649 if (lpLVItem->mask & LVIF_IMAGE)
650 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
652 if (len == -1) goto end; buf += len; size -= len;
653 if (lpLVItem->mask & LVIF_PARAM)
654 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
656 if (len == -1) goto end; buf += len; size -= len;
657 if (lpLVItem->mask & LVIF_INDENT)
658 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
660 if (len == -1) goto end; buf += len;
663 buf = text + strlen(text);
665 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
669 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
671 char* buf = debug_getbuf(), *text = buf;
672 int len, size = DEBUG_BUFFER_SIZE;
674 if (lpColumn == NULL) return "(null)";
675 len = snprintf(buf, size, "{");
676 if (len == -1) goto end; buf += len; size -= len;
677 if (lpColumn->mask & LVCF_SUBITEM)
678 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
680 if (len == -1) goto end; buf += len; size -= len;
681 if (lpColumn->mask & LVCF_FMT)
682 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
684 if (len == -1) goto end; buf += len; size -= len;
685 if (lpColumn->mask & LVCF_WIDTH)
686 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
688 if (len == -1) goto end; buf += len; size -= len;
689 if (lpColumn->mask & LVCF_TEXT)
690 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
692 if (len == -1) goto end; buf += len; size -= len;
693 if (lpColumn->mask & LVCF_IMAGE)
694 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
696 if (len == -1) goto end; buf += len; size -= len;
697 if (lpColumn->mask & LVCF_ORDER)
698 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
700 if (len == -1) goto end; buf += len;
703 buf = text + strlen(text);
705 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
709 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
711 if (!lpht) return "(null)";
713 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
714 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
717 /* Return the corresponding text for a given scroll value */
718 static inline LPCSTR debugscrollcode(int nScrollCode)
722 case SB_LINELEFT: return "SB_LINELEFT";
723 case SB_LINERIGHT: return "SB_LINERIGHT";
724 case SB_PAGELEFT: return "SB_PAGELEFT";
725 case SB_PAGERIGHT: return "SB_PAGERIGHT";
726 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
727 case SB_THUMBTRACK: return "SB_THUMBTRACK";
728 case SB_ENDSCROLL: return "SB_ENDSCROLL";
729 case SB_INTERNAL: return "SB_INTERNAL";
730 default: return "unknown";
735 /******** Notification functions ************************************/
737 static int get_ansi_notification(UINT unicodeNotificationCode)
739 switch (unicodeNotificationCode)
741 case LVN_BEGINLABELEDITA:
742 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
743 case LVN_ENDLABELEDITA:
744 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
745 case LVN_GETDISPINFOA:
746 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
747 case LVN_SETDISPINFOA:
748 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
749 case LVN_ODFINDITEMA:
750 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
751 case LVN_GETINFOTIPA:
752 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
753 /* header forwards */
755 case HDN_TRACKW: return HDN_TRACKA;
757 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
758 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
759 case HDN_ENDDRAG: return HDN_ENDDRAG;
760 case HDN_ITEMCHANGINGA:
761 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
762 case HDN_ITEMCHANGEDA:
763 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
765 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
766 case HDN_DIVIDERDBLCLICKA:
767 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
770 FIXME("unknown notification %x\n", unicodeNotificationCode);
771 return unicodeNotificationCode;
774 /* forwards header notifications to listview parent */
775 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
777 LPCWSTR text = NULL, filter = NULL;
779 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
781 /* on unicode format exit earlier */
782 if (infoPtr->notifyFormat == NFR_UNICODE)
783 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
786 /* header always supplies unicode notifications,
787 all we have to do is to convert strings to ANSI */
790 /* convert item text */
791 if (lpnmh->pitem->mask & HDI_TEXT)
793 text = (LPCWSTR)lpnmh->pitem->pszText;
794 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
796 /* convert filter text */
797 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
798 lpnmh->pitem->pvFilter)
800 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
801 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
804 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
806 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
812 Free(lpnmh->pitem->pszText);
813 lpnmh->pitem->pszText = (LPSTR)text;
817 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
818 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
824 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
828 TRACE("(code=%d)\n", code);
830 pnmh->hwndFrom = infoPtr->hwndSelf;
831 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
833 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
835 TRACE(" <= %ld\n", result);
840 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
843 HWND hwnd = infoPtr->hwndSelf;
844 notify_hdr(infoPtr, code, &nmh);
845 return IsWindow(hwnd);
848 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
859 item.mask = LVIF_PARAM|LVIF_STATE;
860 item.iItem = htInfo->iItem;
862 item.stateMask = (UINT)-1;
863 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
864 nmia.lParam = item.lParam;
865 nmia.uOldState = item.state;
866 nmia.uNewState = item.state | LVIS_ACTIVATING;
867 nmia.uChanged = LVIF_STATE;
870 nmia.iItem = htInfo->iItem;
871 nmia.iSubItem = htInfo->iSubItem;
872 nmia.ptAction = htInfo->pt;
874 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
875 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
876 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
878 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
881 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
883 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
884 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
887 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
891 HWND hwnd = infoPtr->hwndSelf;
893 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
894 ZeroMemory(&nmia, sizeof(nmia));
895 nmia.iItem = lvht->iItem;
896 nmia.iSubItem = lvht->iSubItem;
897 nmia.ptAction = lvht->pt;
898 item.mask = LVIF_PARAM;
899 item.iItem = lvht->iItem;
901 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
902 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
903 return IsWindow(hwnd);
906 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
910 HWND hwnd = infoPtr->hwndSelf;
912 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
914 item.mask = LVIF_PARAM;
917 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
918 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
919 return IsWindow(hwnd);
923 Send notification. depends on dispinfoW having same
924 structure as dispinfoA.
925 infoPtr : listview struct
926 code : *Unicode* notification code
927 pdi : dispinfo structure (can be unicode or ansi)
928 isW : TRUE if dispinfo is Unicode
930 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
932 INT length = 0, ret_length;
933 LPWSTR buffer = NULL, ret_text;
934 BOOL return_ansi = FALSE;
935 BOOL return_unicode = FALSE;
938 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
940 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
941 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
944 ret_length = pdi->item.cchTextMax;
945 ret_text = pdi->item.pszText;
947 if (return_unicode || return_ansi)
949 if (code != LVN_GETDISPINFOW)
951 length = return_ansi ?
952 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
953 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
957 length = pdi->item.cchTextMax;
958 *pdi->item.pszText = 0; /* make sure we don't process garbage */
961 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
962 if (!buffer) return FALSE;
965 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
968 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
971 pdi->item.pszText = buffer;
972 pdi->item.cchTextMax = length;
975 if (infoPtr->notifyFormat == NFR_ANSI)
976 code = get_ansi_notification(code);
978 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
979 ret = notify_hdr(infoPtr, code, &pdi->hdr);
980 TRACE(" resulting code=%d\n", pdi->hdr.code);
982 if (return_ansi || return_unicode)
984 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
986 strcpy((char*)ret_text, (char*)pdi->item.pszText);
988 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
990 strcpyW(ret_text, pdi->item.pszText);
992 else if (return_ansi) /* note : pointer can be changed by app ! */
994 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
995 ret_length, NULL, NULL);
998 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
999 ret_text, ret_length);
1001 pdi->item.pszText = ret_text; /* restores our buffer */
1002 pdi->item.cchTextMax = ret_length;
1008 /* if dipsinfo holder changed notification code then convert */
1009 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1011 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1013 buffer = Alloc(length * sizeof(CHAR));
1014 if (!buffer) return FALSE;
1016 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1017 ret_length, NULL, NULL);
1019 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1026 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1027 const RECT *rcBounds, const LVITEMW *lplvItem)
1029 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1030 lpnmlvcd->nmcd.hdc = hdc;
1031 lpnmlvcd->nmcd.rc = *rcBounds;
1032 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1033 lpnmlvcd->clrText = infoPtr->clrText;
1034 if (!lplvItem) return;
1035 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1036 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1037 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1038 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1039 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1040 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1043 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1045 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1048 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1049 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1050 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1051 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1052 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1053 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1057 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1059 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
1060 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
1061 if (lpnmlvcd->clrText == CLR_DEFAULT)
1062 lpnmlvcd->clrText = comctl32_color.clrWindowText;
1064 /* apparently, for selected items, we have to override the returned values */
1067 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1069 if (infoPtr->bFocus)
1071 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1072 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1074 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1076 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1077 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1082 /* Set the text attributes */
1083 if (lpnmlvcd->clrTextBk != CLR_NONE)
1085 SetBkMode(hdc, OPAQUE);
1086 SetBkColor(hdc,lpnmlvcd->clrTextBk);
1089 SetBkMode(hdc, TRANSPARENT);
1090 SetTextColor(hdc, lpnmlvcd->clrText);
1093 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1095 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1098 /* returns TRUE when repaint needed, FALSE otherwise */
1099 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1101 MEASUREITEMSTRUCT mis;
1102 mis.CtlType = ODT_LISTVIEW;
1103 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1107 mis.itemHeight= infoPtr->nItemHeight;
1108 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1109 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1111 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1117 /******** Item iterator functions **********************************/
1119 static RANGES ranges_create(int count);
1120 static void ranges_destroy(RANGES ranges);
1121 static BOOL ranges_add(RANGES ranges, RANGE range);
1122 static BOOL ranges_del(RANGES ranges, RANGE range);
1123 static void ranges_dump(RANGES ranges);
1125 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1127 RANGE range = { nItem, nItem + 1 };
1129 return ranges_add(ranges, range);
1132 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1134 RANGE range = { nItem, nItem + 1 };
1136 return ranges_del(ranges, range);
1140 * ITERATOR DOCUMENTATION
1142 * The iterator functions allow for easy, and convenient iteration
1143 * over items of interest in the list. Typically, you create a
1144 * iterator, use it, and destroy it, as such:
1147 * iterator_xxxitems(&i, ...);
1148 * while (iterator_{prev,next}(&i)
1150 * //code which uses i.nItem
1152 * iterator_destroy(&i);
1154 * where xxx is either: framed, or visible.
1155 * Note that it is important that the code destroys the iterator
1156 * after it's done with it, as the creation of the iterator may
1157 * allocate memory, which thus needs to be freed.
1159 * You can iterate both forwards, and backwards through the list,
1160 * by using iterator_next or iterator_prev respectively.
1162 * Lower numbered items are draw on top of higher number items in
1163 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1164 * items may overlap). So, to test items, you should use
1166 * which lists the items top to bottom (in Z-order).
1167 * For drawing items, you should use
1169 * which lists the items bottom to top (in Z-order).
1170 * If you keep iterating over the items after the end-of-items
1171 * marker (-1) is returned, the iterator will start from the
1172 * beginning. Typically, you don't need to test for -1,
1173 * because iterator_{next,prev} will return TRUE if more items
1174 * are to be iterated over, or FALSE otherwise.
1176 * Note: the iterator is defined to be bidirectional. That is,
1177 * any number of prev followed by any number of next, or
1178 * five versa, should leave the iterator at the same item:
1179 * prev * n, next * n = next * n, prev * n
1181 * The iterator has a notion of an out-of-order, special item,
1182 * which sits at the start of the list. This is used in
1183 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1184 * which needs to be first, as it may overlap other items.
1186 * The code is a bit messy because we have:
1187 * - a special item to deal with
1188 * - simple range, or composite range
1190 * If you find bugs, or want to add features, please make sure you
1191 * always check/modify *both* iterator_prev, and iterator_next.
1195 * This function iterates through the items in increasing order,
1196 * but prefixed by the special item, then -1. That is:
1197 * special, 1, 2, 3, ..., n, -1.
1198 * Each item is listed only once.
1200 static inline BOOL iterator_next(ITERATOR* i)
1204 i->nItem = i->nSpecial;
1205 if (i->nItem != -1) return TRUE;
1207 if (i->nItem == i->nSpecial)
1209 if (i->ranges) i->index = 0;
1215 if (i->nItem == i->nSpecial) i->nItem++;
1216 if (i->nItem < i->range.upper) return TRUE;
1221 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1222 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1225 else if (i->nItem >= i->range.upper) goto end;
1227 i->nItem = i->range.lower;
1228 if (i->nItem >= 0) goto testitem;
1235 * This function iterates through the items in decreasing order,
1236 * followed by the special item, then -1. That is:
1237 * n, n-1, ..., 3, 2, 1, special, -1.
1238 * Each item is listed only once.
1240 static inline BOOL iterator_prev(ITERATOR* i)
1247 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1250 if (i->nItem == i->nSpecial)
1258 if (i->nItem == i->nSpecial) i->nItem--;
1259 if (i->nItem >= i->range.lower) return TRUE;
1265 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1268 else if (!start && i->nItem < i->range.lower) goto end;
1270 i->nItem = i->range.upper;
1271 if (i->nItem > 0) goto testitem;
1273 return (i->nItem = i->nSpecial) != -1;
1276 static RANGE iterator_range(const ITERATOR *i)
1280 if (!i->ranges) return i->range;
1282 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1284 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1285 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1287 else range.lower = range.upper = 0;
1293 * Releases resources associated with this iterator.
1295 static inline void iterator_destroy(const ITERATOR *i)
1297 ranges_destroy(i->ranges);
1301 * Create an empty iterator.
1303 static inline BOOL iterator_empty(ITERATOR* i)
1305 ZeroMemory(i, sizeof(*i));
1306 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1311 * Create an iterator over a range.
1313 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1321 * Create an iterator over a bunch of ranges.
1322 * Please note that the iterator will take ownership of the ranges,
1323 * and will free them upon destruction.
1325 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1333 * Creates an iterator over the items which intersect frame.
1334 * Uses absolute coordinates rather than compensating for the current offset.
1336 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1338 RECT rcItem, rcTemp;
1340 /* in case we fail, we want to return an empty iterator */
1341 if (!iterator_empty(i)) return FALSE;
1343 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1345 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1349 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1351 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1352 if (IntersectRect(&rcTemp, &rcItem, frame))
1353 i->nSpecial = infoPtr->nFocusedItem;
1355 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1356 /* to do better here, we need to have PosX, and PosY sorted */
1357 TRACE("building icon ranges:\n");
1358 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1360 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1361 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1362 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1363 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1364 if (IntersectRect(&rcTemp, &rcItem, frame))
1365 ranges_additem(i->ranges, nItem);
1369 else if (infoPtr->uView == LV_VIEW_DETAILS)
1373 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1374 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1376 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1377 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1378 if (range.upper <= range.lower) return TRUE;
1379 if (!iterator_rangeitems(i, range)) return FALSE;
1380 TRACE(" report=%s\n", debugrange(&i->range));
1384 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1385 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1386 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1393 if (infoPtr->nItemWidth)
1395 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1396 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1400 nFirstCol = max(frame->left, 0);
1401 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1404 lower = nFirstCol * nPerCol + nFirstRow;
1406 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1407 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1409 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1411 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1412 TRACE("building list ranges:\n");
1413 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1415 item_range.lower = nCol * nPerCol + nFirstRow;
1416 if(item_range.lower >= infoPtr->nItemCount) break;
1417 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1418 TRACE(" list=%s\n", debugrange(&item_range));
1419 ranges_add(i->ranges, item_range);
1427 * Creates an iterator over the items which intersect lprc.
1429 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1434 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1436 LISTVIEW_GetOrigin(infoPtr, &Origin);
1437 OffsetRect(&frame, -Origin.x, -Origin.y);
1439 return iterator_frameditems_absolute(i, infoPtr, &frame);
1443 * Creates an iterator over the items which intersect the visible region of hdc.
1445 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1447 POINT Origin, Position;
1448 RECT rcItem, rcClip;
1451 rgntype = GetClipBox(hdc, &rcClip);
1452 if (rgntype == NULLREGION) return iterator_empty(i);
1453 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1454 if (rgntype == SIMPLEREGION) return TRUE;
1456 /* first deal with the special item */
1457 if (i->nSpecial != -1)
1459 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1460 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1463 /* if we can't deal with the region, we'll just go with the simple range */
1464 LISTVIEW_GetOrigin(infoPtr, &Origin);
1465 TRACE("building visible range:\n");
1466 if (!i->ranges && i->range.lower < i->range.upper)
1468 if (!(i->ranges = ranges_create(50))) return TRUE;
1469 if (!ranges_add(i->ranges, i->range))
1471 ranges_destroy(i->ranges);
1477 /* now delete the invisible items from the list */
1478 while(iterator_next(i))
1480 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1481 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1482 rcItem.top = Position.y + Origin.y;
1483 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1484 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1485 if (!RectVisible(hdc, &rcItem))
1486 ranges_delitem(i->ranges, i->nItem);
1488 /* the iterator should restart on the next iterator_next */
1494 /* Remove common elements from two iterators */
1495 /* Passed iterators have to point on the first elements */
1496 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1498 if(!iter1->ranges || !iter2->ranges) {
1501 if(iter1->ranges || iter2->ranges ||
1502 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1503 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1504 ERR("result is not a one range iterator\n");
1508 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1511 lower = iter1->range.lower;
1512 upper = iter1->range.upper;
1514 if(lower < iter2->range.lower)
1515 iter1->range.upper = iter2->range.lower;
1516 else if(upper > iter2->range.upper)
1517 iter1->range.lower = iter2->range.upper;
1519 iter1->range.lower = iter1->range.upper = -1;
1521 if(iter2->range.lower < lower)
1522 iter2->range.upper = lower;
1523 else if(iter2->range.upper > upper)
1524 iter2->range.lower = upper;
1526 iter2->range.lower = iter2->range.upper = -1;
1531 iterator_next(iter1);
1532 iterator_next(iter2);
1535 if(iter1->nItem==-1 || iter2->nItem==-1)
1538 if(iter1->nItem == iter2->nItem) {
1539 int delete = iter1->nItem;
1541 iterator_prev(iter1);
1542 iterator_prev(iter2);
1543 ranges_delitem(iter1->ranges, delete);
1544 ranges_delitem(iter2->ranges, delete);
1545 iterator_next(iter1);
1546 iterator_next(iter2);
1547 } else if(iter1->nItem > iter2->nItem)
1548 iterator_next(iter2);
1550 iterator_next(iter1);
1553 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1554 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1558 /******** Misc helper functions ************************************/
1560 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1561 WPARAM wParam, LPARAM lParam, BOOL isW)
1563 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1564 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1567 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1569 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1570 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1573 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1575 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1576 if(state == 1 || state == 2)
1580 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1581 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1582 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1586 /* this should be called after window style got updated,
1587 it used to reset view state to match current window style */
1588 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1590 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1593 infoPtr->uView = LV_VIEW_ICON;
1596 infoPtr->uView = LV_VIEW_DETAILS;
1599 infoPtr->uView = LV_VIEW_SMALLICON;
1602 infoPtr->uView = LV_VIEW_LIST;
1606 /* computes next item id value */
1607 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1609 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1613 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1614 return lpID->id + 1;
1619 /******** Internal API functions ************************************/
1621 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1623 static COLUMN_INFO mainItem;
1625 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1626 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1628 /* update cached column rectangles */
1629 if (infoPtr->colRectsDirty)
1632 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1635 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1636 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1637 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1639 Ptr->colRectsDirty = FALSE;
1642 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1645 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1647 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1650 if (infoPtr->hwndHeader) return 0;
1652 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1654 /* setup creation flags */
1655 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1656 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1658 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1661 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1662 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1663 if (!infoPtr->hwndHeader) return -1;
1665 /* set header unicode format */
1666 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1668 /* set header font */
1669 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1671 /* set header image list */
1672 if (infoPtr->himlSmall)
1673 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1675 LISTVIEW_UpdateSize(infoPtr);
1680 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1682 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1685 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1687 return (infoPtr->uView == LV_VIEW_DETAILS ||
1688 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1689 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1692 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1694 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1697 /* used to handle collapse main item column case */
1698 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1700 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1701 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1704 /* Listview invalidation functions: use _only_ these functions to invalidate */
1706 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1708 return infoPtr->bRedraw;
1711 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1713 if(!is_redrawing(infoPtr)) return;
1714 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1715 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1718 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1722 if(!is_redrawing(infoPtr)) return;
1723 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1724 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1727 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1729 POINT Origin, Position;
1732 if(!is_redrawing(infoPtr)) return;
1733 assert (infoPtr->uView == LV_VIEW_DETAILS);
1734 LISTVIEW_GetOrigin(infoPtr, &Origin);
1735 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1736 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1738 rcBox.bottom = infoPtr->nItemHeight;
1739 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1740 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1743 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1745 LISTVIEW_InvalidateRect(infoPtr, NULL);
1748 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1752 if(!is_redrawing(infoPtr)) return;
1753 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1754 rcCol.top = infoPtr->rcList.top;
1755 rcCol.bottom = infoPtr->rcList.bottom;
1756 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1761 * Retrieves the number of items that can fit vertically in the client area.
1764 * [I] infoPtr : valid pointer to the listview structure
1767 * Number of items per row.
1769 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1771 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1773 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1778 * Retrieves the number of items that can fit horizontally in the client
1782 * [I] infoPtr : valid pointer to the listview structure
1785 * Number of items per column.
1787 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1789 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1791 return max(nListHeight / infoPtr->nItemHeight, 1);
1795 /*************************************************************************
1796 * LISTVIEW_ProcessLetterKeys
1798 * Processes keyboard messages generated by pressing the letter keys
1800 * What this does is perform a case insensitive search from the
1801 * current position with the following quirks:
1802 * - If two chars or more are pressed in quick succession we search
1803 * for the corresponding string (e.g. 'abc').
1804 * - If there is a delay we wipe away the current search string and
1805 * restart with just that char.
1806 * - If the user keeps pressing the same character, whether slowly or
1807 * fast, so that the search string is entirely composed of this
1808 * character ('aaaaa' for instance), then we search for first item
1809 * that starting with that character.
1810 * - If the user types the above character in quick succession, then
1811 * we must also search for the corresponding string ('aaaaa'), and
1812 * go to that string if there is a match.
1815 * [I] hwnd : handle to the window
1816 * [I] charCode : the character code, the actual character
1817 * [I] keyData : key data
1825 * - The current implementation has a list of characters it will
1826 * accept and it ignores everything else. In particular it will
1827 * ignore accentuated characters which seems to match what
1828 * Windows does. But I'm not sure it makes sense to follow
1830 * - We don't sound a beep when the search fails.
1834 * TREEVIEW_ProcessLetterKeys
1836 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1838 WCHAR buffer[MAX_PATH];
1839 INT endidx, startidx;
1845 /* simple parameter checking */
1846 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1848 /* only allow the valid WM_CHARs through */
1849 if (!isalnumW(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 != '{' &&
1857 charCode != '/' && charCode != '?' && charCode != '>' &&
1858 charCode != '<' && charCode != ',' && charCode != '~')
1861 /* update the search parameters */
1862 prevTime = infoPtr->lastKeyPressTimestamp;
1863 infoPtr->lastKeyPressTimestamp = GetTickCount();
1864 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1866 if (diff >= 0 && diff < KEY_DELAY)
1868 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1869 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1871 if (infoPtr->charCode != charCode)
1872 infoPtr->charCode = charCode = 0;
1876 infoPtr->charCode = charCode;
1877 infoPtr->szSearchParam[0] = charCode;
1878 infoPtr->nSearchParamLength = 1;
1881 /* and search from the current position */
1883 endidx = infoPtr->nItemCount;
1885 /* should start from next after focused item, so next item that matches
1886 will be selected, if there isn't any and focused matches it will be selected
1887 on second search stage from beginning of the list */
1888 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1889 startidx = infoPtr->nFocusedItem + 1;
1893 /* let application handle this for virtual listview */
1894 if (infoPtr->dwStyle & LVS_OWNERDATA)
1898 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1899 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1900 nmlv.lvfi.psz = infoPtr->szSearchParam;
1901 nmlv.iStart = startidx;
1903 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1905 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1911 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1914 /* start from first item if not found with >= startidx */
1915 if (i == infoPtr->nItemCount && startidx > 0)
1921 for (i = startidx; i < endidx; i++)
1924 item.mask = LVIF_TEXT;
1927 item.pszText = buffer;
1928 item.cchTextMax = MAX_PATH;
1929 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1931 if (lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength) == 0)
1936 else if (nItem == -1 && lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1) == 0)
1938 /* this would work but we must keep looking for a longer match */
1943 if ( nItem != -1 || /* found something */
1944 endidx != infoPtr->nItemCount || /* second search done */
1945 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1951 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1956 /*************************************************************************
1957 * LISTVIEW_UpdateHeaderSize [Internal]
1959 * Function to resize the header control
1962 * [I] hwnd : handle to a window
1963 * [I] nNewScrollPos : scroll pos to set
1968 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1973 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1975 if (!infoPtr->hwndHeader) return;
1977 GetWindowRect(infoPtr->hwndHeader, &winRect);
1978 point[0].x = winRect.left;
1979 point[0].y = winRect.top;
1980 point[1].x = winRect.right;
1981 point[1].y = winRect.bottom;
1983 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1984 point[0].x = -nNewScrollPos;
1985 point[1].x += nNewScrollPos;
1987 SetWindowPos(infoPtr->hwndHeader,0,
1988 point[0].x,point[0].y,point[1].x,point[1].y,
1989 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1990 SWP_NOZORDER | SWP_NOACTIVATE);
1995 * Update the scrollbars. This functions should be called whenever
1996 * the content, size or view changes.
1999 * [I] infoPtr : valid pointer to the listview structure
2004 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
2006 SCROLLINFO horzInfo, vertInfo;
2009 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2011 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2012 horzInfo.cbSize = sizeof(SCROLLINFO);
2013 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2015 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2016 if (infoPtr->uView == LV_VIEW_LIST)
2018 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2019 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2021 /* scroll by at least one column per page */
2022 if(horzInfo.nPage < infoPtr->nItemWidth)
2023 horzInfo.nPage = infoPtr->nItemWidth;
2025 if (infoPtr->nItemWidth)
2026 horzInfo.nPage /= infoPtr->nItemWidth;
2028 else if (infoPtr->uView == LV_VIEW_DETAILS)
2030 horzInfo.nMax = infoPtr->nItemWidth;
2032 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2036 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2039 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2041 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2046 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2047 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2049 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2050 horzInfo.nMax = rcHeader.right;
2051 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2055 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2056 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2057 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2058 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2059 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2061 /* Setting the horizontal scroll can change the listview size
2062 * (and potentially everything else) so we need to recompute
2063 * everything again for the vertical scroll
2066 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2067 vertInfo.cbSize = sizeof(SCROLLINFO);
2068 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2070 if (infoPtr->uView == LV_VIEW_DETAILS)
2072 vertInfo.nMax = infoPtr->nItemCount;
2074 /* scroll by at least one page */
2075 if(vertInfo.nPage < infoPtr->nItemHeight)
2076 vertInfo.nPage = infoPtr->nItemHeight;
2078 if (infoPtr->nItemHeight > 0)
2079 vertInfo.nPage /= infoPtr->nItemHeight;
2081 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2085 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2088 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2089 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2090 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2091 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2092 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2094 /* Change of the range may have changed the scroll pos. If so move the content */
2095 if (dx != 0 || dy != 0)
2098 listRect = infoPtr->rcList;
2099 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2100 SW_ERASE | SW_INVALIDATE);
2103 /* Update the Header Control */
2104 if (infoPtr->hwndHeader)
2106 horzInfo.fMask = SIF_POS;
2107 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2108 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2115 * Shows/hides the focus rectangle.
2118 * [I] infoPtr : valid pointer to the listview structure
2119 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2124 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2128 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2130 if (infoPtr->nFocusedItem < 0) return;
2132 /* we need some gymnastics in ICON mode to handle large items */
2133 if (infoPtr->uView == LV_VIEW_ICON)
2137 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2138 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2140 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2145 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2147 /* for some reason, owner draw should work only in report mode */
2148 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2153 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2154 HFONT hOldFont = SelectObject(hdc, hFont);
2156 item.iItem = infoPtr->nFocusedItem;
2158 item.mask = LVIF_PARAM;
2159 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2161 ZeroMemory(&dis, sizeof(dis));
2162 dis.CtlType = ODT_LISTVIEW;
2163 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2164 dis.itemID = item.iItem;
2165 dis.itemAction = ODA_FOCUS;
2166 if (fShow) dis.itemState |= ODS_FOCUS;
2167 dis.hwndItem = infoPtr->hwndSelf;
2169 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2170 dis.itemData = item.lParam;
2172 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2174 SelectObject(hdc, hOldFont);
2178 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2181 ReleaseDC(infoPtr->hwndSelf, hdc);
2185 * Invalidates all visible selected items.
2187 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2191 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2192 while(iterator_next(&i))
2194 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2195 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2197 iterator_destroy(&i);
2202 * DESCRIPTION: [INTERNAL]
2203 * Computes an item's (left,top) corner, relative to rcView.
2204 * That is, the position has NOT been made relative to the Origin.
2205 * This is deliberate, to avoid computing the Origin over, and
2206 * over again, when this function is called in a loop. Instead,
2207 * one can factor the computation of the Origin before the loop,
2208 * and offset the value returned by this function, on every iteration.
2211 * [I] infoPtr : valid pointer to the listview structure
2212 * [I] nItem : item number
2213 * [O] lpptOrig : item top, left corner
2218 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2220 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2222 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2224 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2225 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2227 else if (infoPtr->uView == LV_VIEW_LIST)
2229 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2230 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2231 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2233 else /* LV_VIEW_DETAILS */
2235 lpptPosition->x = REPORT_MARGINX;
2236 /* item is always at zero indexed column */
2237 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2238 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2239 lpptPosition->y = nItem * infoPtr->nItemHeight;
2244 * DESCRIPTION: [INTERNAL]
2245 * Compute the rectangles of an item. This is to localize all
2246 * the computations in one place. If you are not interested in some
2247 * of these values, simply pass in a NULL -- the function is smart
2248 * enough to compute only what's necessary. The function computes
2249 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2250 * one, the BOX rectangle. This rectangle is very cheap to compute,
2251 * and is guaranteed to contain all the other rectangles. Computing
2252 * the ICON rect is also cheap, but all the others are potentially
2253 * expensive. This gives an easy and effective optimization when
2254 * searching (like point inclusion, or rectangle intersection):
2255 * first test against the BOX, and if TRUE, test against the desired
2257 * If the function does not have all the necessary information
2258 * to computed the requested rectangles, will crash with a
2259 * failed assertion. This is done so we catch all programming
2260 * errors, given that the function is called only from our code.
2262 * We have the following 'special' meanings for a few fields:
2263 * * If LVIS_FOCUSED is set, we assume the item has the focus
2264 * This is important in ICON mode, where it might get a larger
2265 * then usual rectangle
2267 * Please note that subitem support works only in REPORT mode.
2270 * [I] infoPtr : valid pointer to the listview structure
2271 * [I] lpLVItem : item to compute the measures for
2272 * [O] lprcBox : ptr to Box rectangle
2273 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2274 * [0] lprcSelectBox : ptr to select box rectangle
2275 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2276 * [O] lprcIcon : ptr to Icon rectangle
2277 * Same as LVM_GETITEMRECT with LVIR_ICON
2278 * [O] lprcStateIcon: ptr to State Icon rectangle
2279 * [O] lprcLabel : ptr to Label rectangle
2280 * Same as LVM_GETITEMRECT with LVIR_LABEL
2285 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2286 LPRECT lprcBox, LPRECT lprcSelectBox,
2287 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2289 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2290 RECT Box, SelectBox, Icon, Label;
2291 COLUMN_INFO *lpColumnInfo = NULL;
2292 SIZE labelSize = { 0, 0 };
2294 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2296 /* Be smart and try to figure out the minimum we have to do */
2297 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2298 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2300 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2301 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2303 if (lprcSelectBox) doSelectBox = TRUE;
2304 if (lprcLabel) doLabel = TRUE;
2305 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2312 /************************************************************/
2313 /* compute the box rectangle (it should be cheap to do) */
2314 /************************************************************/
2315 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2316 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2318 if (lpLVItem->iSubItem)
2320 Box = lpColumnInfo->rcHeader;
2325 Box.right = infoPtr->nItemWidth;
2328 Box.bottom = infoPtr->nItemHeight;
2330 /******************************************************************/
2331 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2332 /******************************************************************/
2335 LONG state_width = 0;
2337 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2338 state_width = infoPtr->iconStateSize.cx;
2340 if (infoPtr->uView == LV_VIEW_ICON)
2342 Icon.left = Box.left + state_width;
2343 if (infoPtr->himlNormal)
2344 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2345 Icon.top = Box.top + ICON_TOP_PADDING;
2346 Icon.right = Icon.left;
2347 Icon.bottom = Icon.top;
2348 if (infoPtr->himlNormal)
2350 Icon.right += infoPtr->iconSize.cx;
2351 Icon.bottom += infoPtr->iconSize.cy;
2354 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2356 Icon.left = Box.left + state_width;
2358 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2360 /* we need the indent in report mode */
2361 assert(lpLVItem->mask & LVIF_INDENT);
2362 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2366 Icon.right = Icon.left;
2367 if (infoPtr->himlSmall &&
2368 (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2369 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2370 Icon.right += infoPtr->iconSize.cx;
2371 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2373 if(lprcIcon) *lprcIcon = Icon;
2374 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2376 /* TODO: is this correct? */
2379 lprcStateIcon->left = Icon.left - state_width;
2380 lprcStateIcon->right = Icon.left;
2381 lprcStateIcon->top = Icon.top;
2382 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2383 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2386 else Icon.right = 0;
2388 /************************************************************/
2389 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2390 /************************************************************/
2393 /* calculate how far to the right can the label stretch */
2394 Label.right = Box.right;
2395 if (infoPtr->uView == LV_VIEW_DETAILS)
2397 if (lpLVItem->iSubItem == 0)
2399 /* we need a zero based rect here */
2400 Label = lpColumnInfo->rcHeader;
2401 OffsetRect(&Label, -Label.left, 0);
2405 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2407 labelSize.cx = infoPtr->nItemWidth;
2408 labelSize.cy = infoPtr->nItemHeight;
2412 /* we need the text in non owner draw mode */
2413 assert(lpLVItem->mask & LVIF_TEXT);
2414 if (is_text(lpLVItem->pszText))
2416 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2417 HDC hdc = GetDC(infoPtr->hwndSelf);
2418 HFONT hOldFont = SelectObject(hdc, hFont);
2422 /* compute rough rectangle where the label will go */
2423 SetRectEmpty(&rcText);
2424 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2425 rcText.bottom = infoPtr->nItemHeight;
2426 if (infoPtr->uView == LV_VIEW_ICON)
2427 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2429 /* now figure out the flags */
2430 if (infoPtr->uView == LV_VIEW_ICON)
2431 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2433 uFormat = LV_SL_DT_FLAGS;
2435 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2437 if (rcText.right != rcText.left)
2438 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2440 labelSize.cy = rcText.bottom - rcText.top;
2442 SelectObject(hdc, hOldFont);
2443 ReleaseDC(infoPtr->hwndSelf, hdc);
2447 if (infoPtr->uView == LV_VIEW_ICON)
2449 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2450 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2451 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2452 Label.right = Label.left + labelSize.cx;
2453 Label.bottom = Label.top + infoPtr->nItemHeight;
2454 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2456 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2457 labelSize.cy /= infoPtr->ntmHeight;
2458 labelSize.cy = max(labelSize.cy, 1);
2459 labelSize.cy *= infoPtr->ntmHeight;
2461 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2463 else if (infoPtr->uView == LV_VIEW_DETAILS)
2465 Label.left = Icon.right;
2466 Label.top = Box.top;
2467 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2468 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2469 Label.bottom = Label.top + infoPtr->nItemHeight;
2471 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2473 Label.left = Icon.right;
2474 Label.top = Box.top;
2475 Label.right = min(Label.left + labelSize.cx, Label.right);
2476 Label.bottom = Label.top + infoPtr->nItemHeight;
2479 if (lprcLabel) *lprcLabel = Label;
2480 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2483 /************************************************************/
2484 /* compute SELECT bounding box */
2485 /************************************************************/
2488 if (infoPtr->uView == LV_VIEW_DETAILS)
2490 SelectBox.left = Icon.left;
2491 SelectBox.top = Box.top;
2492 SelectBox.bottom = Box.bottom;
2495 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2497 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2501 UnionRect(&SelectBox, &Icon, &Label);
2503 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2504 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2507 /* Fix the Box if necessary */
2510 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2511 else *lprcBox = Box;
2513 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2517 * DESCRIPTION: [INTERNAL]
2520 * [I] infoPtr : valid pointer to the listview structure
2521 * [I] nItem : item number
2522 * [O] lprcBox : ptr to Box rectangle
2527 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2529 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2530 POINT Position, Origin;
2533 LISTVIEW_GetOrigin(infoPtr, &Origin);
2534 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2536 /* Be smart and try to figure out the minimum we have to do */
2538 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2539 lvItem.mask |= LVIF_TEXT;
2540 lvItem.iItem = nItem;
2541 lvItem.iSubItem = 0;
2542 lvItem.pszText = szDispText;
2543 lvItem.cchTextMax = DISP_TEXT_SIZE;
2544 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2545 if (infoPtr->uView == LV_VIEW_ICON)
2547 lvItem.mask |= LVIF_STATE;
2548 lvItem.stateMask = LVIS_FOCUSED;
2549 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2551 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2553 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2554 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2556 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2559 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2562 /* LISTVIEW_MapIdToIndex helper */
2563 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2565 ITEM_ID *id1 = (ITEM_ID*)p1;
2566 ITEM_ID *id2 = (ITEM_ID*)p2;
2568 if (id1->id == id2->id) return 0;
2570 return (id1->id < id2->id) ? -1 : 1;
2575 * Returns the item index for id specified.
2578 * [I] infoPtr : valid pointer to the listview structure
2579 * [I] iID : item id to get index for
2582 * Item index, or -1 on failure.
2584 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2589 TRACE("iID=%d\n", iID);
2591 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2592 if (infoPtr->nItemCount == 0) return -1;
2595 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2599 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2600 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2608 * Returns the item id for index given.
2611 * [I] infoPtr : valid pointer to the listview structure
2612 * [I] iItem : item index to get id for
2617 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2622 TRACE("iItem=%d\n", iItem);
2624 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2625 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2627 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2628 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2630 return lpItem->id->id;
2635 * Returns the current icon position, and advances it along the top.
2636 * The returned position is not offset by Origin.
2639 * [I] infoPtr : valid pointer to the listview structure
2640 * [O] lpPos : will get the current icon position
2645 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2647 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2649 *lpPos = infoPtr->currIconPos;
2651 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2652 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2654 infoPtr->currIconPos.x = 0;
2655 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2661 * Returns the current icon position, and advances it down the left edge.
2662 * The returned position is not offset by Origin.
2665 * [I] infoPtr : valid pointer to the listview structure
2666 * [O] lpPos : will get the current icon position
2671 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2673 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2675 *lpPos = infoPtr->currIconPos;
2677 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2678 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2680 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2681 infoPtr->currIconPos.y = 0;
2687 * Moves an icon to the specified position.
2688 * It takes care of invalidating the item, etc.
2691 * [I] infoPtr : valid pointer to the listview structure
2692 * [I] nItem : the item to move
2693 * [I] lpPos : the new icon position
2694 * [I] isNew : flags the item as being new
2700 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2706 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2707 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2709 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2710 LISTVIEW_InvalidateItem(infoPtr, nItem);
2713 /* Allocating a POINTER for every item is too resource intensive,
2714 * so we'll keep the (x,y) in different arrays */
2715 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2716 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2718 LISTVIEW_InvalidateItem(infoPtr, nItem);
2725 * Arranges listview items in icon display mode.
2728 * [I] infoPtr : valid pointer to the listview structure
2729 * [I] nAlignCode : alignment code
2735 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2737 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2741 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2743 TRACE("nAlignCode=%d\n", nAlignCode);
2745 if (nAlignCode == LVA_DEFAULT)
2747 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2748 else nAlignCode = LVA_ALIGNTOP;
2753 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2754 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2755 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2756 default: return FALSE;
2759 infoPtr->bAutoarrange = TRUE;
2760 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2761 for (i = 0; i < infoPtr->nItemCount; i++)
2763 next_pos(infoPtr, &pos);
2764 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2772 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2773 * For LVS_REPORT always returns empty rectangle.
2776 * [I] infoPtr : valid pointer to the listview structure
2777 * [O] lprcView : bounding rectangle
2783 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2787 SetRectEmpty(lprcView);
2789 switch (infoPtr->uView)
2792 case LV_VIEW_SMALLICON:
2793 for (i = 0; i < infoPtr->nItemCount; i++)
2795 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2796 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2797 lprcView->right = max(lprcView->right, x);
2798 lprcView->bottom = max(lprcView->bottom, y);
2800 if (infoPtr->nItemCount > 0)
2802 lprcView->right += infoPtr->nItemWidth;
2803 lprcView->bottom += infoPtr->nItemHeight;
2808 y = LISTVIEW_GetCountPerColumn(infoPtr);
2809 x = infoPtr->nItemCount / y;
2810 if (infoPtr->nItemCount % y) x++;
2811 lprcView->right = x * infoPtr->nItemWidth;
2812 lprcView->bottom = y * infoPtr->nItemHeight;
2819 * Retrieves the bounding rectangle of all the items.
2822 * [I] infoPtr : valid pointer to the listview structure
2823 * [O] lprcView : bounding rectangle
2829 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2833 TRACE("(lprcView=%p)\n", lprcView);
2835 if (!lprcView) return FALSE;
2837 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2839 if (infoPtr->uView != LV_VIEW_DETAILS)
2841 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2842 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2845 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2852 * Retrieves the subitem pointer associated with the subitem index.
2855 * [I] hdpaSubItems : DPA handle for a specific item
2856 * [I] nSubItem : index of subitem
2859 * SUCCESS : subitem pointer
2862 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2864 SUBITEM_INFO *lpSubItem;
2867 /* we should binary search here if need be */
2868 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2870 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2871 if (lpSubItem->iSubItem == nSubItem)
2881 * Calculates the desired item width.
2884 * [I] infoPtr : valid pointer to the listview structure
2887 * The desired item width.
2889 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2893 TRACE("uView=%d\n", infoPtr->uView);
2895 if (infoPtr->uView == LV_VIEW_ICON)
2896 nItemWidth = infoPtr->iconSpacing.cx;
2897 else if (infoPtr->uView == LV_VIEW_DETAILS)
2899 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2904 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2905 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2907 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2908 nItemWidth = rcHeader.right;
2911 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2913 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2917 lvItem.mask = LVIF_TEXT;
2918 lvItem.iSubItem = 0;
2920 for (i = 0; i < infoPtr->nItemCount; i++)
2923 lvItem.pszText = szDispText;
2924 lvItem.cchTextMax = DISP_TEXT_SIZE;
2925 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2926 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2930 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2931 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2933 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2941 * Calculates the desired item height.
2944 * [I] infoPtr : valid pointer to the listview structure
2947 * The desired item height.
2949 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2953 TRACE("uView=%d\n", infoPtr->uView);
2955 if (infoPtr->uView == LV_VIEW_ICON)
2956 nItemHeight = infoPtr->iconSpacing.cy;
2959 nItemHeight = infoPtr->ntmHeight;
2960 if (infoPtr->himlState)
2961 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2962 if (infoPtr->himlSmall)
2963 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2964 nItemHeight += HEIGHT_PADDING;
2965 if (infoPtr->nMeasureItemHeight > 0)
2966 nItemHeight = infoPtr->nMeasureItemHeight;
2969 return max(nItemHeight, 1);
2974 * Updates the width, and height of an item.
2977 * [I] infoPtr : valid pointer to the listview structure
2982 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2984 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2985 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2991 * Retrieves and saves important text metrics info for the current
2995 * [I] infoPtr : valid pointer to the listview structure
2998 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3000 HDC hdc = GetDC(infoPtr->hwndSelf);
3001 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3002 HFONT hOldFont = SelectObject(hdc, hFont);
3006 if (GetTextMetricsW(hdc, &tm))
3008 infoPtr->ntmHeight = tm.tmHeight;
3009 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3012 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3013 infoPtr->nEllipsisWidth = sz.cx;
3015 SelectObject(hdc, hOldFont);
3016 ReleaseDC(infoPtr->hwndSelf, hdc);
3018 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3023 * A compare function for ranges
3026 * [I] range1 : pointer to range 1;
3027 * [I] range2 : pointer to range 2;
3031 * > 0 : if range 1 > range 2
3032 * < 0 : if range 2 > range 1
3033 * = 0 : if range intersects range 2
3035 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3039 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3041 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3046 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3051 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3053 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3058 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3060 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3061 ranges_dump(ranges);
3062 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3064 prev = DPA_GetPtr(ranges->hdpa, 0);
3065 assert (prev->lower >= 0 && prev->lower < prev->upper);
3066 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3068 curr = DPA_GetPtr(ranges->hdpa, i);
3069 assert (prev->upper <= curr->lower);
3070 assert (curr->lower < curr->upper);
3074 TRACE("--- Done checking---\n");
3077 static RANGES ranges_create(int count)
3079 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3080 if (!ranges) return NULL;
3081 ranges->hdpa = DPA_Create(count);
3082 if (ranges->hdpa) return ranges;
3087 static void ranges_clear(RANGES ranges)
3091 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3092 Free(DPA_GetPtr(ranges->hdpa, i));
3093 DPA_DeleteAllPtrs(ranges->hdpa);
3097 static void ranges_destroy(RANGES ranges)
3099 if (!ranges) return;
3100 ranges_clear(ranges);
3101 DPA_Destroy(ranges->hdpa);
3105 static RANGES ranges_clone(RANGES ranges)
3110 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3112 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3114 RANGE *newrng = Alloc(sizeof(RANGE));
3115 if (!newrng) goto fail;
3116 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3117 DPA_SetPtr(clone->hdpa, i, newrng);
3122 TRACE ("clone failed\n");
3123 ranges_destroy(clone);
3127 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3131 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3132 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3137 static void ranges_dump(RANGES ranges)
3141 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3142 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3145 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3147 RANGE srchrng = { nItem, nItem + 1 };
3149 TRACE("(nItem=%d)\n", nItem);
3150 ranges_check(ranges, "before contain");
3151 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3154 static INT ranges_itemcount(RANGES ranges)
3158 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3160 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3161 count += sel->upper - sel->lower;
3167 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3169 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3172 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3173 if (index == -1) return TRUE;
3175 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3177 chkrng = DPA_GetPtr(ranges->hdpa, index);
3178 if (chkrng->lower >= nItem)
3179 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3180 if (chkrng->upper > nItem)
3181 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3186 static BOOL ranges_add(RANGES ranges, RANGE range)
3191 TRACE("(%s)\n", debugrange(&range));
3192 ranges_check(ranges, "before add");
3194 /* try find overlapping regions first */
3195 srchrgn.lower = range.lower - 1;
3196 srchrgn.upper = range.upper + 1;
3197 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3203 TRACE("Adding new range\n");
3205 /* create the brand new range to insert */
3206 newrgn = Alloc(sizeof(RANGE));
3207 if(!newrgn) goto fail;
3210 /* figure out where to insert it */
3211 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3212 TRACE("index=%d\n", index);
3213 if (index == -1) index = 0;
3215 /* and get it over with */
3216 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3224 RANGE *chkrgn, *mrgrgn;
3225 INT fromindex, mergeindex;
3227 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3228 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3230 chkrgn->lower = min(range.lower, chkrgn->lower);
3231 chkrgn->upper = max(range.upper, chkrgn->upper);
3233 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3235 /* merge now common ranges */
3237 srchrgn.lower = chkrgn->lower - 1;
3238 srchrgn.upper = chkrgn->upper + 1;
3242 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3243 if (mergeindex == -1) break;
3244 if (mergeindex == index)
3246 fromindex = index + 1;
3250 TRACE("Merge with index %i\n", mergeindex);
3252 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3253 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3254 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3256 DPA_DeletePtr(ranges->hdpa, mergeindex);
3257 if (mergeindex < index) index --;
3261 ranges_check(ranges, "after add");
3265 ranges_check(ranges, "failed add");
3269 static BOOL ranges_del(RANGES ranges, RANGE range)
3274 TRACE("(%s)\n", debugrange(&range));
3275 ranges_check(ranges, "before del");
3277 /* we don't use DPAS_SORTED here, since we need *
3278 * to find the first overlapping range */
3279 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3282 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3284 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3286 /* case 1: Same range */
3287 if ( (chkrgn->upper == range.upper) &&
3288 (chkrgn->lower == range.lower) )
3290 DPA_DeletePtr(ranges->hdpa, index);
3294 /* case 2: engulf */
3295 else if ( (chkrgn->upper <= range.upper) &&
3296 (chkrgn->lower >= range.lower) )
3298 DPA_DeletePtr(ranges->hdpa, index);
3301 /* case 3: overlap upper */
3302 else if ( (chkrgn->upper <= range.upper) &&
3303 (chkrgn->lower < range.lower) )
3305 chkrgn->upper = range.lower;
3307 /* case 4: overlap lower */
3308 else if ( (chkrgn->upper > range.upper) &&
3309 (chkrgn->lower >= range.lower) )
3311 chkrgn->lower = range.upper;
3314 /* case 5: fully internal */
3319 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3320 newrgn->lower = chkrgn->lower;
3321 newrgn->upper = range.lower;
3322 chkrgn->lower = range.upper;
3323 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3331 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3334 ranges_check(ranges, "after del");
3338 ranges_check(ranges, "failed del");
3344 * Removes all selection ranges
3347 * [I] infoPtr : valid pointer to the listview structure
3348 * [I] toSkip : item range to skip removing the selection
3354 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3363 lvItem.stateMask = LVIS_SELECTED;
3365 /* need to clone the DPA because callbacks can change it */
3366 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3367 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3368 while(iterator_next(&i))
3369 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3370 /* note that the iterator destructor will free the cloned range */
3371 iterator_destroy(&i);
3376 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3380 if (!(toSkip = ranges_create(1))) return FALSE;
3381 if (nItem != -1) ranges_additem(toSkip, nItem);
3382 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3383 ranges_destroy(toSkip);
3387 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3389 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3394 * Retrieves the number of items that are marked as selected.
3397 * [I] infoPtr : valid pointer to the listview structure
3400 * Number of items selected.
3402 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3404 INT nSelectedCount = 0;
3406 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3409 for (i = 0; i < infoPtr->nItemCount; i++)
3411 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3416 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3418 TRACE("nSelectedCount=%d\n", nSelectedCount);
3419 return nSelectedCount;
3424 * Manages the item focus.
3427 * [I] infoPtr : valid pointer to the listview structure
3428 * [I] nItem : item index
3431 * TRUE : focused item changed
3432 * FALSE : focused item has NOT changed
3434 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3436 INT oldFocus = infoPtr->nFocusedItem;
3439 if (nItem == infoPtr->nFocusedItem) return FALSE;
3441 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3442 lvItem.stateMask = LVIS_FOCUSED;
3443 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3445 return oldFocus != infoPtr->nFocusedItem;
3448 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3450 if (nShiftItem < nItem) return nShiftItem;
3452 if (nShiftItem > nItem) return nShiftItem + direction;
3454 if (direction > 0) return nShiftItem + direction;
3456 return min(nShiftItem, infoPtr->nItemCount - 1);
3459 /* This function updates focus index.
3462 focus : current focus index
3463 item : index of item to be added/removed
3464 direction : add/remove flag
3466 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3468 BOOL old_change = infoPtr->bDoChangeNotify;
3470 infoPtr->bDoChangeNotify = FALSE;
3471 focus = shift_item(infoPtr, focus, item, direction);
3472 if (focus != infoPtr->nFocusedItem)
3473 LISTVIEW_SetItemFocus(infoPtr, focus);
3474 infoPtr->bDoChangeNotify = old_change;
3479 * Updates the various indices after an item has been inserted or deleted.
3482 * [I] infoPtr : valid pointer to the listview structure
3483 * [I] nItem : item index
3484 * [I] direction : Direction of shift, +1 or -1.
3489 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3491 TRACE("Shifting %i, %i steps\n", nItem, direction);
3493 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3494 assert(abs(direction) == 1);
3495 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3497 /* But we are not supposed to modify nHotItem! */
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)) {
3597 ranges_destroy (selection);
3600 rcSelMark.left = LVIR_BOUNDS;
3601 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3602 ranges_destroy (selection);
3605 UnionRect(&rcSel, &rcItem, &rcSelMark);
3606 iterator_frameditems(&i, infoPtr, &rcSel);
3607 while(iterator_next(&i))
3609 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3610 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3612 iterator_destroy(&i);
3615 /* disable per item notifications on LVS_OWNERDATA style
3616 FIXME: single LVN_ODSTATECHANGED should be used */
3617 bOldChange = infoPtr->bDoChangeNotify;
3618 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3620 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3623 iterator_rangesitems(&i, selection);
3624 while(iterator_next(&i))
3625 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3626 /* this will also destroy the selection */
3627 iterator_destroy(&i);
3629 infoPtr->bDoChangeNotify = bOldChange;
3631 LISTVIEW_SetItemFocus(infoPtr, nItem);
3636 * Sets a single selection.
3639 * [I] infoPtr : valid pointer to the listview structure
3640 * [I] nItem : item index
3645 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3649 TRACE("nItem=%d\n", nItem);
3651 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3653 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3654 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3655 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3657 infoPtr->nSelectionMark = nItem;
3662 * Set selection(s) with keyboard.
3665 * [I] infoPtr : valid pointer to the listview structure
3666 * [I] nItem : item index
3667 * [I] space : VK_SPACE code sent
3670 * SUCCESS : TRUE (needs to be repainted)
3671 * FAILURE : FALSE (nothing has changed)
3673 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3675 /* FIXME: pass in the state */
3676 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3677 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3678 BOOL bResult = FALSE;
3680 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3681 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3685 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3686 LISTVIEW_SetSelection(infoPtr, nItem);
3690 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3694 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3695 lvItem.stateMask = LVIS_SELECTED;
3698 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3699 if (lvItem.state & LVIS_SELECTED)
3700 infoPtr->nSelectionMark = nItem;
3702 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3705 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3708 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3712 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3714 LVHITTESTINFO lvHitTestInfo;
3716 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3717 lvHitTestInfo.pt.x = pt.x;
3718 lvHitTestInfo.pt.y = pt.y;
3720 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3722 lpLVItem->mask = LVIF_PARAM;
3723 lpLVItem->iItem = lvHitTestInfo.iItem;
3724 lpLVItem->iSubItem = 0;
3726 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3729 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3731 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3732 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3733 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3738 * Called when the mouse is being actively tracked and has hovered for a specified
3742 * [I] infoPtr : valid pointer to the listview structure
3743 * [I] fwKeys : key indicator
3744 * [I] x,y : mouse position
3747 * 0 if the message was processed, non-zero if there was an error
3750 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3751 * over the item for a certain period of time.
3754 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3758 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3760 if (LISTVIEW_IsHotTracking(infoPtr))
3768 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3769 LISTVIEW_SetSelection(infoPtr, item.iItem);
3771 SetFocus(infoPtr->hwndSelf);
3777 #define SCROLL_LEFT 0x1
3778 #define SCROLL_RIGHT 0x2
3779 #define SCROLL_UP 0x4
3780 #define SCROLL_DOWN 0x8
3784 * Utility routine to draw and highlight items within a marquee selection rectangle.
3787 * [I] infoPtr : valid pointer to the listview structure
3788 * [I] coords_orig : original co-ordinates of the cursor
3789 * [I] coords_offs : offsetted coordinates of the cursor
3790 * [I] offset : offset amount
3791 * [I] scroll : Bitmask of which directions we should scroll, if at all
3796 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3797 const POINT *coords_offs, const POINT *offset,
3800 BOOL controlDown = FALSE;
3802 ITERATOR old_elems, new_elems;
3805 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3807 rect.left = infoPtr->marqueeOrigin.x;
3808 rect.right = coords_offs->x;
3812 rect.left = coords_offs->x;
3813 rect.right = infoPtr->marqueeOrigin.x;
3816 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3818 rect.top = infoPtr->marqueeOrigin.y;
3819 rect.bottom = coords_offs->y;
3823 rect.top = coords_offs->y;
3824 rect.bottom = infoPtr->marqueeOrigin.y;
3827 /* Cancel out the old marquee rectangle and draw the new one */
3828 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3830 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3831 the cursor is further away */
3833 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3834 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3836 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3837 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3839 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3840 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3842 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3843 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3845 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3847 CopyRect(&infoPtr->marqueeRect, &rect);
3849 CopyRect(&infoPtr->marqueeDrawRect, &rect);
3850 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3852 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3853 iterator_remove_common_items(&old_elems, &new_elems);
3855 /* Iterate over no longer selected items */
3856 while (iterator_next(&old_elems))
3858 if (old_elems.nItem > -1)
3860 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3863 item.state = LVIS_SELECTED;
3865 item.stateMask = LVIS_SELECTED;
3867 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3870 iterator_destroy(&old_elems);
3873 /* Iterate over newly selected items */
3874 if (GetKeyState(VK_CONTROL) & 0x8000)
3877 while (iterator_next(&new_elems))
3879 if (new_elems.nItem > -1)
3881 /* If CTRL is pressed, invert. If not, always select the item. */
3882 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3885 item.state = LVIS_SELECTED;
3887 item.stateMask = LVIS_SELECTED;
3889 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3892 iterator_destroy(&new_elems);
3894 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3899 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3900 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3903 * [I] hwnd : Handle to the listview
3904 * [I] uMsg : WM_TIMER (ignored)
3905 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3906 * [I] dwTimer : The elapsed time (ignored)
3911 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3913 LISTVIEW_INFO *infoPtr;
3914 SCROLLINFO scrollInfo;
3920 infoPtr = (LISTVIEW_INFO *) idEvent;
3925 /* Get the current cursor position and convert to client coordinates */
3926 GetCursorPos(&coords_orig);
3927 ScreenToClient(hWnd, &coords_orig);
3929 /* Ensure coordinates are within client bounds */
3930 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3931 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3934 LISTVIEW_GetOrigin(infoPtr, &offset);
3936 /* Offset coordinates by the appropriate amount */
3937 coords_offs.x -= offset.x;
3938 coords_offs.y -= offset.y;
3940 scrollInfo.cbSize = sizeof(SCROLLINFO);
3941 scrollInfo.fMask = SIF_ALL;
3943 /* Work out in which directions we can scroll */
3944 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3946 if (scrollInfo.nPos != scrollInfo.nMin)
3947 scroll |= SCROLL_UP;
3949 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3950 scroll |= SCROLL_DOWN;
3953 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3955 if (scrollInfo.nPos != scrollInfo.nMin)
3956 scroll |= SCROLL_LEFT;
3958 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3959 scroll |= SCROLL_RIGHT;
3962 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
3963 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
3964 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
3965 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
3967 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
3973 * Called whenever WM_MOUSEMOVE is received.
3976 * [I] infoPtr : valid pointer to the listview structure
3977 * [I] fwKeys : key indicator
3978 * [I] x,y : mouse position
3981 * 0 if the message is processed, non-zero if there was an error
3983 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3989 if (!(fwKeys & MK_LBUTTON))
3990 infoPtr->bLButtonDown = FALSE;
3992 if (infoPtr->bLButtonDown)
3994 rect.left = rect.right = infoPtr->ptClickPos.x;
3995 rect.top = rect.bottom = infoPtr->ptClickPos.y;
3997 InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
4000 if (infoPtr->bLButtonDown)
4002 if (infoPtr->bMarqueeSelect)
4012 LISTVIEW_GetOrigin(infoPtr, &offset);
4014 /* Ensure coordinates are within client bounds */
4015 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4016 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4018 /* Offset coordinates by the appropriate amount */
4019 coords_offs.x -= offset.x;
4020 coords_offs.y -= offset.y;
4022 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4023 move the mouse again */
4025 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4026 (y >= infoPtr->rcList.bottom))
4028 if (!infoPtr->bScrolling)
4030 infoPtr->bScrolling = TRUE;
4031 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4036 infoPtr->bScrolling = FALSE;
4037 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4040 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4048 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4050 /* reset item marker */
4051 if (infoPtr->nLButtonDownItem != ht.iItem)
4052 infoPtr->nLButtonDownItem = -1;
4054 if (!PtInRect(&rect, pt))
4056 /* this path covers the following:
4057 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4058 2. change focus with keys
4059 3. move mouse over item from step 1 selects it and moves focus on it */
4060 if (infoPtr->nLButtonDownItem != -1 &&
4061 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4065 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4066 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4068 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4069 infoPtr->nLButtonDownItem = -1;
4072 if (!infoPtr->bDragging)
4074 ht.pt = infoPtr->ptClickPos;
4075 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4077 /* If the click is outside the range of an item, begin a
4078 highlight. If not, begin an item drag. */
4083 /* If we're allowing multiple selections, send notification.
4084 If return value is non-zero, cancel. */
4085 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4087 /* Store the absolute coordinates of the click */
4089 LISTVIEW_GetOrigin(infoPtr, &offset);
4091 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4092 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4094 /* Begin selection and capture mouse */
4095 infoPtr->bMarqueeSelect = TRUE;
4096 SetCapture(infoPtr->hwndSelf);
4103 ZeroMemory(&nmlv, sizeof(nmlv));
4104 nmlv.iItem = ht.iItem;
4105 nmlv.ptAction = infoPtr->ptClickPos;
4107 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4108 infoPtr->bDragging = TRUE;
4116 /* see if we are supposed to be tracking mouse hovering */
4117 if (LISTVIEW_IsHotTracking(infoPtr)) {
4118 TRACKMOUSEEVENT trackinfo;
4120 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4121 trackinfo.dwFlags = TME_QUERY;
4123 /* see if we are already tracking this hwnd */
4124 _TrackMouseEvent(&trackinfo);
4126 if(!(trackinfo.dwFlags & TME_HOVER) || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4127 trackinfo.dwFlags = TME_HOVER;
4128 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4129 trackinfo.hwndTrack = infoPtr->hwndSelf;
4131 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4132 _TrackMouseEvent(&trackinfo);
4141 * Tests whether the item is assignable to a list with style lStyle
4143 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4145 if ( (lpLVItem->mask & LVIF_TEXT) &&
4146 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4147 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4155 * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4158 * [I] infoPtr : valid pointer to the listview structure
4159 * [I] lpLVItem : valid pointer to new item attributes
4160 * [I] isNew : the item being set is being inserted
4161 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4162 * [O] bChanged : will be set to TRUE if the item really changed
4168 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4174 /* stateMask is ignored for LVM_INSERTITEM */
4175 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4179 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4181 if (lpLVItem->mask == 0) return TRUE;
4183 if (infoPtr->dwStyle & LVS_OWNERDATA)
4185 /* a virtual listview only stores selection and focus */
4186 if (lpLVItem->mask & ~LVIF_STATE)
4192 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4193 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4197 /* we need to get the lParam and state of the item */
4198 item.iItem = lpLVItem->iItem;
4199 item.iSubItem = lpLVItem->iSubItem;
4200 item.mask = LVIF_STATE | LVIF_PARAM;
4201 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4205 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4207 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4208 /* determine what fields will change */
4209 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4210 uChanged |= LVIF_STATE;
4212 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4213 uChanged |= LVIF_IMAGE;
4215 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4216 uChanged |= LVIF_PARAM;
4218 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4219 uChanged |= LVIF_INDENT;
4221 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4222 uChanged |= LVIF_TEXT;
4224 TRACE("change mask=0x%x\n", uChanged);
4226 memset(&nmlv, 0, sizeof(NMLISTVIEW));
4227 nmlv.iItem = lpLVItem->iItem;
4228 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4229 nmlv.uOldState = item.state;
4230 nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4231 nmlv.lParam = item.lParam;
4233 /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4234 and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4235 are enabled. Even nothing really changed we still need to send this,
4236 in this case uChanged mask is just set to passed item mask. */
4237 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4239 HWND hwndSelf = infoPtr->hwndSelf;
4241 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4243 if (!IsWindow(hwndSelf))
4247 /* When item is inserted we need to shift existing focus index if new item has lower index. */
4248 if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4249 /* this means we won't hit a focus change path later */
4250 ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4252 if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4253 infoPtr->nFocusedItem++;
4256 if (!uChanged) return TRUE;
4259 /* copy information */
4260 if (lpLVItem->mask & LVIF_TEXT)
4261 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4263 if (lpLVItem->mask & LVIF_IMAGE)
4264 lpItem->hdr.iImage = lpLVItem->iImage;
4266 if (lpLVItem->mask & LVIF_PARAM)
4267 lpItem->lParam = lpLVItem->lParam;
4269 if (lpLVItem->mask & LVIF_INDENT)
4270 lpItem->iIndent = lpLVItem->iIndent;
4272 if (uChanged & LVIF_STATE)
4274 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4276 lpItem->state &= ~stateMask;
4277 lpItem->state |= (lpLVItem->state & stateMask);
4279 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4281 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4282 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4284 else if (stateMask & LVIS_SELECTED)
4286 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4288 /* If we are asked to change focus, and we manage it, do it.
4289 It's important to have all new item data stored at this point,
4290 cause changing existing focus could result in redrawing operation,
4291 which in turn could ask for disp data, application should see all data
4292 for inserted item when processing LVN_GETDISPINFO.
4294 The way this works application will see nested item change notifications -
4295 changed item notifications interrupted by ones from item loosing focus. */
4296 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4298 if (lpLVItem->state & LVIS_FOCUSED)
4300 /* update selection mark */
4301 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4302 infoPtr->nSelectionMark = lpLVItem->iItem;
4304 if (infoPtr->nFocusedItem != -1)
4306 /* remove current focus */
4307 item.mask = LVIF_STATE;
4309 item.stateMask = LVIS_FOCUSED;
4311 /* recurse with redrawing an item */
4312 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4315 infoPtr->nFocusedItem = lpLVItem->iItem;
4316 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4318 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4320 infoPtr->nFocusedItem = -1;
4325 /* if we're inserting the item, we're done */
4326 if (isNew) return TRUE;
4328 /* send LVN_ITEMCHANGED notification */
4329 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4330 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4337 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4340 * [I] infoPtr : valid pointer to the listview structure
4341 * [I] lpLVItem : valid pointer to new subitem attributes
4342 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4343 * [O] bChanged : will be set to TRUE if the item really changed
4349 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4352 SUBITEM_INFO *lpSubItem;
4354 /* we do not support subitems for virtual listviews */
4355 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4357 /* set subitem only if column is present */
4358 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4360 /* First do some sanity checks */
4361 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4362 particularly useful. We currently do not actually do anything with
4363 the flag on subitems.
4365 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4366 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4368 /* get the subitem structure, and create it if not there */
4369 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4370 assert (hdpaSubItems);
4372 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4375 SUBITEM_INFO *tmpSubItem;
4378 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4379 if (!lpSubItem) return FALSE;
4380 /* we could binary search here, if need be...*/
4381 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4383 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4384 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4386 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4391 lpSubItem->iSubItem = lpLVItem->iSubItem;
4392 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4396 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4398 lpSubItem->hdr.iImage = lpLVItem->iImage;
4402 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4404 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4413 * Sets item attributes.
4416 * [I] infoPtr : valid pointer to the listview structure
4417 * [I] lpLVItem : new item attributes
4418 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4424 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4426 HWND hwndSelf = infoPtr->hwndSelf;
4427 LPWSTR pszText = NULL;
4428 BOOL bResult, bChanged = FALSE;
4431 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4433 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4436 /* Store old item area */
4437 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4439 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4440 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4442 pszText = lpLVItem->pszText;
4443 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4446 /* actually set the fields */
4447 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4449 if (lpLVItem->iSubItem)
4450 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4452 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4453 if (!IsWindow(hwndSelf))
4456 /* redraw item, if necessary */
4457 if (bChanged && !infoPtr->bIsDrawing)
4459 /* this little optimization eliminates some nasty flicker */
4460 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4461 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4462 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4463 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4466 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4467 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4473 textfreeT(lpLVItem->pszText, isW);
4474 lpLVItem->pszText = pszText;
4482 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4485 * [I] infoPtr : valid pointer to the listview structure
4490 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4493 SCROLLINFO scrollInfo;
4495 scrollInfo.cbSize = sizeof(SCROLLINFO);
4496 scrollInfo.fMask = SIF_POS;
4498 if (infoPtr->uView == LV_VIEW_LIST)
4500 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4501 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4503 else if (infoPtr->uView == LV_VIEW_DETAILS)
4505 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4506 nItem = scrollInfo.nPos;
4510 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4511 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4514 TRACE("nItem=%d\n", nItem);
4522 * Erases the background of the given rectangle
4525 * [I] infoPtr : valid pointer to the listview structure
4526 * [I] hdc : device context handle
4527 * [I] lprcBox : clipping rectangle
4533 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4535 if (!infoPtr->hBkBrush) return FALSE;
4537 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4539 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4547 * [I] infoPtr : valid pointer to the listview structure
4548 * [I] hdc : device context handle
4549 * [I] nItem : item index
4550 * [I] nSubItem : subitem index
4551 * [I] pos : item position in client coordinates
4552 * [I] cdmode : custom draw mode
4558 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
4561 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4562 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4563 DWORD cdsubitemmode = CDRF_DODEFAULT;
4565 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
4566 NMLVCUSTOMDRAW nmlvcd;
4571 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
4573 /* get information needed for drawing the item */
4574 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
4575 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
4576 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4577 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT;
4578 lvItem.iItem = nItem;
4579 lvItem.iSubItem = nSubItem;
4582 lvItem.cchTextMax = DISP_TEXT_SIZE;
4583 lvItem.pszText = szDispText;
4584 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4585 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4586 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4587 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
4588 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4590 /* now check if we need to update the focus rectangle */
4591 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4593 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
4594 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4595 OffsetRect(&rcBox, pos.x, pos.y);
4596 OffsetRect(&rcSelect, pos.x, pos.y);
4597 OffsetRect(&rcIcon, pos.x, pos.y);
4598 OffsetRect(&rcStateIcon, pos.x, pos.y);
4599 OffsetRect(&rcLabel, pos.x, pos.y);
4600 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
4601 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4602 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4604 /* fill in the custom draw structure */
4605 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4607 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
4608 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
4609 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
4610 if (cdmode & CDRF_NOTIFYITEMDRAW)
4611 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4612 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
4613 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4614 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
4615 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
4617 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4618 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4620 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4621 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4622 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
4623 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4625 /* in full row select, subitems, will just use main item's colors */
4626 if (nSubItem && infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4627 nmlvcd.clrTextBk = CLR_NONE;
4629 /* FIXME: temporary hack */
4630 rcSelect.left = rcLabel.left;
4632 /* draw the selection background, if we're drawing the main item */
4635 /* in icon mode, the label rect is really what we want to draw the
4637 if (infoPtr->uView == LV_VIEW_ICON)
4640 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4642 /* we have to update left focus bound too if item isn't in leftmost column
4643 and reduce right box bound */
4644 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4648 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4650 INT Originx = pos.x - LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
4651 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4652 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4654 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, index)->rcHeader.right + Originx;
4655 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4659 rcSelect.right = rcBox.right;
4662 if (nmlvcd.clrTextBk != CLR_NONE)
4663 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, NULL, 0, NULL);
4664 /* store new focus rectangle */
4665 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
4669 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
4671 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
4674 TRACE("uStateImage=%d\n", uStateImage);
4675 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
4676 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4681 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4682 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
4686 TRACE("iImage=%d\n", lvItem.iImage);
4688 if (lvItem.state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4689 style = ILD_SELECTED;
4693 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
4694 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4695 lvItem.state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4699 /* Don't bother painting item being edited */
4700 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
4702 /* figure out the text drawing flags */
4703 uFormat = (infoPtr->uView == LV_VIEW_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4704 if (infoPtr->uView == LV_VIEW_ICON)
4705 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4708 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4710 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
4711 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
4712 default: uFormat |= DT_LEFT;
4715 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
4717 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4718 else rcLabel.left += LABEL_HOR_PADDING;
4720 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4722 /* for GRIDLINES reduce the bottom so the text formats correctly */
4723 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4726 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4729 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4730 notify_postpaint(infoPtr, &nmlvcd);
4731 if (cdsubitemmode & CDRF_NEWFONT)
4732 SelectObject(hdc, hOldFont);
4738 * Draws listview items when in owner draw mode.
4741 * [I] infoPtr : valid pointer to the listview structure
4742 * [I] hdc : device context handle
4747 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4749 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4750 DWORD cditemmode = CDRF_DODEFAULT;
4751 NMLVCUSTOMDRAW nmlvcd;
4752 POINT Origin, Position;
4758 ZeroMemory(&dis, sizeof(dis));
4760 /* Get scroll info once before loop */
4761 LISTVIEW_GetOrigin(infoPtr, &Origin);
4763 /* iterate through the invalidated rows */
4764 while(iterator_next(i))
4766 item.iItem = i->nItem;
4768 item.mask = LVIF_PARAM | LVIF_STATE;
4769 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4770 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4772 dis.CtlType = ODT_LISTVIEW;
4774 dis.itemID = item.iItem;
4775 dis.itemAction = ODA_DRAWENTIRE;
4777 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4778 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4779 dis.hwndItem = infoPtr->hwndSelf;
4781 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4782 dis.rcItem.left = Position.x + Origin.x;
4783 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4784 dis.rcItem.top = Position.y + Origin.y;
4785 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4786 dis.itemData = item.lParam;
4788 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4791 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4792 * structure for the rest. of the paint cycle
4794 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4795 if (cdmode & CDRF_NOTIFYITEMDRAW)
4796 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4798 if (!(cditemmode & CDRF_SKIPDEFAULT))
4800 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4801 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4804 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4805 notify_postpaint(infoPtr, &nmlvcd);
4811 * Draws listview items when in report display mode.
4814 * [I] infoPtr : valid pointer to the listview structure
4815 * [I] hdc : device context handle
4816 * [I] cdmode : custom draw mode
4821 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4824 RECT rcClip, rcItem;
4825 POINT Origin, Position;
4832 /* figure out what to draw */
4833 rgntype = GetClipBox(hdc, &rcClip);
4834 if (rgntype == NULLREGION) return;
4836 /* Get scroll info once before loop */
4837 LISTVIEW_GetOrigin(infoPtr, &Origin);
4839 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4841 /* narrow down the columns we need to paint */
4842 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4844 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4846 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4847 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4848 ranges_additem(colRanges, index);
4850 iterator_rangesitems(&j, colRanges);
4852 /* in full row select, we _have_ to draw the main item */
4853 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4856 /* iterate through the invalidated rows */
4857 while(iterator_next(i))
4859 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4860 Position.y += Origin.y;
4862 /* iterate through the invalidated columns */
4863 while(iterator_next(&j))
4865 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4866 Position.x = (j.nItem == 0) ? rcItem.left + Origin.x : Origin.x;
4868 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4871 rcItem.bottom = infoPtr->nItemHeight;
4872 OffsetRect(&rcItem, Origin.x, Position.y);
4873 if (!RectVisible(hdc, &rcItem)) continue;
4876 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4879 iterator_destroy(&j);
4884 * Draws the gridlines if necessary when in report display mode.
4887 * [I] infoPtr : valid pointer to the listview structure
4888 * [I] hdc : device context handle
4893 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4899 RECT rcClip, rcItem = {0};
4907 /* figure out what to draw */
4908 rgntype = GetClipBox(hdc, &rcClip);
4909 if (rgntype == NULLREGION) return;
4911 /* Get scroll info once before loop */
4912 LISTVIEW_GetOrigin(infoPtr, &Origin);
4914 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4916 /* narrow down the columns we need to paint */
4917 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4919 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4921 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4922 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4923 ranges_additem(colRanges, index);
4926 /* is right most vertical line visible? */
4927 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4929 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4930 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4931 rmost = (rcItem.right + Origin.x < rcClip.right);
4934 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4936 hOldPen = SelectObject ( hdc, hPen );
4938 /* draw the vertical lines for the columns */
4939 iterator_rangesitems(&j, colRanges);
4940 while(iterator_next(&j))
4942 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4943 if (rcItem.left == 0) continue; /* skip leftmost column */
4944 rcItem.left += Origin.x;
4945 rcItem.right += Origin.x;
4946 rcItem.top = infoPtr->rcList.top;
4947 rcItem.bottom = infoPtr->rcList.bottom;
4948 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4949 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4950 LineTo (hdc, rcItem.left, rcItem.bottom);
4952 iterator_destroy(&j);
4953 /* draw rightmost grid line if visible */
4956 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4957 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4958 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4960 rcItem.right += Origin.x;
4962 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
4963 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
4966 /* draw the horizontal lines for the rows */
4967 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4968 rcItem.left = infoPtr->rcList.left;
4969 rcItem.right = infoPtr->rcList.right;
4970 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
4972 rcItem.bottom = rcItem.top = y;
4973 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4974 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4975 LineTo (hdc, rcItem.right, rcItem.top);
4978 SelectObject( hdc, hOldPen );
4979 DeleteObject( hPen );
4982 ranges_destroy(colRanges);
4987 * Draws listview items when in list display mode.
4990 * [I] infoPtr : valid pointer to the listview structure
4991 * [I] hdc : device context handle
4992 * [I] cdmode : custom draw mode
4997 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4999 POINT Origin, Position;
5001 /* Get scroll info once before loop */
5002 LISTVIEW_GetOrigin(infoPtr, &Origin);
5004 while(iterator_prev(i))
5006 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
5007 Position.x += Origin.x;
5008 Position.y += Origin.y;
5010 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
5017 * Draws listview items.
5020 * [I] infoPtr : valid pointer to the listview structure
5021 * [I] hdc : device context handle
5022 * [I] prcErase : rect to be erased before refresh (may be NULL)
5027 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5029 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
5030 NMLVCUSTOMDRAW nmlvcd;
5037 HBITMAP hbmp = NULL;
5040 LISTVIEW_DUMP(infoPtr);
5042 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5043 TRACE("double buffering\n");
5045 hdc = CreateCompatibleDC(hdcOrig);
5047 ERR("Failed to create DC for backbuffer\n");
5050 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5051 infoPtr->rcList.bottom);
5053 ERR("Failed to create bitmap for backbuffer\n");
5058 SelectObject(hdc, hbmp);
5059 SelectObject(hdc, infoPtr->hFont);
5061 if(GetClipBox(hdcOrig, &rcClient))
5062 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5064 /* Save dc values we're gonna trash while drawing
5065 * FIXME: Should be done in LISTVIEW_DrawItem() */
5066 hOldFont = SelectObject(hdc, infoPtr->hFont);
5067 oldBkMode = GetBkMode(hdc);
5068 oldBkColor = GetBkColor(hdc);
5069 oldTextColor = GetTextColor(hdc);
5072 infoPtr->bIsDrawing = TRUE;
5075 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5076 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5077 /* If no erasing was done (usually because RedrawWindow was called
5078 * with RDW_INVALIDATE only) we need to copy the old contents into
5079 * the backbuffer before continuing. */
5080 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5081 infoPtr->rcList.right - infoPtr->rcList.left,
5082 infoPtr->rcList.bottom - infoPtr->rcList.top,
5083 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5086 /* FIXME: Shouldn't need to do this */
5087 oldClrTextBk = infoPtr->clrTextBk;
5088 oldClrText = infoPtr->clrText;
5090 infoPtr->cditemmode = CDRF_DODEFAULT;
5092 GetClientRect(infoPtr->hwndSelf, &rcClient);
5093 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5094 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5095 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5096 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
5098 /* Use these colors to draw the items */
5099 infoPtr->clrTextBk = nmlvcd.clrTextBk;
5100 infoPtr->clrText = nmlvcd.clrText;
5102 /* nothing to draw */
5103 if(infoPtr->nItemCount == 0) goto enddraw;
5105 /* figure out what we need to draw */
5106 iterator_visibleitems(&i, infoPtr, hdc);
5107 range = iterator_range(&i);
5109 /* send cache hint notification */
5110 if (infoPtr->dwStyle & LVS_OWNERDATA)
5114 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5115 nmlv.iFrom = range.lower;
5116 nmlv.iTo = range.upper - 1;
5117 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5120 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5121 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5124 if (infoPtr->uView == LV_VIEW_DETAILS)
5125 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5126 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5127 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5129 /* if we have a focus rect and it's visible, draw it */
5130 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5131 (range.upper - 1) >= infoPtr->nFocusedItem)
5132 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5134 iterator_destroy(&i);
5137 /* For LVS_EX_GRIDLINES go and draw lines */
5138 /* This includes the case where there were *no* items */
5139 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5140 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5142 /* Draw marquee rectangle if appropriate */
5143 if (infoPtr->bMarqueeSelect)
5144 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5146 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5147 notify_postpaint(infoPtr, &nmlvcd);
5149 infoPtr->clrTextBk = oldClrTextBk;
5150 infoPtr->clrText = oldClrText;
5153 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5154 infoPtr->rcList.right - infoPtr->rcList.left,
5155 infoPtr->rcList.bottom - infoPtr->rcList.top,
5156 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5161 SelectObject(hdc, hOldFont);
5162 SetBkMode(hdc, oldBkMode);
5163 SetBkColor(hdc, oldBkColor);
5164 SetTextColor(hdc, oldTextColor);
5167 infoPtr->bIsDrawing = FALSE;
5173 * Calculates the approximate width and height of a given number of items.
5176 * [I] infoPtr : valid pointer to the listview structure
5177 * [I] nItemCount : number of items
5178 * [I] wWidth : width
5179 * [I] wHeight : height
5182 * Returns a DWORD. The width in the low word and the height in high word.
5184 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5185 WORD wWidth, WORD wHeight)
5187 DWORD dwViewRect = 0;
5189 if (nItemCount == -1)
5190 nItemCount = infoPtr->nItemCount;
5192 if (infoPtr->uView == LV_VIEW_LIST)
5194 INT nItemCountPerColumn = 1;
5195 INT nColumnCount = 0;
5197 if (wHeight == 0xFFFF)
5199 /* use current height */
5200 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5203 if (wHeight < infoPtr->nItemHeight)
5204 wHeight = infoPtr->nItemHeight;
5208 if (infoPtr->nItemHeight > 0)
5210 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5211 if (nItemCountPerColumn == 0)
5212 nItemCountPerColumn = 1;
5214 if (nItemCount % nItemCountPerColumn != 0)
5215 nColumnCount = nItemCount / nItemCountPerColumn;
5217 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5221 /* Microsoft padding magic */
5222 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5223 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5225 dwViewRect = MAKELONG(wWidth, wHeight);
5227 else if (infoPtr->uView == LV_VIEW_DETAILS)
5231 if (infoPtr->nItemCount > 0)
5233 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5234 wWidth = rcBox.right - rcBox.left;
5235 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5239 /* use current height and width */
5240 if (wHeight == 0xffff)
5241 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5242 if (wWidth == 0xffff)
5243 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5246 dwViewRect = MAKELONG(wWidth, wHeight);
5248 else if (infoPtr->uView == LV_VIEW_ICON)
5254 nItemWidth = infoPtr->iconSpacing.cx;
5255 nItemHeight = infoPtr->iconSpacing.cy;
5257 if (wWidth == 0xffff)
5258 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5260 if (wWidth < nItemWidth)
5261 wWidth = nItemWidth;
5263 cols = wWidth / nItemWidth;
5264 if (cols > nItemCount)
5271 rows = nItemCount / cols;
5272 if (nItemCount % cols)
5278 wHeight = (nItemHeight * rows)+2;
5279 wWidth = (nItemWidth * cols)+2;
5281 dwViewRect = MAKELONG(wWidth, wHeight);
5283 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5284 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5291 * Cancel edit label with saving item text.
5294 * [I] infoPtr : valid pointer to the listview structure
5297 * Always returns TRUE.
5299 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5301 if (infoPtr->hwndEdit)
5303 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5304 HWND edit = infoPtr->hwndEdit;
5306 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5307 SendMessageW(edit, WM_CLOSE, 0, 0);
5315 * Create a drag image list for the specified item.
5318 * [I] infoPtr : valid pointer to the listview structure
5319 * [I] iItem : index of item
5320 * [O] lppt : Upper-left corner of the image
5323 * Returns a handle to the image list if successful, NULL otherwise.
5325 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5331 HBITMAP hbmp, hOldbmp;
5332 HIMAGELIST dragList = 0;
5333 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5335 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5338 rcItem.left = LVIR_BOUNDS;
5339 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5342 lppt->x = rcItem.left;
5343 lppt->y = rcItem.top;
5345 size.cx = rcItem.right - rcItem.left;
5346 size.cy = rcItem.bottom - rcItem.top;
5348 hdcOrig = GetDC(infoPtr->hwndSelf);
5349 hdc = CreateCompatibleDC(hdcOrig);
5350 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5351 hOldbmp = SelectObject(hdc, hbmp);
5353 rcItem.left = rcItem.top = 0;
5354 rcItem.right = size.cx;
5355 rcItem.bottom = size.cy;
5356 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5359 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
5361 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5362 SelectObject(hdc, hOldbmp);
5363 ImageList_Add(dragList, hbmp, 0);
5366 SelectObject(hdc, hOldbmp);
5370 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5372 TRACE("ret=%p\n", dragList);
5380 * Removes all listview items and subitems.
5383 * [I] infoPtr : valid pointer to the listview structure
5389 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5392 HDPA hdpaSubItems = NULL;
5401 /* we do it directly, to avoid notifications */
5402 ranges_clear(infoPtr->selectionRanges);
5403 infoPtr->nSelectionMark = -1;
5404 infoPtr->nFocusedItem = -1;
5405 SetRectEmpty(&infoPtr->rcFocus);
5406 /* But we are supposed to leave nHotItem as is! */
5409 /* send LVN_DELETEALLITEMS notification */
5410 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
5412 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5414 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5416 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5418 /* send LVN_DELETEITEM notification, if not suppressed
5419 and if it is not a virtual listview */
5420 if (!bSuppress) notify_deleteitem(infoPtr, i);
5421 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5422 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5423 /* free id struct */
5424 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5425 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5426 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5428 /* both item and subitem start with ITEMHDR header */
5429 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5431 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5432 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5435 DPA_Destroy(hdpaSubItems);
5436 DPA_DeletePtr(infoPtr->hdpaItems, i);
5438 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5439 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5440 infoPtr->nItemCount --;
5445 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5446 LISTVIEW_UpdateScroll(infoPtr);
5448 LISTVIEW_InvalidateList(infoPtr);
5455 * Scrolls, and updates the columns, when a column is changing width.
5458 * [I] infoPtr : valid pointer to the listview structure
5459 * [I] nColumn : column to scroll
5460 * [I] dx : amount of scroll, in pixels
5465 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5467 COLUMN_INFO *lpColumnInfo;
5473 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5474 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5475 rcCol = lpColumnInfo->rcHeader;
5476 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5477 rcCol.left = rcCol.right;
5479 /* adjust the other columns */
5480 hdi.mask = HDI_ORDER;
5481 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5483 INT nOrder = hdi.iOrder;
5484 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5486 hdi.mask = HDI_ORDER;
5487 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5488 if (hdi.iOrder >= nOrder) {
5489 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5490 lpColumnInfo->rcHeader.left += dx;
5491 lpColumnInfo->rcHeader.right += dx;
5496 /* do not update screen if not in report mode */
5497 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5499 /* Need to reset the item width when inserting a new column */
5500 infoPtr->nItemWidth += dx;
5502 LISTVIEW_UpdateScroll(infoPtr);
5503 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5505 /* scroll to cover the deleted column, and invalidate for redraw */
5506 rcOld = infoPtr->rcList;
5507 rcOld.left = ptOrigin.x + rcCol.left + dx;
5508 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5513 * Removes a column from the listview control.
5516 * [I] infoPtr : valid pointer to the listview structure
5517 * [I] nColumn : column index
5523 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5527 TRACE("nColumn=%d\n", nColumn);
5529 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
5530 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5532 /* While the MSDN specifically says that column zero should not be deleted,
5533 what actually happens is that the column itself is deleted but no items or subitems
5537 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5539 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5542 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5543 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5545 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5547 SUBITEM_INFO *lpSubItem, *lpDelItem;
5549 INT nItem, nSubItem, i;
5551 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5553 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5556 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5558 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5559 if (lpSubItem->iSubItem == nColumn)
5562 lpDelItem = lpSubItem;
5564 else if (lpSubItem->iSubItem > nColumn)
5566 lpSubItem->iSubItem--;
5570 /* if we found our subitem, zap it */
5574 if (is_text(lpDelItem->hdr.pszText))
5575 Free(lpDelItem->hdr.pszText);
5580 /* free dpa memory */
5581 DPA_DeletePtr(hdpaSubItems, nSubItem);
5586 /* update the other column info */
5587 LISTVIEW_UpdateItemSize(infoPtr);
5588 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5589 LISTVIEW_InvalidateList(infoPtr);
5591 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5598 * Invalidates the listview after an item's insertion or deletion.
5601 * [I] infoPtr : valid pointer to the listview structure
5602 * [I] nItem : item index
5603 * [I] dir : -1 if deleting, 1 if inserting
5608 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5610 INT nPerCol, nItemCol, nItemRow;
5614 /* if we don't refresh, what's the point of scrolling? */
5615 if (!is_redrawing(infoPtr)) return;
5617 assert (abs(dir) == 1);
5619 /* arrange icons if autoarrange is on */
5620 if (is_autoarrange(infoPtr))
5622 BOOL arrange = TRUE;
5623 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5624 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5625 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5628 /* scrollbars need updating */
5629 LISTVIEW_UpdateScroll(infoPtr);
5631 /* figure out the item's position */
5632 if (infoPtr->uView == LV_VIEW_DETAILS)
5633 nPerCol = infoPtr->nItemCount + 1;
5634 else if (infoPtr->uView == LV_VIEW_LIST)
5635 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5636 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5639 nItemCol = nItem / nPerCol;
5640 nItemRow = nItem % nPerCol;
5641 LISTVIEW_GetOrigin(infoPtr, &Origin);
5643 /* move the items below up a slot */
5644 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5645 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5646 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5647 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5648 OffsetRect(&rcScroll, Origin.x, Origin.y);
5649 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5650 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5652 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5653 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5656 /* report has only that column, so we're done */
5657 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5659 /* now for LISTs, we have to deal with the columns to the right */
5660 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
5662 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
5663 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5664 OffsetRect(&rcScroll, Origin.x, Origin.y);
5665 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5666 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5671 * Removes an item from the listview control.
5674 * [I] infoPtr : valid pointer to the listview structure
5675 * [I] nItem : item index
5681 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5684 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5685 INT focus = infoPtr->nFocusedItem;
5687 TRACE("(nItem=%d)\n", nItem);
5689 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5691 /* remove selection, and focus */
5693 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5694 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5696 /* send LVN_DELETEITEM notification. */
5697 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5699 /* we need to do this here, because we'll be deleting stuff */
5701 LISTVIEW_InvalidateItem(infoPtr, nItem);
5703 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5711 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5712 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5714 /* free id struct */
5715 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5716 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5717 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5719 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5721 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5722 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5725 DPA_Destroy(hdpaSubItems);
5730 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5731 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5734 infoPtr->nItemCount--;
5735 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5736 LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
5738 /* now is the invalidation fun */
5740 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5747 * Callback implementation for editlabel control
5750 * [I] infoPtr : valid pointer to the listview structure
5751 * [I] storeText : store edit box text as item text
5752 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5758 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5760 HWND hwndSelf = infoPtr->hwndSelf;
5761 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5762 NMLVDISPINFOW dispInfo;
5763 INT editedItem = infoPtr->nEditLabelItem;
5765 WCHAR *pszText = NULL;
5770 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5774 if ((pszText = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5776 if (isW) GetWindowTextW(infoPtr->hwndEdit, pszText, len+1);
5777 else GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len+1);
5782 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5784 ZeroMemory(&dispInfo, sizeof(dispInfo));
5785 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5786 dispInfo.item.iItem = editedItem;
5787 dispInfo.item.iSubItem = 0;
5788 dispInfo.item.stateMask = ~0;
5789 dispInfo.item.pszText = szDispText;
5790 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5791 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5798 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5801 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5802 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5803 textfreeT(tmp, FALSE);
5806 /* add the text from the edit in */
5807 dispInfo.item.mask |= LVIF_TEXT;
5808 dispInfo.item.pszText = same ? NULL : pszText;
5809 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5811 /* Do we need to update the Item Text */
5812 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5814 infoPtr->nEditLabelItem = -1;
5815 infoPtr->hwndEdit = 0;
5817 if (!res) goto cleanup;
5819 if (!IsWindow(hwndSelf))
5824 if (!pszText) return TRUE;
5831 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5833 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5834 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5835 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5837 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5843 ZeroMemory(&dispInfo, sizeof(dispInfo));
5844 dispInfo.item.mask = LVIF_TEXT;
5845 dispInfo.item.iItem = editedItem;
5846 dispInfo.item.iSubItem = 0;
5847 dispInfo.item.pszText = pszText;
5848 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5849 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5859 * Subclassed edit control windproc function
5862 * [I] hwnd : the edit window handle
5863 * [I] uMsg : the message that is to be processed
5864 * [I] wParam : first message parameter
5865 * [I] lParam : second message parameter
5866 * [I] isW : TRUE if input is Unicode
5871 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
5873 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
5876 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
5877 hwnd, uMsg, wParam, lParam, isW);
5882 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
5886 WNDPROC editProc = infoPtr->EditWndProc;
5887 infoPtr->EditWndProc = 0;
5888 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
5889 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
5893 if (VK_ESCAPE == (INT)wParam)
5898 else if (VK_RETURN == (INT)wParam)
5902 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
5906 if (infoPtr->hwndEdit)
5907 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
5909 SendMessageW(hwnd, WM_CLOSE, 0, 0);
5915 * Subclassed edit control Unicode windproc function
5918 * [I] hwnd : the edit window handle
5919 * [I] uMsg : the message that is to be processed
5920 * [I] wParam : first message parameter
5921 * [I] lParam : second message parameter
5925 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5927 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
5932 * Subclassed edit control ANSI windproc function
5935 * [I] hwnd : the edit window handle
5936 * [I] uMsg : the message that is to be processed
5937 * [I] wParam : first message parameter
5938 * [I] lParam : second message parameter
5942 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
5944 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
5949 * Creates a subclassed edit control
5952 * [I] infoPtr : valid pointer to the listview structure
5953 * [I] text : initial text for the edit
5954 * [I] style : the window style
5955 * [I] isW : TRUE if input is Unicode
5959 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
5961 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
5962 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
5965 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
5967 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
5969 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5971 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
5973 if (!hedit) return 0;
5975 infoPtr->EditWndProc = (WNDPROC)
5976 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
5977 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
5979 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
5980 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
5987 * Begin in place editing of specified list view item
5990 * [I] infoPtr : valid pointer to the listview structure
5991 * [I] nItem : item index
5992 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5998 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
6000 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
6001 HWND hwndSelf = infoPtr->hwndSelf;
6002 NMLVDISPINFOW dispInfo;
6003 HFONT hOldFont = NULL;
6009 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
6011 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
6013 /* remove existing edit box */
6014 if (infoPtr->hwndEdit)
6016 SetFocus(infoPtr->hwndSelf);
6017 infoPtr->hwndEdit = 0;
6020 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6022 infoPtr->nEditLabelItem = nItem;
6024 LISTVIEW_SetSelection(infoPtr, nItem);
6025 LISTVIEW_SetItemFocus(infoPtr, nItem);
6026 LISTVIEW_InvalidateItem(infoPtr, nItem);
6028 rect.left = LVIR_LABEL;
6029 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6031 ZeroMemory(&dispInfo, sizeof(dispInfo));
6032 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6033 dispInfo.item.iItem = nItem;
6034 dispInfo.item.iSubItem = 0;
6035 dispInfo.item.stateMask = ~0;
6036 dispInfo.item.pszText = disptextW;
6037 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6038 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6040 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6041 if (!infoPtr->hwndEdit) return 0;
6043 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6045 if (!IsWindow(hwndSelf))
6047 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6048 infoPtr->hwndEdit = 0;
6052 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6054 /* position and display edit box */
6055 hdc = GetDC(infoPtr->hwndSelf);
6057 /* select the font to get appropriate metric dimensions */
6059 hOldFont = SelectObject(hdc, infoPtr->hFont);
6061 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6062 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6063 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6065 /* get string length in pixels */
6066 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6068 /* add extra spacing for the next character */
6069 GetTextMetricsW(hdc, &tm);
6070 sz.cx += tm.tmMaxCharWidth * 2;
6073 SelectObject(hdc, hOldFont);
6075 ReleaseDC(infoPtr->hwndSelf, hdc);
6077 sz.cy = rect.bottom - rect.top + 2;
6080 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6081 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6082 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6083 SetFocus(infoPtr->hwndEdit);
6084 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6085 return infoPtr->hwndEdit;
6091 * Ensures the specified item is visible, scrolling into view if necessary.
6094 * [I] infoPtr : valid pointer to the listview structure
6095 * [I] nItem : item index
6096 * [I] bPartial : partially or entirely visible
6102 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6104 INT nScrollPosHeight = 0;
6105 INT nScrollPosWidth = 0;
6106 INT nHorzAdjust = 0;
6107 INT nVertAdjust = 0;
6110 RECT rcItem, rcTemp;
6112 rcItem.left = LVIR_BOUNDS;
6113 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6115 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6117 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6119 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6120 if (infoPtr->uView == LV_VIEW_LIST)
6121 nScrollPosWidth = infoPtr->nItemWidth;
6122 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6123 nScrollPosWidth = 1;
6125 if (rcItem.left < infoPtr->rcList.left)
6128 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6133 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6137 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6139 /* scroll up/down, but not in LVS_LIST mode */
6140 if (infoPtr->uView == LV_VIEW_DETAILS)
6141 nScrollPosHeight = infoPtr->nItemHeight;
6142 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6143 nScrollPosHeight = 1;
6145 if (rcItem.top < infoPtr->rcList.top)
6148 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6153 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6157 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6159 if (nScrollPosWidth)
6161 INT diff = nHorzDiff / nScrollPosWidth;
6162 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6163 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6166 if (nScrollPosHeight)
6168 INT diff = nVertDiff / nScrollPosHeight;
6169 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6170 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6178 * Searches for an item with specific characteristics.
6181 * [I] hwnd : window handle
6182 * [I] nStart : base item index
6183 * [I] lpFindInfo : item information to look for
6186 * SUCCESS : index of item
6189 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6190 const LVFINDINFOW *lpFindInfo)
6192 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6193 BOOL bWrap = FALSE, bNearest = FALSE;
6194 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6195 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6196 POINT Position, Destination;
6199 /* Search in virtual listviews should be done by application, not by
6200 listview control, so we just send LVN_ODFINDITEMW and return the result */
6201 if (infoPtr->dwStyle & LVS_OWNERDATA)
6205 nmlv.iStart = nStart;
6206 nmlv.lvfi = *lpFindInfo;
6207 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6210 if (!lpFindInfo || nItem < 0) return -1;
6213 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6214 lpFindInfo->flags & LVFI_SUBSTRING)
6216 lvItem.mask |= LVIF_TEXT;
6217 lvItem.pszText = szDispText;
6218 lvItem.cchTextMax = DISP_TEXT_SIZE;
6221 if (lpFindInfo->flags & LVFI_WRAP)
6224 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6225 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6230 LISTVIEW_GetOrigin(infoPtr, &Origin);
6231 Destination.x = lpFindInfo->pt.x - Origin.x;
6232 Destination.y = lpFindInfo->pt.y - Origin.y;
6233 switch(lpFindInfo->vkDirection)
6235 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6236 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6237 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6238 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6239 case VK_HOME: Destination.x = Destination.y = 0; break;
6240 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6241 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6243 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6244 Destination.x = rcArea.right;
6245 Destination.y = rcArea.bottom;
6247 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6251 else Destination.x = Destination.y = 0;
6253 /* if LVFI_PARAM is specified, all other flags are ignored */
6254 if (lpFindInfo->flags & LVFI_PARAM)
6256 lvItem.mask |= LVIF_PARAM;
6258 lvItem.mask &= ~LVIF_TEXT;
6262 for (; nItem < nLast; nItem++)
6264 lvItem.iItem = nItem;
6265 lvItem.iSubItem = 0;
6266 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6268 if (lvItem.mask & LVIF_PARAM)
6270 if (lpFindInfo->lParam == lvItem.lParam)
6276 if (lvItem.mask & LVIF_TEXT)
6278 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6280 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6281 if (!p || p != lvItem.pszText) continue;
6285 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6289 if (!bNearest) return nItem;
6291 /* This is very inefficient. To do a good job here,
6292 * we need a sorted array of (x,y) item positions */
6293 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6295 /* compute the distance^2 to the destination */
6296 xdist = Destination.x - Position.x;
6297 ydist = Destination.y - Position.y;
6298 dist = xdist * xdist + ydist * ydist;
6300 /* remember the distance, and item if it's closer */
6304 nNearestItem = nItem;
6311 nLast = min(nStart + 1, infoPtr->nItemCount);
6316 return nNearestItem;
6321 * Searches for an item with specific characteristics.
6324 * [I] hwnd : window handle
6325 * [I] nStart : base item index
6326 * [I] lpFindInfo : item information to look for
6329 * SUCCESS : index of item
6332 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6333 const LVFINDINFOA *lpFindInfo)
6335 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6336 lpFindInfo->flags & LVFI_SUBSTRING;
6341 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6342 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6343 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6344 textfreeT(strW, FALSE);
6350 * Retrieves column attributes.
6353 * [I] infoPtr : valid pointer to the listview structure
6354 * [I] nColumn : column index
6355 * [IO] lpColumn : column information
6356 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6357 * otherwise it is in fact a LPLVCOLUMNA
6363 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6365 COLUMN_INFO *lpColumnInfo;
6368 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6369 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6371 /* initialize memory */
6372 ZeroMemory(&hdi, sizeof(hdi));
6374 if (lpColumn->mask & LVCF_TEXT)
6376 hdi.mask |= HDI_TEXT;
6377 hdi.pszText = lpColumn->pszText;
6378 hdi.cchTextMax = lpColumn->cchTextMax;
6381 if (lpColumn->mask & LVCF_IMAGE)
6382 hdi.mask |= HDI_IMAGE;
6384 if (lpColumn->mask & LVCF_ORDER)
6385 hdi.mask |= HDI_ORDER;
6387 if (lpColumn->mask & LVCF_SUBITEM)
6388 hdi.mask |= HDI_LPARAM;
6390 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6392 if (lpColumn->mask & LVCF_FMT)
6393 lpColumn->fmt = lpColumnInfo->fmt;
6395 if (lpColumn->mask & LVCF_WIDTH)
6396 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6398 if (lpColumn->mask & LVCF_IMAGE)
6399 lpColumn->iImage = hdi.iImage;
6401 if (lpColumn->mask & LVCF_ORDER)
6402 lpColumn->iOrder = hdi.iOrder;
6404 if (lpColumn->mask & LVCF_SUBITEM)
6405 lpColumn->iSubItem = hdi.lParam;
6407 if (lpColumn->mask & LVCF_MINWIDTH)
6408 lpColumn->cxMin = lpColumnInfo->cxMin;
6414 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6416 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
6421 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6426 * Retrieves the column width.
6429 * [I] infoPtr : valid pointer to the listview structure
6430 * [I] int : column index
6433 * SUCCESS : column width
6436 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6438 INT nColumnWidth = 0;
6441 TRACE("nColumn=%d\n", nColumn);
6443 /* we have a 'column' in LIST and REPORT mode only */
6444 switch(infoPtr->uView)
6447 nColumnWidth = infoPtr->nItemWidth;
6449 case LV_VIEW_DETAILS:
6450 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6451 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6452 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6454 * TODO: should we do the same in LVM_GETCOLUMN?
6456 hdItem.mask = HDI_WIDTH;
6457 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6459 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6462 nColumnWidth = hdItem.cxy;
6466 TRACE("nColumnWidth=%d\n", nColumnWidth);
6467 return nColumnWidth;
6472 * In list or report display mode, retrieves the number of items that can fit
6473 * vertically in the visible area. In icon or small icon display mode,
6474 * retrieves the total number of visible items.
6477 * [I] infoPtr : valid pointer to the listview structure
6480 * Number of fully visible items.
6482 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6484 switch (infoPtr->uView)
6487 case LV_VIEW_SMALLICON:
6488 return infoPtr->nItemCount;
6489 case LV_VIEW_DETAILS:
6490 return LISTVIEW_GetCountPerColumn(infoPtr);
6492 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6500 * Retrieves an image list handle.
6503 * [I] infoPtr : valid pointer to the listview structure
6504 * [I] nImageList : image list identifier
6507 * SUCCESS : image list handle
6510 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6514 case LVSIL_NORMAL: return infoPtr->himlNormal;
6515 case LVSIL_SMALL: return infoPtr->himlSmall;
6516 case LVSIL_STATE: return infoPtr->himlState;
6517 case LVSIL_GROUPHEADER:
6518 FIXME("LVSIL_GROUPHEADER not supported\n");
6521 WARN("got unknown imagelist index - %d\n", nImageList);
6526 /* LISTVIEW_GetISearchString */
6530 * Retrieves item attributes.
6533 * [I] hwnd : window handle
6534 * [IO] lpLVItem : item info
6535 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6536 * if FALSE, then lpLVItem is a LPLVITEMA.
6539 * This is the internal 'GetItem' interface -- it tries to
6540 * be smart and avoid text copies, if possible, by modifying
6541 * lpLVItem->pszText to point to the text string. Please note
6542 * that this is not always possible (e.g. OWNERDATA), so on
6543 * entry you *must* supply valid values for pszText, and cchTextMax.
6544 * The only difference to the documented interface is that upon
6545 * return, you should use *only* the lpLVItem->pszText, rather than
6546 * the buffer pointer you provided on input. Most code already does
6547 * that, so it's not a problem.
6548 * For the two cases when the text must be copied (that is,
6549 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6555 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6557 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6558 NMLVDISPINFOW dispInfo;
6564 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6566 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6569 if (lpLVItem->mask == 0) return TRUE;
6570 TRACE("mask=%x\n", lpLVItem->mask);
6572 /* make a local copy */
6573 isubitem = lpLVItem->iSubItem;
6575 /* a quick optimization if all we're asked is the focus state
6576 * these queries are worth optimising since they are common,
6577 * and can be answered in constant time, without the heavy accesses */
6578 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6579 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6581 lpLVItem->state = 0;
6582 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6583 lpLVItem->state |= LVIS_FOCUSED;
6587 ZeroMemory(&dispInfo, sizeof(dispInfo));
6589 /* if the app stores all the data, handle it separately */
6590 if (infoPtr->dwStyle & LVS_OWNERDATA)
6592 dispInfo.item.state = 0;
6594 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6595 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6596 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6598 UINT mask = lpLVItem->mask;
6600 /* NOTE: copy only fields which we _know_ are initialized, some apps
6601 * depend on the uninitialized fields being 0 */
6602 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6603 dispInfo.item.iItem = lpLVItem->iItem;
6604 dispInfo.item.iSubItem = isubitem;
6605 if (lpLVItem->mask & LVIF_TEXT)
6607 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6609 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6612 dispInfo.item.pszText = lpLVItem->pszText;
6613 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6616 if (lpLVItem->mask & LVIF_STATE)
6617 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6618 /* could be zeroed on LVIF_NORECOMPUTE case */
6619 if (dispInfo.item.mask)
6621 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6622 dispInfo.item.stateMask = lpLVItem->stateMask;
6623 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6625 /* full size structure expected - _WIN32IE >= 0x560 */
6626 *lpLVItem = dispInfo.item;
6628 else if (lpLVItem->mask & LVIF_INDENT)
6630 /* indent member expected - _WIN32IE >= 0x300 */
6631 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6635 /* minimal structure expected */
6636 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6638 lpLVItem->mask = mask;
6639 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6643 /* make sure lParam is zeroed out */
6644 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6646 /* callback marked pointer required here */
6647 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6648 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6650 /* we store only a little state, so if we're not asked, we're done */
6651 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6653 /* if focus is handled by us, report it */
6654 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6656 lpLVItem->state &= ~LVIS_FOCUSED;
6657 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6658 lpLVItem->state |= LVIS_FOCUSED;
6661 /* and do the same for selection, if we handle it */
6662 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6664 lpLVItem->state &= ~LVIS_SELECTED;
6665 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6666 lpLVItem->state |= LVIS_SELECTED;
6672 /* find the item and subitem structures before we proceed */
6673 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6674 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6679 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6680 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6683 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6688 pItemHdr = &lpItem->hdr;
6690 /* Do we need to query the state from the app? */
6691 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6693 dispInfo.item.mask |= LVIF_STATE;
6694 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6697 /* Do we need to enquire about the image? */
6698 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6699 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6701 dispInfo.item.mask |= LVIF_IMAGE;
6702 dispInfo.item.iImage = I_IMAGECALLBACK;
6705 /* Only items support indentation */
6706 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6709 dispInfo.item.mask |= LVIF_INDENT;
6710 dispInfo.item.iIndent = I_INDENTCALLBACK;
6713 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6714 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6715 !is_text(pItemHdr->pszText))
6717 dispInfo.item.mask |= LVIF_TEXT;
6718 dispInfo.item.pszText = lpLVItem->pszText;
6719 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6720 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6721 *dispInfo.item.pszText = '\0';
6724 /* If we don't have all the requested info, query the application */
6725 if (dispInfo.item.mask)
6727 dispInfo.item.iItem = lpLVItem->iItem;
6728 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6729 dispInfo.item.lParam = lpItem->lParam;
6730 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6731 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6734 /* we should not store values for subitems */
6735 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6737 /* Now, handle the iImage field */
6738 if (dispInfo.item.mask & LVIF_IMAGE)
6740 lpLVItem->iImage = dispInfo.item.iImage;
6741 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6742 pItemHdr->iImage = dispInfo.item.iImage;
6744 else if (lpLVItem->mask & LVIF_IMAGE)
6746 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6747 lpLVItem->iImage = pItemHdr->iImage;
6749 lpLVItem->iImage = 0;
6752 /* The pszText field */
6753 if (dispInfo.item.mask & LVIF_TEXT)
6755 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6756 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6758 lpLVItem->pszText = dispInfo.item.pszText;
6760 else if (lpLVItem->mask & LVIF_TEXT)
6762 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6763 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6764 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6767 /* Next is the lParam field */
6768 if (dispInfo.item.mask & LVIF_PARAM)
6770 lpLVItem->lParam = dispInfo.item.lParam;
6771 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6772 lpItem->lParam = dispInfo.item.lParam;
6774 else if (lpLVItem->mask & LVIF_PARAM)
6775 lpLVItem->lParam = lpItem->lParam;
6777 /* if this is a subitem, we're done */
6778 if (isubitem) return TRUE;
6780 /* ... the state field (this one is different due to uCallbackmask) */
6781 if (lpLVItem->mask & LVIF_STATE)
6783 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6784 if (dispInfo.item.mask & LVIF_STATE)
6786 lpLVItem->state &= ~dispInfo.item.stateMask;
6787 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6789 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6791 lpLVItem->state &= ~LVIS_FOCUSED;
6792 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6793 lpLVItem->state |= LVIS_FOCUSED;
6795 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6797 lpLVItem->state &= ~LVIS_SELECTED;
6798 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6799 lpLVItem->state |= LVIS_SELECTED;
6803 /* and last, but not least, the indent field */
6804 if (dispInfo.item.mask & LVIF_INDENT)
6806 lpLVItem->iIndent = dispInfo.item.iIndent;
6807 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6808 lpItem->iIndent = dispInfo.item.iIndent;
6810 else if (lpLVItem->mask & LVIF_INDENT)
6812 lpLVItem->iIndent = lpItem->iIndent;
6820 * Retrieves item attributes.
6823 * [I] hwnd : window handle
6824 * [IO] lpLVItem : item info
6825 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6826 * if FALSE, then lpLVItem is a LPLVITEMA.
6829 * This is the external 'GetItem' interface -- it properly copies
6830 * the text in the provided buffer.
6836 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6841 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6844 pszText = lpLVItem->pszText;
6845 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6846 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6848 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6849 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6851 pszText = LPSTR_TEXTCALLBACKW;
6853 lpLVItem->pszText = pszText;
6861 * Retrieves the position (upper-left) of the listview control item.
6862 * Note that for LVS_ICON style, the upper-left is that of the icon
6863 * and not the bounding box.
6866 * [I] infoPtr : valid pointer to the listview structure
6867 * [I] nItem : item index
6868 * [O] lpptPosition : coordinate information
6874 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6878 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6880 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6882 LISTVIEW_GetOrigin(infoPtr, &Origin);
6883 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6885 if (infoPtr->uView == LV_VIEW_ICON)
6887 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6888 lpptPosition->y += ICON_TOP_PADDING;
6890 lpptPosition->x += Origin.x;
6891 lpptPosition->y += Origin.y;
6893 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6900 * Retrieves the bounding rectangle for a listview control item.
6903 * [I] infoPtr : valid pointer to the listview structure
6904 * [I] nItem : item index
6905 * [IO] lprc : bounding rectangle coordinates
6906 * lprc->left specifies the portion of the item for which the bounding
6907 * rectangle will be retrieved.
6909 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
6910 * including the icon and label.
6913 * * Experiment shows that native control returns:
6914 * * width = min (48, length of text line)
6915 * * .left = position.x - (width - iconsize.cx)/2
6916 * * .right = .left + width
6917 * * height = #lines of text * ntmHeight + icon height + 8
6918 * * .top = position.y - 2
6919 * * .bottom = .top + height
6920 * * separation between items .y = itemSpacing.cy - height
6921 * * .x = itemSpacing.cx - width
6922 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
6925 * * Experiment shows that native control returns:
6926 * * width = iconSize.cx + 16
6927 * * .left = position.x - (width - iconsize.cx)/2
6928 * * .right = .left + width
6929 * * height = iconSize.cy + 4
6930 * * .top = position.y - 2
6931 * * .bottom = .top + height
6932 * * separation between items .y = itemSpacing.cy - height
6933 * * .x = itemSpacing.cx - width
6934 * LVIR_LABEL Returns the bounding rectangle of the item text.
6937 * * Experiment shows that native control returns:
6938 * * width = text length
6939 * * .left = position.x - width/2
6940 * * .right = .left + width
6941 * * height = ntmH * linecount + 2
6942 * * .top = position.y + iconSize.cy + 6
6943 * * .bottom = .top + height
6944 * * separation between items .y = itemSpacing.cy - height
6945 * * .x = itemSpacing.cx - width
6946 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
6947 * rectangles, but excludes columns in report view.
6954 * Note that the bounding rectangle of the label in the LVS_ICON view depends
6955 * upon whether the window has the focus currently and on whether the item
6956 * is the one with the focus. Ensure that the control's record of which
6957 * item has the focus agrees with the items' records.
6959 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6961 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6962 BOOL doLabel = TRUE, oversizedBox = FALSE;
6963 POINT Position, Origin;
6967 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
6969 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6971 LISTVIEW_GetOrigin(infoPtr, &Origin);
6972 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6974 /* Be smart and try to figure out the minimum we have to do */
6975 if (lprc->left == LVIR_ICON) doLabel = FALSE;
6976 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
6977 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
6978 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
6979 oversizedBox = TRUE;
6981 /* get what we need from the item before hand, so we make
6982 * only one request. This can speed up things, if data
6983 * is stored on the app side */
6985 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
6986 if (doLabel) lvItem.mask |= LVIF_TEXT;
6987 lvItem.iItem = nItem;
6988 lvItem.iSubItem = 0;
6989 lvItem.pszText = szDispText;
6990 lvItem.cchTextMax = DISP_TEXT_SIZE;
6991 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6992 /* we got the state already up, simulate it here, to avoid a reget */
6993 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
6995 lvItem.mask |= LVIF_STATE;
6996 lvItem.stateMask = LVIS_FOCUSED;
6997 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
7000 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
7001 lprc->left = LVIR_BOUNDS;
7007 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7011 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
7015 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7018 case LVIR_SELECTBOUNDS:
7019 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
7023 WARN("Unknown value: %d\n", lprc->left);
7027 if (infoPtr->uView == LV_VIEW_DETAILS)
7029 if (mode != LVIR_BOUNDS)
7030 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7031 Position.y + Origin.y);
7033 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7036 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7038 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7045 * Retrieves the spacing between listview control items.
7048 * [I] infoPtr : valid pointer to the listview structure
7049 * [IO] lprc : rectangle to receive the output
7050 * on input, lprc->top = nSubItem
7051 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7053 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7054 * not only those of the first column.
7060 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7062 RECT rect = { 0, 0, 0, 0 };
7066 if (!lprc) return FALSE;
7068 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7069 /* Subitem of '0' means item itself, and this works for all control view modes */
7071 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7073 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7075 LISTVIEW_GetOrigin(infoPtr, &origin);
7076 /* this works for any item index, no matter if it exists or not */
7077 y = item * infoPtr->nItemHeight + origin.y;
7079 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7082 rect.bottom = infoPtr->nItemHeight;
7086 /* Native implementation is broken for this case and garbage is left for left and right fields,
7087 we zero them to get predictable output */
7088 lprc->left = lprc->right = lprc->top = 0;
7089 lprc->bottom = infoPtr->nItemHeight;
7090 OffsetRect(lprc, origin.x, y);
7091 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7099 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7100 if (infoPtr->himlSmall)
7101 rect.right = rect.left + infoPtr->iconSize.cx;
7103 rect.right = rect.left;
7105 rect.bottom = rect.top + infoPtr->iconSize.cy;
7113 ERR("Unknown bounds=%d\n", lprc->left);
7117 OffsetRect(&rect, origin.x, y);
7119 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7126 * Retrieves the spacing between listview control items.
7129 * [I] infoPtr : valid pointer to the listview structure
7130 * [I] bSmall : flag for small or large icon
7133 * Horizontal + vertical spacing
7135 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7141 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7145 if (infoPtr->uView == LV_VIEW_ICON)
7146 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7148 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7155 * Retrieves the state of a listview control item.
7158 * [I] infoPtr : valid pointer to the listview structure
7159 * [I] nItem : item index
7160 * [I] uMask : state mask
7163 * State specified by the mask.
7165 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7169 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7171 lvItem.iItem = nItem;
7172 lvItem.iSubItem = 0;
7173 lvItem.mask = LVIF_STATE;
7174 lvItem.stateMask = uMask;
7175 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7177 return lvItem.state & uMask;
7182 * Retrieves the text of a listview control item or subitem.
7185 * [I] hwnd : window handle
7186 * [I] nItem : item index
7187 * [IO] lpLVItem : item information
7188 * [I] isW : TRUE if lpLVItem is Unicode
7191 * SUCCESS : string length
7194 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7196 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7198 lpLVItem->mask = LVIF_TEXT;
7199 lpLVItem->iItem = nItem;
7200 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7202 return textlenT(lpLVItem->pszText, isW);
7207 * Searches for an item based on properties + relationships.
7210 * [I] infoPtr : valid pointer to the listview structure
7211 * [I] nItem : item index
7212 * [I] uFlags : relationship flag
7215 * SUCCESS : item index
7218 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7221 LVFINDINFOW lvFindInfo;
7222 INT nCountPerColumn;
7226 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7227 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7229 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7231 if (uFlags & LVNI_CUT)
7234 if (uFlags & LVNI_DROPHILITED)
7235 uMask |= LVIS_DROPHILITED;
7237 if (uFlags & LVNI_FOCUSED)
7238 uMask |= LVIS_FOCUSED;
7240 if (uFlags & LVNI_SELECTED)
7241 uMask |= LVIS_SELECTED;
7243 /* if we're asked for the focused item, that's only one,
7244 * so it's worth optimizing */
7245 if (uFlags & LVNI_FOCUSED)
7247 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7248 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7251 if (uFlags & LVNI_ABOVE)
7253 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7258 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7264 /* Special case for autoarrange - move 'til the top of a list */
7265 if (is_autoarrange(infoPtr))
7267 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7268 while (nItem - nCountPerRow >= 0)
7270 nItem -= nCountPerRow;
7271 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7276 lvFindInfo.flags = LVFI_NEARESTXY;
7277 lvFindInfo.vkDirection = VK_UP;
7278 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7279 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7281 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7286 else if (uFlags & LVNI_BELOW)
7288 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7290 while (nItem < infoPtr->nItemCount)
7293 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7299 /* Special case for autoarrange - move 'til the bottom of a list */
7300 if (is_autoarrange(infoPtr))
7302 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7303 while (nItem + nCountPerRow < infoPtr->nItemCount )
7305 nItem += nCountPerRow;
7306 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7311 lvFindInfo.flags = LVFI_NEARESTXY;
7312 lvFindInfo.vkDirection = VK_DOWN;
7313 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7314 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7316 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7321 else if (uFlags & LVNI_TOLEFT)
7323 if (infoPtr->uView == LV_VIEW_LIST)
7325 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7326 while (nItem - nCountPerColumn >= 0)
7328 nItem -= nCountPerColumn;
7329 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7333 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7335 /* Special case for autoarrange - move 'til the beginning of a row */
7336 if (is_autoarrange(infoPtr))
7338 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7339 while (nItem % nCountPerRow > 0)
7342 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7347 lvFindInfo.flags = LVFI_NEARESTXY;
7348 lvFindInfo.vkDirection = VK_LEFT;
7349 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7350 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7352 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7357 else if (uFlags & LVNI_TORIGHT)
7359 if (infoPtr->uView == LV_VIEW_LIST)
7361 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7362 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7364 nItem += nCountPerColumn;
7365 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7369 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7371 /* Special case for autoarrange - move 'til the end of a row */
7372 if (is_autoarrange(infoPtr))
7374 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7375 while (nItem % nCountPerRow < nCountPerRow - 1 )
7378 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7383 lvFindInfo.flags = LVFI_NEARESTXY;
7384 lvFindInfo.vkDirection = VK_RIGHT;
7385 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7386 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7388 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7397 /* search by index */
7398 for (i = nItem; i < infoPtr->nItemCount; i++)
7400 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7408 /* LISTVIEW_GetNumberOfWorkAreas */
7412 * Retrieves the origin coordinates when in icon or small icon display mode.
7415 * [I] infoPtr : valid pointer to the listview structure
7416 * [O] lpptOrigin : coordinate information
7421 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7423 INT nHorzPos = 0, nVertPos = 0;
7424 SCROLLINFO scrollInfo;
7426 scrollInfo.cbSize = sizeof(SCROLLINFO);
7427 scrollInfo.fMask = SIF_POS;
7429 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7430 nHorzPos = scrollInfo.nPos;
7431 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7432 nVertPos = scrollInfo.nPos;
7434 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7436 lpptOrigin->x = infoPtr->rcList.left;
7437 lpptOrigin->y = infoPtr->rcList.top;
7438 if (infoPtr->uView == LV_VIEW_LIST)
7439 nHorzPos *= infoPtr->nItemWidth;
7440 else if (infoPtr->uView == LV_VIEW_DETAILS)
7441 nVertPos *= infoPtr->nItemHeight;
7443 lpptOrigin->x -= nHorzPos;
7444 lpptOrigin->y -= nVertPos;
7446 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7451 * Retrieves the width of a string.
7454 * [I] hwnd : window handle
7455 * [I] lpszText : text string to process
7456 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7459 * SUCCESS : string width (in pixels)
7462 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7467 if (is_text(lpszText))
7469 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7470 HDC hdc = GetDC(infoPtr->hwndSelf);
7471 HFONT hOldFont = SelectObject(hdc, hFont);
7474 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7476 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7477 SelectObject(hdc, hOldFont);
7478 ReleaseDC(infoPtr->hwndSelf, hdc);
7480 return stringSize.cx;
7485 * Determines which listview item is located at the specified position.
7488 * [I] infoPtr : valid pointer to the listview structure
7489 * [IO] lpht : hit test information
7490 * [I] subitem : fill out iSubItem.
7491 * [I] select : return the index only if the hit selects the item
7494 * (mm 20001022): We must not allow iSubItem to be touched, for
7495 * an app might pass only a structure with space up to iItem!
7496 * (MS Office 97 does that for instance in the file open dialog)
7499 * SUCCESS : item index
7502 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7504 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7505 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7506 POINT Origin, Position, opt;
7511 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7515 if (subitem) lpht->iSubItem = 0;
7517 LISTVIEW_GetOrigin(infoPtr, &Origin);
7519 /* set whole list relation flags */
7520 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7522 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7523 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7524 lpht->flags |= LVHT_TOLEFT;
7526 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7527 opt.y = lpht->pt.y + infoPtr->rcList.top;
7531 if (infoPtr->rcList.bottom < opt.y)
7532 lpht->flags |= LVHT_BELOW;
7536 if (infoPtr->rcList.left > lpht->pt.x)
7537 lpht->flags |= LVHT_TOLEFT;
7538 else if (infoPtr->rcList.right < lpht->pt.x)
7539 lpht->flags |= LVHT_TORIGHT;
7541 if (infoPtr->rcList.top > lpht->pt.y)
7542 lpht->flags |= LVHT_ABOVE;
7543 else if (infoPtr->rcList.bottom < lpht->pt.y)
7544 lpht->flags |= LVHT_BELOW;
7547 /* even if item is invalid try to find subitem */
7548 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7553 opt.x = lpht->pt.x - Origin.x;
7555 lpht->iSubItem = -1;
7556 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7558 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7560 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7566 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7568 /* if we're outside horizontal columns bounds there's nothing to test further */
7569 if (lpht->iSubItem == -1)
7572 lpht->flags = LVHT_NOWHERE;
7577 TRACE("lpht->flags=0x%x\n", lpht->flags);
7578 if (lpht->flags) return -1;
7580 lpht->flags |= LVHT_NOWHERE;
7582 /* first deal with the large items */
7583 rcSearch.left = lpht->pt.x;
7584 rcSearch.top = lpht->pt.y;
7585 rcSearch.right = rcSearch.left + 1;
7586 rcSearch.bottom = rcSearch.top + 1;
7588 iterator_frameditems(&i, infoPtr, &rcSearch);
7589 iterator_next(&i); /* go to first item in the sequence */
7591 iterator_destroy(&i);
7593 TRACE("lpht->iItem=%d\n", iItem);
7594 if (iItem == -1) return -1;
7596 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7597 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7598 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7599 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7600 lvItem.iItem = iItem;
7601 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7602 lvItem.pszText = szDispText;
7603 lvItem.cchTextMax = DISP_TEXT_SIZE;
7604 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7605 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7607 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7608 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7609 opt.x = lpht->pt.x - Position.x - Origin.x;
7611 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7612 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7614 opt.y = lpht->pt.y - Position.y - Origin.y;
7616 if (infoPtr->uView == LV_VIEW_DETAILS)
7619 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7620 opt.x = lpht->pt.x - Origin.x;
7624 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7625 UnionRect(&rcBounds, &rcBounds, &rcState);
7627 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7628 if (!PtInRect(&rcBounds, opt)) return -1;
7630 if (PtInRect(&rcIcon, opt))
7631 lpht->flags |= LVHT_ONITEMICON;
7632 else if (PtInRect(&rcLabel, opt))
7633 lpht->flags |= LVHT_ONITEMLABEL;
7634 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7635 lpht->flags |= LVHT_ONITEMSTATEICON;
7636 /* special case for LVS_EX_FULLROWSELECT */
7637 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
7638 !(lpht->flags & LVHT_ONITEM))
7640 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7642 if (lpht->flags & LVHT_ONITEM)
7643 lpht->flags &= ~LVHT_NOWHERE;
7644 TRACE("lpht->flags=0x%x\n", lpht->flags);
7646 if (select && !(infoPtr->uView == LV_VIEW_DETAILS &&
7647 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
7648 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
7650 if (infoPtr->uView == LV_VIEW_DETAILS)
7652 /* get main item bounds */
7653 lvItem.iSubItem = 0;
7654 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7655 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7656 UnionRect(&rcBounds, &rcBounds, &rcState);
7658 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7660 return lpht->iItem = iItem;
7665 * Inserts a new item in the listview control.
7668 * [I] infoPtr : valid pointer to the listview structure
7669 * [I] lpLVItem : item information
7670 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7673 * SUCCESS : new item index
7676 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7683 BOOL is_sorted, has_changed;
7685 HWND hwndSelf = infoPtr->hwndSelf;
7687 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7689 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7691 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7692 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7694 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7696 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7698 /* insert item in listview control data structure */
7699 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7700 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7702 /* link with id struct */
7703 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7705 lpID->item = hdpaSubItems;
7706 lpID->id = get_next_itemid(infoPtr);
7707 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7709 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7710 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7712 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7714 /* calculate new item index */
7721 while (i < infoPtr->nItemCount)
7723 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7724 item_s = DPA_GetPtr(hItem, 0);
7726 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, isW);
7727 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7729 if (cmpv >= 0) break;
7735 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7737 TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7738 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7739 if (nItem == -1) goto fail;
7740 infoPtr->nItemCount++;
7742 /* shift indices first so they don't get tangled */
7743 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7745 /* set the item attributes */
7746 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7748 /* full size structure expected - _WIN32IE >= 0x560 */
7751 else if (lpLVItem->mask & LVIF_INDENT)
7753 /* indent member expected - _WIN32IE >= 0x300 */
7754 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7758 /* minimal structure expected */
7759 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7762 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7764 item.mask |= LVIF_STATE;
7765 item.stateMask |= LVIS_STATEIMAGEMASK;
7766 item.state &= ~LVIS_STATEIMAGEMASK;
7767 item.state |= INDEXTOSTATEIMAGEMASK(1);
7770 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7772 /* make room for the position, if we are in the right mode */
7773 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7775 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7777 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7779 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7784 /* send LVN_INSERTITEM notification */
7785 memset(&nmlv, 0, sizeof(NMLISTVIEW));
7787 nmlv.lParam = lpItem->lParam;
7788 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7789 if (!IsWindow(hwndSelf))
7792 /* align items (set position of each item) */
7793 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7797 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7798 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7800 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7802 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7805 /* now is the invalidation fun */
7806 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7810 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7811 LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
7812 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7813 infoPtr->nItemCount--;
7815 DPA_DeletePtr(hdpaSubItems, 0);
7816 DPA_Destroy (hdpaSubItems);
7823 * Checks item visibility.
7826 * [I] infoPtr : valid pointer to the listview structure
7827 * [I] nFirst : item index to check for
7830 * Item visible : TRUE
7831 * Item invisible or failure : FALSE
7833 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7835 POINT Origin, Position;
7840 TRACE("nItem=%d\n", nItem);
7842 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7844 LISTVIEW_GetOrigin(infoPtr, &Origin);
7845 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7846 rcItem.left = Position.x + Origin.x;
7847 rcItem.top = Position.y + Origin.y;
7848 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7849 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7851 hdc = GetDC(infoPtr->hwndSelf);
7852 if (!hdc) return FALSE;
7853 ret = RectVisible(hdc, &rcItem);
7854 ReleaseDC(infoPtr->hwndSelf, hdc);
7861 * Redraws a range of items.
7864 * [I] infoPtr : valid pointer to the listview structure
7865 * [I] nFirst : first item
7866 * [I] nLast : last item
7872 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7876 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
7877 max(nFirst, nLast) >= infoPtr->nItemCount)
7880 for (i = nFirst; i <= nLast; i++)
7881 LISTVIEW_InvalidateItem(infoPtr, i);
7888 * Scroll the content of a listview.
7891 * [I] infoPtr : valid pointer to the listview structure
7892 * [I] dx : horizontal scroll amount in pixels
7893 * [I] dy : vertical scroll amount in pixels
7900 * If the control is in report view (LV_VIEW_DETAILS) the control can
7901 * be scrolled only in line increments. "dy" will be rounded to the
7902 * nearest number of pixels that are a whole line. Ex: if line height
7903 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
7904 * is passed, then the scroll will be 0. (per MSDN 7/2002)
7906 * For: (per experimentation with native control and CSpy ListView)
7907 * LV_VIEW_ICON scrolling in any direction is allowed
7908 * LV_VIEW_SMALLICON scrolling in any direction is allowed
7909 * LV_VIEW_LIST dx=1 = 1 column (horizontal only)
7910 * but will only scroll 1 column per message
7911 * no matter what the value.
7912 * dy must be 0 or FALSE returned.
7913 * LV_VIEW_DETAILS dx=1 = 1 pixel
7917 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7919 switch(infoPtr->uView) {
7920 case LV_VIEW_DETAILS:
7921 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
7922 dy /= infoPtr->nItemHeight;
7925 if (dy != 0) return FALSE;
7931 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
7932 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
7939 * Sets the background color.
7942 * [I] infoPtr : valid pointer to the listview structure
7943 * [I] color : background color
7949 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
7951 TRACE("(color=%x)\n", color);
7953 if(infoPtr->clrBk != color) {
7954 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
7955 infoPtr->clrBk = color;
7956 if (color == CLR_NONE)
7957 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
7960 infoPtr->hBkBrush = CreateSolidBrush(color);
7961 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
7968 /* LISTVIEW_SetBkImage */
7970 /*** Helper for {Insert,Set}ColumnT *only* */
7971 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
7972 const LVCOLUMNW *lpColumn, BOOL isW)
7974 if (lpColumn->mask & LVCF_FMT)
7976 /* format member is valid */
7977 lphdi->mask |= HDI_FORMAT;
7979 /* set text alignment (leftmost column must be left-aligned) */
7980 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
7981 lphdi->fmt |= HDF_LEFT;
7982 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
7983 lphdi->fmt |= HDF_RIGHT;
7984 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
7985 lphdi->fmt |= HDF_CENTER;
7987 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
7988 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
7990 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
7992 lphdi->fmt |= HDF_IMAGE;
7993 lphdi->iImage = I_IMAGECALLBACK;
7996 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
7997 lphdi->fmt |= HDF_FIXEDWIDTH;
8000 if (lpColumn->mask & LVCF_WIDTH)
8002 lphdi->mask |= HDI_WIDTH;
8003 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
8005 /* make it fill the remainder of the controls width */
8009 for(item_index = 0; item_index < (nColumn - 1); item_index++)
8011 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
8012 lphdi->cxy += rcHeader.right - rcHeader.left;
8015 /* retrieve the layout of the header */
8016 GetClientRect(infoPtr->hwndSelf, &rcHeader);
8017 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
8019 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
8022 lphdi->cxy = lpColumn->cx;
8025 if (lpColumn->mask & LVCF_TEXT)
8027 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8028 lphdi->fmt |= HDF_STRING;
8029 lphdi->pszText = lpColumn->pszText;
8030 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8033 if (lpColumn->mask & LVCF_IMAGE)
8035 lphdi->mask |= HDI_IMAGE;
8036 lphdi->iImage = lpColumn->iImage;
8039 if (lpColumn->mask & LVCF_ORDER)
8041 lphdi->mask |= HDI_ORDER;
8042 lphdi->iOrder = lpColumn->iOrder;
8049 * Inserts a new column.
8052 * [I] infoPtr : valid pointer to the listview structure
8053 * [I] nColumn : column index
8054 * [I] lpColumn : column information
8055 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8058 * SUCCESS : new column index
8061 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8062 const LVCOLUMNW *lpColumn, BOOL isW)
8064 COLUMN_INFO *lpColumnInfo;
8068 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8070 if (!lpColumn || nColumn < 0) return -1;
8071 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8073 ZeroMemory(&hdi, sizeof(HDITEMW));
8074 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8077 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8078 * (can be seen in SPY) otherwise column never gets added.
8080 if (!(lpColumn->mask & LVCF_WIDTH)) {
8081 hdi.mask |= HDI_WIDTH;
8086 * when the iSubItem is available Windows copies it to the header lParam. It seems
8087 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8089 if (lpColumn->mask & LVCF_SUBITEM)
8091 hdi.mask |= HDI_LPARAM;
8092 hdi.lParam = lpColumn->iSubItem;
8095 /* create header if not present */
8096 LISTVIEW_CreateHeader(infoPtr);
8097 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8098 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8100 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8103 /* insert item in header control */
8104 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8105 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8106 nColumn, (LPARAM)&hdi);
8107 if (nNewColumn == -1) return -1;
8108 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8110 /* create our own column info */
8111 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8112 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8114 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8115 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8116 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8119 /* now we have to actually adjust the data */
8120 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8122 SUBITEM_INFO *lpSubItem;
8128 item.iSubItem = nNewColumn;
8129 item.mask = LVIF_TEXT | LVIF_IMAGE;
8130 item.iImage = I_IMAGECALLBACK;
8131 item.pszText = LPSTR_TEXTCALLBACKW;
8133 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8135 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8136 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8138 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8139 if (lpSubItem->iSubItem >= nNewColumn)
8140 lpSubItem->iSubItem++;
8143 /* add new subitem for each item */
8145 set_sub_item(infoPtr, &item, isW, &changed);
8149 /* make space for the new column */
8150 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8151 LISTVIEW_UpdateItemSize(infoPtr);
8156 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8159 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8167 * Sets the attributes of a header item.
8170 * [I] infoPtr : valid pointer to the listview structure
8171 * [I] nColumn : column index
8172 * [I] lpColumn : column attributes
8173 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8179 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8180 const LVCOLUMNW *lpColumn, BOOL isW)
8182 HDITEMW hdi, hdiget;
8185 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8187 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8189 ZeroMemory(&hdi, sizeof(HDITEMW));
8190 if (lpColumn->mask & LVCF_FMT)
8192 hdi.mask |= HDI_FORMAT;
8193 hdiget.mask = HDI_FORMAT;
8194 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8195 hdi.fmt = hdiget.fmt & HDF_STRING;
8197 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8199 /* set header item attributes */
8200 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8201 if (!bResult) return FALSE;
8203 if (lpColumn->mask & LVCF_FMT)
8205 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8206 INT oldFmt = lpColumnInfo->fmt;
8208 lpColumnInfo->fmt = lpColumn->fmt;
8209 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8211 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8215 if (lpColumn->mask & LVCF_MINWIDTH)
8216 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8223 * Sets the column order array
8226 * [I] infoPtr : valid pointer to the listview structure
8227 * [I] iCount : number of elements in column order array
8228 * [I] lpiArray : pointer to column order array
8234 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8236 TRACE("iCount %d lpiArray %p\n", iCount, lpiArray);
8238 if (!lpiArray || !IsWindow(infoPtr->hwndHeader)) return FALSE;
8240 infoPtr->colRectsDirty = TRUE;
8242 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8247 * Sets the width of a column
8250 * [I] infoPtr : valid pointer to the listview structure
8251 * [I] nColumn : column index
8252 * [I] cx : column width
8258 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8260 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8264 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
8266 /* set column width only if in report or list mode */
8267 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8269 /* take care of invalid cx values */
8270 if(infoPtr->uView == LV_VIEW_DETAILS && cx < -2) cx = LVSCW_AUTOSIZE;
8271 else if (infoPtr->uView == LV_VIEW_LIST && cx < 1) return FALSE;
8273 /* resize all columns if in LV_VIEW_LIST mode */
8274 if(infoPtr->uView == LV_VIEW_LIST)
8276 infoPtr->nItemWidth = cx;
8277 LISTVIEW_InvalidateList(infoPtr);
8281 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8283 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8288 lvItem.mask = LVIF_TEXT;
8290 lvItem.iSubItem = nColumn;
8291 lvItem.pszText = szDispText;
8292 lvItem.cchTextMax = DISP_TEXT_SIZE;
8293 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8295 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8296 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8297 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8299 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8300 max_cx += infoPtr->iconSize.cx;
8301 max_cx += TRAILING_LABEL_PADDING;
8304 /* autosize based on listview items width */
8305 if(cx == LVSCW_AUTOSIZE)
8307 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8309 /* if iCol is the last column make it fill the remainder of the controls width */
8310 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8315 LISTVIEW_GetOrigin(infoPtr, &Origin);
8316 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8318 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8322 /* Despite what the MS docs say, if this is not the last
8323 column, then MS resizes the column to the width of the
8324 largest text string in the column, including headers
8325 and items. This is different from LVSCW_AUTOSIZE in that
8326 LVSCW_AUTOSIZE ignores the header string length. */
8329 /* retrieve header text */
8330 hdi.mask = HDI_TEXT;
8331 hdi.cchTextMax = DISP_TEXT_SIZE;
8332 hdi.pszText = szDispText;
8333 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8335 HDC hdc = GetDC(infoPtr->hwndSelf);
8336 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8339 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8340 cx = size.cx + TRAILING_HEADER_PADDING;
8341 /* FIXME: Take into account the header image, if one is present */
8342 SelectObject(hdc, old_font);
8343 ReleaseDC(infoPtr->hwndSelf, hdc);
8345 cx = max (cx, max_cx);
8349 if (cx < 0) return FALSE;
8351 /* call header to update the column change */
8352 hdi.mask = HDI_WIDTH;
8353 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8354 TRACE("hdi.cxy=%d\n", hdi.cxy);
8355 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8359 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8362 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8365 HBITMAP hbm_im, hbm_mask, hbm_orig;
8367 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8368 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8371 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8372 ILC_COLOR | ILC_MASK, 2, 2);
8373 hdc_wnd = GetDC(infoPtr->hwndSelf);
8374 hdc = CreateCompatibleDC(hdc_wnd);
8375 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8376 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8377 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8379 rc.left = rc.top = 0;
8380 rc.right = GetSystemMetrics(SM_CXSMICON);
8381 rc.bottom = GetSystemMetrics(SM_CYSMICON);
8383 hbm_orig = SelectObject(hdc, hbm_mask);
8384 FillRect(hdc, &rc, hbr_white);
8385 InflateRect(&rc, -2, -2);
8386 FillRect(hdc, &rc, hbr_black);
8388 SelectObject(hdc, hbm_im);
8389 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8390 SelectObject(hdc, hbm_orig);
8391 ImageList_Add(himl, hbm_im, hbm_mask);
8393 SelectObject(hdc, hbm_im);
8394 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8395 SelectObject(hdc, hbm_orig);
8396 ImageList_Add(himl, hbm_im, hbm_mask);
8398 DeleteObject(hbm_mask);
8399 DeleteObject(hbm_im);
8407 * Sets the extended listview style.
8410 * [I] infoPtr : valid pointer to the listview structure
8412 * [I] dwStyle : style
8415 * SUCCESS : previous style
8418 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8420 DWORD old_ex_style = infoPtr->dwLvExStyle;
8422 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8426 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8428 infoPtr->dwLvExStyle = ex_style;
8430 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8432 HIMAGELIST himl = 0;
8433 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8436 item.mask = LVIF_STATE;
8437 item.stateMask = LVIS_STATEIMAGEMASK;
8438 item.state = INDEXTOSTATEIMAGEMASK(1);
8439 LISTVIEW_SetItemState(infoPtr, -1, &item);
8441 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8442 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8443 ImageList_Destroy(infoPtr->himlState);
8445 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8446 /* checkbox list replaces previous custom list or... */
8447 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8448 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8449 /* ...previous was checkbox list */
8450 (old_ex_style & LVS_EX_CHECKBOXES))
8451 ImageList_Destroy(himl);
8454 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8458 /* if not already created */
8459 LISTVIEW_CreateHeader(infoPtr);
8461 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8462 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8463 style |= HDS_DRAGDROP;
8465 style &= ~HDS_DRAGDROP;
8466 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8469 /* GRIDLINES adds decoration at top so changes sizes */
8470 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8472 LISTVIEW_CreateHeader(infoPtr);
8473 LISTVIEW_UpdateSize(infoPtr);
8476 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8478 LISTVIEW_CreateHeader(infoPtr);
8481 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8483 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8484 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8487 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8489 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8490 LISTVIEW_CreateHeader(infoPtr);
8492 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8493 LISTVIEW_UpdateSize(infoPtr);
8494 LISTVIEW_UpdateScroll(infoPtr);
8497 LISTVIEW_InvalidateList(infoPtr);
8498 return old_ex_style;
8503 * Sets the new hot cursor used during hot tracking and hover selection.
8506 * [I] infoPtr : valid pointer to the listview structure
8507 * [I] hCursor : the new hot cursor handle
8510 * Returns the previous hot cursor
8512 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8514 HCURSOR oldCursor = infoPtr->hHotCursor;
8516 infoPtr->hHotCursor = hCursor;
8524 * Sets the hot item index.
8527 * [I] infoPtr : valid pointer to the listview structure
8528 * [I] iIndex : index
8531 * SUCCESS : previous hot item index
8532 * FAILURE : -1 (no hot item)
8534 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8536 INT iOldIndex = infoPtr->nHotItem;
8538 infoPtr->nHotItem = iIndex;
8546 * Sets the amount of time the cursor must hover over an item before it is selected.
8549 * [I] infoPtr : valid pointer to the listview structure
8550 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8553 * Returns the previous hover time
8555 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8557 DWORD oldHoverTime = infoPtr->dwHoverTime;
8559 infoPtr->dwHoverTime = dwHoverTime;
8561 return oldHoverTime;
8566 * Sets spacing for icons of LVS_ICON style.
8569 * [I] infoPtr : valid pointer to the listview structure
8570 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8571 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8574 * MAKELONG(oldcx, oldcy)
8576 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8578 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8580 TRACE("requested=(%d,%d)\n", cx, cy);
8582 /* this is supported only for LVS_ICON style */
8583 if (infoPtr->uView != LV_VIEW_ICON) return oldspacing;
8585 /* set to defaults, if instructed to */
8586 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
8587 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
8589 /* if 0 then compute width
8590 * FIXME: computed cx and cy is not matching native behaviour */
8592 cx = GetSystemMetrics(SM_CXICONSPACING);
8593 if (infoPtr->iconSize.cx + ICON_LR_PADDING > cx)
8594 cx = infoPtr->iconSize.cx + ICON_LR_PADDING;
8597 /* if 0 then compute height */
8599 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
8600 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
8603 infoPtr->iconSpacing.cx = cx;
8604 infoPtr->iconSpacing.cy = cy;
8606 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8607 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
8608 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8609 infoPtr->ntmHeight);
8611 /* these depend on the iconSpacing */
8612 LISTVIEW_UpdateItemSize(infoPtr);
8617 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8621 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8628 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8629 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8638 * [I] infoPtr : valid pointer to the listview structure
8639 * [I] nType : image list type
8640 * [I] himl : image list handle
8643 * SUCCESS : old image list
8646 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8648 INT oldHeight = infoPtr->nItemHeight;
8649 HIMAGELIST himlOld = 0;
8651 TRACE("(nType=%d, himl=%p\n", nType, himl);
8656 himlOld = infoPtr->himlNormal;
8657 infoPtr->himlNormal = himl;
8658 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8659 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8663 himlOld = infoPtr->himlSmall;
8664 infoPtr->himlSmall = himl;
8665 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8666 if (infoPtr->hwndHeader)
8667 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
8671 himlOld = infoPtr->himlState;
8672 infoPtr->himlState = himl;
8673 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8674 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8678 ERR("Unknown icon type=%d\n", nType);
8682 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8683 if (infoPtr->nItemHeight != oldHeight)
8684 LISTVIEW_UpdateScroll(infoPtr);
8691 * Preallocates memory (does *not* set the actual count of items !)
8694 * [I] infoPtr : valid pointer to the listview structure
8695 * [I] nItems : item count (projected number of items to allocate)
8696 * [I] dwFlags : update flags
8702 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8704 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8706 if (infoPtr->dwStyle & LVS_OWNERDATA)
8708 INT nOldCount = infoPtr->nItemCount;
8710 if (nItems < nOldCount)
8712 RANGE range = { nItems, nOldCount };
8713 ranges_del(infoPtr->selectionRanges, range);
8714 if (infoPtr->nFocusedItem >= nItems)
8716 LISTVIEW_SetItemFocus(infoPtr, -1);
8717 SetRectEmpty(&infoPtr->rcFocus);
8721 infoPtr->nItemCount = nItems;
8722 LISTVIEW_UpdateScroll(infoPtr);
8724 /* the flags are valid only in ownerdata report and list modes */
8725 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8727 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8728 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8730 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8731 LISTVIEW_InvalidateList(infoPtr);
8738 LISTVIEW_GetOrigin(infoPtr, &Origin);
8739 nFrom = min(nOldCount, nItems);
8740 nTo = max(nOldCount, nItems);
8742 if (infoPtr->uView == LV_VIEW_DETAILS)
8745 rcErase.top = nFrom * infoPtr->nItemHeight;
8746 rcErase.right = infoPtr->nItemWidth;
8747 rcErase.bottom = nTo * infoPtr->nItemHeight;
8748 OffsetRect(&rcErase, Origin.x, Origin.y);
8749 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8750 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8752 else /* LV_VIEW_LIST */
8754 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8756 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8757 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8758 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8759 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8760 OffsetRect(&rcErase, Origin.x, Origin.y);
8761 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8762 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8764 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8766 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8767 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8768 OffsetRect(&rcErase, Origin.x, Origin.y);
8769 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8770 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8776 /* According to MSDN for non-LVS_OWNERDATA this is just
8777 * a performance issue. The control allocates its internal
8778 * data structures for the number of items specified. It
8779 * cuts down on the number of memory allocations. Therefore
8780 * we will just issue a WARN here
8782 WARN("for non-ownerdata performance option not implemented.\n");
8790 * Sets the position of an item.
8793 * [I] infoPtr : valid pointer to the listview structure
8794 * [I] nItem : item index
8795 * [I] pt : coordinate
8801 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8805 TRACE("(nItem=%d, pt=%s\n", nItem, wine_dbgstr_point(pt));
8807 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8808 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8811 LISTVIEW_GetOrigin(infoPtr, &Origin);
8813 /* This point value seems to be an undocumented feature.
8814 * The best guess is that it means either at the origin,
8815 * or at true beginning of the list. I will assume the origin. */
8816 if ((Pt.x == -1) && (Pt.y == -1))
8819 if (infoPtr->uView == LV_VIEW_ICON)
8821 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8822 Pt.y -= ICON_TOP_PADDING;
8827 infoPtr->bAutoarrange = FALSE;
8829 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8834 * Sets the state of one or many items.
8837 * [I] infoPtr : valid pointer to the listview structure
8838 * [I] nItem : item index
8839 * [I] item : item or subitem info
8845 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8850 if (!item) return FALSE;
8852 lvItem.iItem = nItem;
8853 lvItem.iSubItem = 0;
8854 lvItem.mask = LVIF_STATE;
8855 lvItem.state = item->state;
8856 lvItem.stateMask = item->stateMask;
8857 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8864 /* special case optimization for recurring attemp to deselect all */
8865 if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
8868 /* select all isn't allowed in LVS_SINGLESEL */
8869 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8872 /* focus all isn't allowed */
8873 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8875 notify = infoPtr->bDoChangeNotify;
8876 if (infoPtr->dwStyle & LVS_OWNERDATA)
8878 infoPtr->bDoChangeNotify = FALSE;
8879 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8880 oldstate |= LVIS_SELECTED;
8881 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8884 /* apply to all items */
8885 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8886 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
8888 if (infoPtr->dwStyle & LVS_OWNERDATA)
8892 infoPtr->bDoChangeNotify = notify;
8896 nmlv.uNewState = lvItem.state & lvItem.stateMask;
8897 nmlv.uOldState = oldstate & lvItem.stateMask;
8898 nmlv.uChanged = LVIF_STATE;
8899 nmlv.ptAction.x = nmlv.ptAction.y = 0;
8902 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
8906 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
8913 * Sets the text of an item or subitem.
8916 * [I] hwnd : window handle
8917 * [I] nItem : item index
8918 * [I] lpLVItem : item or subitem info
8919 * [I] isW : TRUE if input is Unicode
8925 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
8929 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8930 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
8932 lvItem.iItem = nItem;
8933 lvItem.iSubItem = lpLVItem->iSubItem;
8934 lvItem.mask = LVIF_TEXT;
8935 lvItem.pszText = lpLVItem->pszText;
8936 lvItem.cchTextMax = lpLVItem->cchTextMax;
8938 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
8940 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
8945 * Set item index that marks the start of a multiple selection.
8948 * [I] infoPtr : valid pointer to the listview structure
8949 * [I] nIndex : index
8952 * Index number or -1 if there is no selection mark.
8954 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
8956 INT nOldIndex = infoPtr->nSelectionMark;
8958 TRACE("(nIndex=%d)\n", nIndex);
8960 infoPtr->nSelectionMark = nIndex;
8967 * Sets the text background color.
8970 * [I] infoPtr : valid pointer to the listview structure
8971 * [I] color : text background color
8977 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8979 TRACE("(color=%x)\n", color);
8981 infoPtr->clrTextBk = color;
8987 * Sets the text foreground color.
8990 * [I] infoPtr : valid pointer to the listview structure
8991 * [I] color : text color
8997 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
8999 TRACE("(color=%x)\n", color);
9001 infoPtr->clrText = color;
9007 * Sets new ToolTip window to ListView control.
9010 * [I] infoPtr : valid pointer to the listview structure
9011 * [I] hwndNewToolTip : handle to new ToolTip
9016 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
9018 HWND hwndOldToolTip = infoPtr->hwndToolTip;
9019 infoPtr->hwndToolTip = hwndNewToolTip;
9020 return hwndOldToolTip;
9025 * sets the Unicode character format flag for the control
9027 * [I] infoPtr :valid pointer to the listview structure
9028 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9031 * Old Unicode Format
9033 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9035 SHORT rc = infoPtr->notifyFormat;
9036 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9037 return rc == NFR_UNICODE;
9042 * sets the control view mode
9044 * [I] infoPtr :valid pointer to the listview structure
9045 * [I] nView :new view mode value
9051 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9053 SIZE oldIconSize = infoPtr->iconSize;
9056 if (infoPtr->uView == nView) return 1;
9058 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9059 if (nView == LV_VIEW_TILE)
9061 FIXME("View LV_VIEW_TILE unimplemented\n");
9065 infoPtr->uView = nView;
9067 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9068 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9070 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9071 SetRectEmpty(&infoPtr->rcFocus);
9073 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9074 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9079 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9081 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9082 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9083 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9085 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9087 case LV_VIEW_SMALLICON:
9088 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9090 case LV_VIEW_DETAILS:
9095 LISTVIEW_CreateHeader( infoPtr );
9097 hl.prc = &infoPtr->rcList;
9099 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9100 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9101 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9108 LISTVIEW_UpdateItemSize(infoPtr);
9109 LISTVIEW_UpdateSize(infoPtr);
9110 LISTVIEW_UpdateScroll(infoPtr);
9111 LISTVIEW_InvalidateList(infoPtr);
9113 TRACE("nView=%d\n", nView);
9118 /* LISTVIEW_SetWorkAreas */
9122 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9125 * [I] first : pointer to first ITEM_INFO to compare
9126 * [I] second : pointer to second ITEM_INFO to compare
9127 * [I] lParam : HWND of control
9130 * if first comes before second : negative
9131 * if first comes after second : positive
9132 * if first and second are equivalent : zero
9134 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9136 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9137 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9138 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9140 /* Forward the call to the client defined callback */
9141 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9146 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9149 * [I] first : pointer to first ITEM_INFO to compare
9150 * [I] second : pointer to second ITEM_INFO to compare
9151 * [I] lParam : HWND of control
9154 * if first comes before second : negative
9155 * if first comes after second : positive
9156 * if first and second are equivalent : zero
9158 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9160 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9161 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9162 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9164 /* Forward the call to the client defined callback */
9165 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9170 * Sorts the listview items.
9173 * [I] infoPtr : valid pointer to the listview structure
9174 * [I] pfnCompare : application-defined value
9175 * [I] lParamSort : pointer to comparison callback
9176 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9182 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9183 LPARAM lParamSort, BOOL IsEx)
9187 LPVOID selectionMarkItem = NULL;
9188 LPVOID focusedItem = NULL;
9191 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9193 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9195 if (!pfnCompare) return FALSE;
9196 if (!infoPtr->hdpaItems) return FALSE;
9198 /* if there are 0 or 1 items, there is no need to sort */
9199 if (infoPtr->nItemCount < 2) return TRUE;
9201 /* clear selection */
9202 ranges_clear(infoPtr->selectionRanges);
9204 /* save selection mark and focused item */
9205 if (infoPtr->nSelectionMark >= 0)
9206 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9207 if (infoPtr->nFocusedItem >= 0)
9208 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9210 infoPtr->pfnCompare = pfnCompare;
9211 infoPtr->lParamSort = lParamSort;
9213 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9215 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9217 /* restore selection ranges */
9218 for (i=0; i < infoPtr->nItemCount; i++)
9220 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9221 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9223 if (lpItem->state & LVIS_SELECTED)
9224 ranges_additem(infoPtr->selectionRanges, i);
9226 /* restore selection mark and focused item */
9227 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9228 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9230 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9232 /* refresh the display */
9233 LISTVIEW_InvalidateList(infoPtr);
9239 * Update theme handle after a theme change.
9242 * [I] infoPtr : valid pointer to the listview structure
9246 * FAILURE : something else
9248 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9250 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9251 CloseThemeData(theme);
9252 OpenThemeData(infoPtr->hwndSelf, themeClass);
9258 * Updates an items or rearranges the listview control.
9261 * [I] infoPtr : valid pointer to the listview structure
9262 * [I] nItem : item index
9268 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9270 TRACE("(nItem=%d)\n", nItem);
9272 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9274 /* rearrange with default alignment style */
9275 if (is_autoarrange(infoPtr))
9276 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9278 LISTVIEW_InvalidateItem(infoPtr, nItem);
9285 * Draw the track line at the place defined in the infoPtr structure.
9286 * The line is drawn with a XOR pen so drawing the line for the second time
9287 * in the same place erases the line.
9290 * [I] infoPtr : valid pointer to the listview structure
9296 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9300 if (infoPtr->xTrackLine == -1)
9303 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9305 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9306 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9307 ReleaseDC(infoPtr->hwndSelf, hdc);
9313 * Called when an edit control should be displayed. This function is called after
9314 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9317 * [I] hwnd : Handle to the listview
9318 * [I] uMsg : WM_TIMER (ignored)
9319 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9320 * [I] dwTimer : The elapsed time (ignored)
9325 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9327 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9328 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9330 KillTimer(hwnd, idEvent);
9331 editItem->fEnabled = FALSE;
9332 /* check if the item is still selected */
9333 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9334 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9339 * Creates the listview control - the WM_NCCREATE phase.
9342 * [I] hwnd : window handle
9343 * [I] lpcs : the create parameters
9349 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9351 LISTVIEW_INFO *infoPtr;
9354 TRACE("(lpcs=%p)\n", lpcs);
9356 /* initialize info pointer */
9357 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9358 if (!infoPtr) return FALSE;
9360 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9362 infoPtr->hwndSelf = hwnd;
9363 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9364 map_style_view(infoPtr);
9365 /* determine the type of structures to use */
9366 infoPtr->hwndNotify = lpcs->hwndParent;
9367 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9369 /* initialize color information */
9370 infoPtr->clrBk = CLR_NONE;
9371 infoPtr->clrText = CLR_DEFAULT;
9372 infoPtr->clrTextBk = CLR_DEFAULT;
9373 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9375 /* set default values */
9376 infoPtr->nFocusedItem = -1;
9377 infoPtr->nSelectionMark = -1;
9378 infoPtr->nHotItem = -1;
9379 infoPtr->bRedraw = TRUE;
9380 infoPtr->bNoItemMetrics = TRUE;
9381 infoPtr->bDoChangeNotify = TRUE;
9382 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
9383 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
9384 infoPtr->nEditLabelItem = -1;
9385 infoPtr->nLButtonDownItem = -1;
9386 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9387 infoPtr->nMeasureItemHeight = 0;
9388 infoPtr->xTrackLine = -1; /* no track line */
9389 infoPtr->itemEdit.fEnabled = FALSE;
9390 infoPtr->iVersion = COMCTL32_VERSION;
9391 infoPtr->colRectsDirty = FALSE;
9393 /* get default font (icon title) */
9394 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9395 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9396 infoPtr->hFont = infoPtr->hDefaultFont;
9397 LISTVIEW_SaveTextMetrics(infoPtr);
9399 /* allocate memory for the data structure */
9400 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9401 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9402 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9403 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9404 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9405 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9409 DestroyWindow(infoPtr->hwndHeader);
9410 ranges_destroy(infoPtr->selectionRanges);
9411 DPA_Destroy(infoPtr->hdpaItems);
9412 DPA_Destroy(infoPtr->hdpaItemIds);
9413 DPA_Destroy(infoPtr->hdpaPosX);
9414 DPA_Destroy(infoPtr->hdpaPosY);
9415 DPA_Destroy(infoPtr->hdpaColumns);
9422 * Creates the listview control - the WM_CREATE phase. Most of the data is
9423 * already set up in LISTVIEW_NCCreate
9426 * [I] hwnd : window handle
9427 * [I] lpcs : the create parameters
9433 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9435 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9437 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9439 infoPtr->dwStyle = lpcs->style;
9440 map_style_view(infoPtr);
9442 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9443 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9444 /* on error defaulting to ANSI notifications */
9445 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9446 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9448 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9450 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9453 infoPtr->hwndHeader = 0;
9455 /* init item size to avoid division by 0 */
9456 LISTVIEW_UpdateItemSize (infoPtr);
9457 LISTVIEW_UpdateSize (infoPtr);
9459 if (infoPtr->uView == LV_VIEW_DETAILS)
9461 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9463 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9465 LISTVIEW_UpdateScroll(infoPtr);
9466 /* send WM_MEASUREITEM notification */
9467 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9470 OpenThemeData(hwnd, themeClass);
9472 /* initialize the icon sizes */
9473 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9474 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9480 * Destroys the listview control.
9483 * [I] infoPtr : valid pointer to the listview structure
9489 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9491 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9492 CloseThemeData(theme);
9494 /* delete all items */
9495 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9502 * Enables the listview control.
9505 * [I] infoPtr : valid pointer to the listview structure
9506 * [I] bEnable : specifies whether to enable or disable the window
9512 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9514 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9515 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9521 * Erases the background of the listview control.
9524 * [I] infoPtr : valid pointer to the listview structure
9525 * [I] hdc : device context handle
9531 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9535 TRACE("(hdc=%p)\n", hdc);
9537 if (!GetClipBox(hdc, &rc)) return FALSE;
9539 if (infoPtr->clrBk == CLR_NONE)
9541 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9542 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9543 (WPARAM)hdc, PRF_ERASEBKGND);
9545 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9548 /* for double buffered controls we need to do this during refresh */
9549 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9551 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9557 * Helper function for LISTVIEW_[HV]Scroll *only*.
9558 * Performs vertical/horizontal scrolling by a give amount.
9561 * [I] infoPtr : valid pointer to the listview structure
9562 * [I] dx : amount of horizontal scroll
9563 * [I] dy : amount of vertical scroll
9565 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9567 /* now we can scroll the list */
9568 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9569 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9570 /* if we have focus, adjust rect */
9571 OffsetRect(&infoPtr->rcFocus, dx, dy);
9572 UpdateWindow(infoPtr->hwndSelf);
9577 * Performs vertical scrolling.
9580 * [I] infoPtr : valid pointer to the listview structure
9581 * [I] nScrollCode : scroll code
9582 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9583 * [I] hScrollWnd : scrollbar control window handle
9589 * SB_LINEUP/SB_LINEDOWN:
9590 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9591 * for LVS_REPORT is 1 line
9592 * for LVS_LIST cannot occur
9595 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9598 INT nOldScrollPos, nNewScrollPos;
9599 SCROLLINFO scrollInfo;
9602 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9603 debugscrollcode(nScrollCode), nScrollDiff);
9605 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9607 scrollInfo.cbSize = sizeof(SCROLLINFO);
9608 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9610 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9612 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9614 nOldScrollPos = scrollInfo.nPos;
9615 switch (nScrollCode)
9621 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9625 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9629 nScrollDiff = -scrollInfo.nPage;
9633 nScrollDiff = scrollInfo.nPage;
9636 case SB_THUMBPOSITION:
9638 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9645 /* quit right away if pos isn't changing */
9646 if (nScrollDiff == 0) return 0;
9648 /* calculate new position, and handle overflows */
9649 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9650 if (nScrollDiff > 0) {
9651 if (nNewScrollPos < nOldScrollPos ||
9652 nNewScrollPos > scrollInfo.nMax)
9653 nNewScrollPos = scrollInfo.nMax;
9655 if (nNewScrollPos > nOldScrollPos ||
9656 nNewScrollPos < scrollInfo.nMin)
9657 nNewScrollPos = scrollInfo.nMin;
9660 /* set the new position, and reread in case it changed */
9661 scrollInfo.fMask = SIF_POS;
9662 scrollInfo.nPos = nNewScrollPos;
9663 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9665 /* carry on only if it really changed */
9666 if (nNewScrollPos == nOldScrollPos) return 0;
9668 /* now adjust to client coordinates */
9669 nScrollDiff = nOldScrollPos - nNewScrollPos;
9670 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9672 /* and scroll the window */
9673 scroll_list(infoPtr, 0, nScrollDiff);
9680 * Performs horizontal scrolling.
9683 * [I] infoPtr : valid pointer to the listview structure
9684 * [I] nScrollCode : scroll code
9685 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9686 * [I] hScrollWnd : scrollbar control window handle
9692 * SB_LINELEFT/SB_LINERIGHT:
9693 * for LVS_ICON, LVS_SMALLICON 1 pixel
9694 * for LVS_REPORT is 1 pixel
9695 * for LVS_LIST is 1 column --> which is a 1 because the
9696 * scroll is based on columns not pixels
9699 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9702 INT nOldScrollPos, nNewScrollPos;
9703 SCROLLINFO scrollInfo;
9706 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9707 debugscrollcode(nScrollCode), nScrollDiff);
9709 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9711 scrollInfo.cbSize = sizeof(SCROLLINFO);
9712 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9714 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9716 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9718 nOldScrollPos = scrollInfo.nPos;
9720 switch (nScrollCode)
9726 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9730 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9734 nScrollDiff = -scrollInfo.nPage;
9738 nScrollDiff = scrollInfo.nPage;
9741 case SB_THUMBPOSITION:
9743 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9750 /* quit right away if pos isn't changing */
9751 if (nScrollDiff == 0) return 0;
9753 /* calculate new position, and handle overflows */
9754 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9755 if (nScrollDiff > 0) {
9756 if (nNewScrollPos < nOldScrollPos ||
9757 nNewScrollPos > scrollInfo.nMax)
9758 nNewScrollPos = scrollInfo.nMax;
9760 if (nNewScrollPos > nOldScrollPos ||
9761 nNewScrollPos < scrollInfo.nMin)
9762 nNewScrollPos = scrollInfo.nMin;
9765 /* set the new position, and reread in case it changed */
9766 scrollInfo.fMask = SIF_POS;
9767 scrollInfo.nPos = nNewScrollPos;
9768 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9770 /* carry on only if it really changed */
9771 if (nNewScrollPos == nOldScrollPos) return 0;
9773 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9775 /* now adjust to client coordinates */
9776 nScrollDiff = nOldScrollPos - nNewScrollPos;
9777 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9779 /* and scroll the window */
9780 scroll_list(infoPtr, nScrollDiff, 0);
9785 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9787 INT gcWheelDelta = 0;
9788 INT pulScrollLines = 3;
9790 TRACE("(wheelDelta=%d)\n", wheelDelta);
9792 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9793 gcWheelDelta -= wheelDelta;
9795 switch(infoPtr->uView)
9798 case LV_VIEW_SMALLICON:
9800 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9801 * should be fixed in the future.
9803 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
9804 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9807 case LV_VIEW_DETAILS:
9808 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
9810 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9811 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
9812 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll);
9817 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9828 * [I] infoPtr : valid pointer to the listview structure
9829 * [I] nVirtualKey : virtual key
9830 * [I] lKeyData : key data
9835 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9837 HWND hwndSelf = infoPtr->hwndSelf;
9839 NMLVKEYDOWN nmKeyDown;
9841 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9843 /* send LVN_KEYDOWN notification */
9844 nmKeyDown.wVKey = nVirtualKey;
9845 nmKeyDown.flags = 0;
9846 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9847 if (!IsWindow(hwndSelf))
9850 switch (nVirtualKey)
9853 nItem = infoPtr->nFocusedItem;
9854 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9855 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9859 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9861 if (!notify(infoPtr, NM_RETURN)) return 0;
9862 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9867 if (infoPtr->nItemCount > 0)
9872 if (infoPtr->nItemCount > 0)
9873 nItem = infoPtr->nItemCount - 1;
9877 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9881 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9885 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9889 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
9893 if (infoPtr->uView == LV_VIEW_DETAILS)
9895 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9896 if (infoPtr->nFocusedItem == topidx)
9897 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
9902 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
9903 * LISTVIEW_GetCountPerRow(infoPtr);
9904 if(nItem < 0) nItem = 0;
9908 if (infoPtr->uView == LV_VIEW_DETAILS)
9910 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
9911 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
9912 if (infoPtr->nFocusedItem == topidx + cnt - 1)
9913 nItem = infoPtr->nFocusedItem + cnt - 1;
9915 nItem = topidx + cnt - 1;
9918 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
9919 * LISTVIEW_GetCountPerRow(infoPtr);
9920 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
9924 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
9925 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
9935 * [I] infoPtr : valid pointer to the listview structure
9940 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
9944 /* if we did not have the focus, there's nothing to do */
9945 if (!infoPtr->bFocus) return 0;
9947 /* send NM_KILLFOCUS notification */
9948 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
9950 /* if we have a focus rectangle, get rid of it */
9951 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
9953 /* if have a marquee selection, stop it */
9954 if (infoPtr->bMarqueeSelect)
9956 /* Remove the marquee rectangle and release our mouse capture */
9957 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
9960 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
9962 infoPtr->bMarqueeSelect = FALSE;
9963 infoPtr->bScrolling = FALSE;
9964 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
9967 /* set window focus flag */
9968 infoPtr->bFocus = FALSE;
9970 /* invalidate the selected items before resetting focus flag */
9971 LISTVIEW_InvalidateSelectedItems(infoPtr);
9978 * Processes double click messages (left mouse button).
9981 * [I] infoPtr : valid pointer to the listview structure
9982 * [I] wKey : key flag
9983 * [I] x,y : mouse coordinate
9988 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9990 LVHITTESTINFO htInfo;
9992 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
9994 /* Cancel the item edition if any */
9995 if (infoPtr->itemEdit.fEnabled)
9997 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
9998 infoPtr->itemEdit.fEnabled = FALSE;
10001 /* send NM_RELEASEDCAPTURE notification */
10002 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10007 /* send NM_DBLCLK notification */
10008 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
10009 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
10011 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
10012 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
10017 static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
10022 r.top = r.bottom = pt.y;
10023 r.left = r.right = pt.x;
10025 InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
10027 SetCapture(infoPtr->hwndSelf);
10031 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
10033 if (msg.message == WM_MOUSEMOVE)
10035 pt.x = (short)LOWORD(msg.lParam);
10036 pt.y = (short)HIWORD(msg.lParam);
10037 if (PtInRect(&r, pt))
10045 else if (msg.message >= WM_LBUTTONDOWN &&
10046 msg.message <= WM_RBUTTONDBLCLK)
10051 DispatchMessageW(&msg);
10054 if (GetCapture() != infoPtr->hwndSelf)
10065 * Processes mouse down messages (left mouse button).
10068 * infoPtr [I ] valid pointer to the listview structure
10069 * wKey [I ] key flag
10070 * x,y [I ] mouse coordinate
10075 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10077 LVHITTESTINFO lvHitTestInfo;
10078 static BOOL bGroupSelect = TRUE;
10079 POINT pt = { x, y };
10082 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10084 /* send NM_RELEASEDCAPTURE notification */
10085 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10087 /* set left button down flag and record the click position */
10088 infoPtr->bLButtonDown = TRUE;
10089 infoPtr->ptClickPos = pt;
10090 infoPtr->bDragging = FALSE;
10091 infoPtr->bMarqueeSelect = FALSE;
10092 infoPtr->bScrolling = FALSE;
10094 lvHitTestInfo.pt.x = x;
10095 lvHitTestInfo.pt.y = y;
10097 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10098 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10099 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10101 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10103 toggle_checkbox_state(infoPtr, nItem);
10107 if (infoPtr->dwStyle & LVS_SINGLESEL)
10109 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10110 infoPtr->nEditLabelItem = nItem;
10112 LISTVIEW_SetSelection(infoPtr, nItem);
10116 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10120 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10121 LISTVIEW_SetItemFocus(infoPtr, nItem);
10122 infoPtr->nSelectionMark = nItem;
10128 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10129 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10131 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10132 infoPtr->nSelectionMark = nItem;
10135 else if (wKey & MK_CONTROL)
10139 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10141 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10142 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10143 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10144 infoPtr->nSelectionMark = nItem;
10146 else if (wKey & MK_SHIFT)
10148 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10152 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10154 infoPtr->nEditLabelItem = nItem;
10155 infoPtr->nLButtonDownItem = nItem;
10157 LISTVIEW_SetItemFocus(infoPtr, nItem);
10160 /* set selection (clears other pre-existing selections) */
10161 LISTVIEW_SetSelection(infoPtr, nItem);
10165 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10166 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10170 if (!infoPtr->bFocus)
10171 SetFocus(infoPtr->hwndSelf);
10173 /* remove all selections */
10174 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10175 LISTVIEW_DeselectAll(infoPtr);
10184 * Processes mouse up messages (left mouse button).
10187 * infoPtr [I ] valid pointer to the listview structure
10188 * wKey [I ] key flag
10189 * x,y [I ] mouse coordinate
10194 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10196 LVHITTESTINFO lvHitTestInfo;
10198 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10200 if (!infoPtr->bLButtonDown) return 0;
10202 lvHitTestInfo.pt.x = x;
10203 lvHitTestInfo.pt.y = y;
10205 /* send NM_CLICK notification */
10206 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10207 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10209 /* set left button flag */
10210 infoPtr->bLButtonDown = FALSE;
10212 /* set a single selection, reset others */
10213 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10214 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10215 infoPtr->nLButtonDownItem = -1;
10217 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10219 /* Remove the marquee rectangle and release our mouse capture */
10220 if (infoPtr->bMarqueeSelect)
10222 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10226 SetRect(&infoPtr->marqueeRect, 0, 0, 0, 0);
10227 SetRect(&infoPtr->marqueeDrawRect, 0, 0, 0, 0);
10229 infoPtr->bDragging = FALSE;
10230 infoPtr->bMarqueeSelect = FALSE;
10231 infoPtr->bScrolling = FALSE;
10233 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10237 /* if we clicked on a selected item, edit the label */
10238 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10240 /* we want to make sure the user doesn't want to do a double click. So we will
10241 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10243 infoPtr->itemEdit.fEnabled = TRUE;
10244 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10245 SetTimer(infoPtr->hwndSelf,
10246 (UINT_PTR)&infoPtr->itemEdit,
10247 GetDoubleClickTime(),
10248 LISTVIEW_DelayedEditItem);
10251 if (!infoPtr->bFocus)
10252 SetFocus(infoPtr->hwndSelf);
10259 * Destroys the listview control (called after WM_DESTROY).
10262 * [I] infoPtr : valid pointer to the listview structure
10267 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10273 /* destroy data structure */
10274 DPA_Destroy(infoPtr->hdpaItems);
10275 DPA_Destroy(infoPtr->hdpaItemIds);
10276 DPA_Destroy(infoPtr->hdpaPosX);
10277 DPA_Destroy(infoPtr->hdpaPosY);
10279 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10280 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10281 DPA_Destroy(infoPtr->hdpaColumns);
10282 ranges_destroy(infoPtr->selectionRanges);
10284 /* destroy image lists */
10285 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10287 ImageList_Destroy(infoPtr->himlNormal);
10288 ImageList_Destroy(infoPtr->himlSmall);
10289 ImageList_Destroy(infoPtr->himlState);
10292 /* destroy font, bkgnd brush */
10293 infoPtr->hFont = 0;
10294 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10295 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10297 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10299 /* free listview info pointer*/
10307 * Handles notifications.
10310 * [I] infoPtr : valid pointer to the listview structure
10311 * [I] lpnmhdr : notification information
10316 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10320 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10322 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10324 /* remember: HDN_LAST < HDN_FIRST */
10325 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10326 lpnmh = (NMHEADERW *)lpnmhdr;
10328 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10330 switch (lpnmhdr->code)
10335 COLUMN_INFO *lpColumnInfo;
10339 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10342 /* remove the old line (if any) */
10343 LISTVIEW_DrawTrackLine(infoPtr);
10345 /* compute & draw the new line */
10346 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10347 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10348 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10349 infoPtr->xTrackLine = x + ptOrigin.x;
10350 LISTVIEW_DrawTrackLine(infoPtr);
10351 return notify_forward_header(infoPtr, lpnmh);
10354 case HDN_ENDTRACKA:
10355 case HDN_ENDTRACKW:
10356 /* remove the track line (if any) */
10357 LISTVIEW_DrawTrackLine(infoPtr);
10358 infoPtr->xTrackLine = -1;
10359 return notify_forward_header(infoPtr, lpnmh);
10361 case HDN_BEGINDRAG:
10362 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10363 return notify_forward_header(infoPtr, lpnmh);
10366 infoPtr->colRectsDirty = TRUE;
10367 LISTVIEW_InvalidateList(infoPtr);
10368 return notify_forward_header(infoPtr, lpnmh);
10370 case HDN_ITEMCHANGEDW:
10371 case HDN_ITEMCHANGEDA:
10373 COLUMN_INFO *lpColumnInfo;
10377 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10379 hdi.mask = HDI_WIDTH;
10380 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10384 cxy = lpnmh->pitem->cxy;
10386 /* determine how much we change since the last know position */
10387 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10388 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10391 lpColumnInfo->rcHeader.right += dx;
10393 hdi.mask = HDI_ORDER;
10394 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10396 /* not the rightmost one */
10397 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10399 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10400 hdi.iOrder + 1, 0);
10401 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10405 /* only needs to update the scrolls */
10406 infoPtr->nItemWidth += dx;
10407 LISTVIEW_UpdateScroll(infoPtr);
10409 LISTVIEW_UpdateItemSize(infoPtr);
10410 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10413 RECT rcCol = lpColumnInfo->rcHeader;
10415 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10416 OffsetRect(&rcCol, ptOrigin.x, 0);
10418 rcCol.top = infoPtr->rcList.top;
10419 rcCol.bottom = infoPtr->rcList.bottom;
10421 /* resizing left-aligned columns leaves most of the left side untouched */
10422 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10424 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10427 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10430 /* when shrinking the last column clear the now unused field */
10431 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10437 /* deal with right from rightmost column area */
10438 right.left = rcCol.right;
10439 right.top = rcCol.top;
10440 right.bottom = rcCol.bottom;
10441 right.right = infoPtr->rcList.right;
10443 LISTVIEW_InvalidateRect(infoPtr, &right);
10446 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10452 case HDN_ITEMCLICKW:
10453 case HDN_ITEMCLICKA:
10455 /* Handle sorting by Header Column */
10458 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10460 nmlv.iSubItem = lpnmh->iItem;
10461 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10462 return notify_forward_header(infoPtr, lpnmh);
10465 case HDN_DIVIDERDBLCLICKW:
10466 case HDN_DIVIDERDBLCLICKA:
10467 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10468 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10469 split needed for that */
10470 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10471 return notify_forward_header(infoPtr, lpnmh);
10478 * Paint non-client area of control.
10481 * [I] infoPtr : valid pointer to the listview structureof the sender
10482 * [I] region : update region
10485 * TRUE - frame was painted
10486 * FALSE - call default window proc
10488 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10490 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10494 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10495 cyEdge = GetSystemMetrics (SM_CYEDGE);
10498 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10500 GetWindowRect(infoPtr->hwndSelf, &r);
10502 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10503 r.right - cxEdge, r.bottom - cyEdge);
10504 if (region != (HRGN)1)
10505 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10506 OffsetRect(&r, -r.left, -r.top);
10508 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10509 OffsetRect(&r, -r.left, -r.top);
10511 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10512 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10513 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10514 ReleaseDC(infoPtr->hwndSelf, dc);
10516 /* Call default proc to get the scrollbars etc. painted */
10517 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10524 * Determines the type of structure to use.
10527 * [I] infoPtr : valid pointer to the listview structureof the sender
10528 * [I] hwndFrom : listview window handle
10529 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10534 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10536 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10538 if (nCommand == NF_REQUERY)
10539 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10541 return infoPtr->notifyFormat;
10546 * Paints/Repaints the listview control. Internal use.
10549 * [I] infoPtr : valid pointer to the listview structure
10550 * [I] hdc : device context handle
10555 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10557 TRACE("(hdc=%p)\n", hdc);
10559 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10561 infoPtr->bNoItemMetrics = FALSE;
10562 LISTVIEW_UpdateItemSize(infoPtr);
10563 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10564 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10565 LISTVIEW_UpdateScroll(infoPtr);
10568 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10571 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10576 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10577 if (!hdc) return 1;
10578 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10579 EndPaint(infoPtr->hwndSelf, &ps);
10587 * Paints/Repaints the listview control, WM_PAINT handler.
10590 * [I] infoPtr : valid pointer to the listview structure
10591 * [I] hdc : device context handle
10596 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10598 TRACE("(hdc=%p)\n", hdc);
10600 if (!is_redrawing(infoPtr))
10601 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10603 return LISTVIEW_Paint(infoPtr, hdc);
10608 * Paints/Repaints the listview control.
10611 * [I] infoPtr : valid pointer to the listview structure
10612 * [I] hdc : device context handle
10613 * [I] options : drawing options
10618 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10620 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10622 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10625 if (options & PRF_ERASEBKGND)
10626 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10628 if (options & PRF_CLIENT)
10629 LISTVIEW_Paint(infoPtr, hdc);
10637 * Processes double click messages (right mouse button).
10640 * [I] infoPtr : valid pointer to the listview structure
10641 * [I] wKey : key flag
10642 * [I] x,y : mouse coordinate
10647 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10649 LVHITTESTINFO lvHitTestInfo;
10651 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10653 /* send NM_RELEASEDCAPTURE notification */
10654 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10656 /* send NM_RDBLCLK notification */
10657 lvHitTestInfo.pt.x = x;
10658 lvHitTestInfo.pt.y = y;
10659 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10660 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10667 * Processes WM_RBUTTONDOWN message and corresponding drag operation.
10670 * [I] infoPtr : valid pointer to the listview structure
10671 * [I] wKey : key flag
10672 * [I] x, y : mouse coordinate
10677 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10682 TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
10684 /* send NM_RELEASEDCAPTURE notification */
10685 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10687 /* determine the index of the selected item */
10690 item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
10692 /* make sure the listview control window has the focus */
10693 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10695 if ((item >= 0) && (item < infoPtr->nItemCount))
10697 LISTVIEW_SetItemFocus(infoPtr, item);
10698 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10699 !LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
10700 LISTVIEW_SetSelection(infoPtr, item);
10703 LISTVIEW_DeselectAll(infoPtr);
10705 if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
10707 if (ht.iItem != -1)
10711 memset(&nmlv, 0, sizeof(nmlv));
10712 nmlv.iItem = ht.iItem;
10713 nmlv.ptAction = ht.pt;
10715 notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
10720 SetFocus(infoPtr->hwndSelf);
10724 LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
10726 if (notify_click(infoPtr, NM_RCLICK, &ht))
10728 /* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
10729 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10730 (WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
10742 * [I] infoPtr : valid pointer to the listview structure
10743 * [I] hwnd : window handle of window containing the cursor
10744 * [I] nHittest : hit-test code
10745 * [I] wMouseMsg : ideintifier of the mouse message
10748 * TRUE if cursor is set
10751 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10753 LVHITTESTINFO lvHitTestInfo;
10755 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10757 if (!infoPtr->hHotCursor) goto forward;
10759 GetCursorPos(&lvHitTestInfo.pt);
10760 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10762 SetCursor(infoPtr->hHotCursor);
10768 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10776 * [I] infoPtr : valid pointer to the listview structure
10777 * [I] hwndLoseFocus : handle of previously focused window
10782 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10784 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10786 /* if we have the focus already, there's nothing to do */
10787 if (infoPtr->bFocus) return 0;
10789 /* send NM_SETFOCUS notification */
10790 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10792 /* set window focus flag */
10793 infoPtr->bFocus = TRUE;
10795 /* put the focus rect back on */
10796 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10798 /* redraw all visible selected items */
10799 LISTVIEW_InvalidateSelectedItems(infoPtr);
10809 * [I] infoPtr : valid pointer to the listview structure
10810 * [I] fRedraw : font handle
10811 * [I] fRedraw : redraw flag
10816 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10818 HFONT oldFont = infoPtr->hFont;
10819 INT oldHeight = infoPtr->nItemHeight;
10821 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10823 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10824 if (infoPtr->hFont == oldFont) return 0;
10826 LISTVIEW_SaveTextMetrics(infoPtr);
10828 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10830 if (infoPtr->uView == LV_VIEW_DETAILS)
10832 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10833 LISTVIEW_UpdateSize(infoPtr);
10834 LISTVIEW_UpdateScroll(infoPtr);
10836 else if (infoPtr->nItemHeight != oldHeight)
10837 LISTVIEW_UpdateScroll(infoPtr);
10839 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10846 * Message handling for WM_SETREDRAW.
10847 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10850 * [I] infoPtr : valid pointer to the listview structure
10851 * [I] bRedraw: state of redraw flag
10854 * DefWinProc return value
10856 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
10858 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
10860 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
10861 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
10863 infoPtr->bRedraw = bRedraw;
10865 if(!bRedraw) return 0;
10867 if (is_autoarrange(infoPtr))
10868 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10869 LISTVIEW_UpdateScroll(infoPtr);
10871 /* despite what the WM_SETREDRAW docs says, apps expect us
10872 * to invalidate the listview here... stupid! */
10873 LISTVIEW_InvalidateList(infoPtr);
10880 * Resizes the listview control. This function processes WM_SIZE
10881 * messages. At this time, the width and height are not used.
10884 * [I] infoPtr : valid pointer to the listview structure
10885 * [I] Width : new width
10886 * [I] Height : new height
10891 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
10893 RECT rcOld = infoPtr->rcList;
10895 TRACE("(width=%d, height=%d)\n", Width, Height);
10897 LISTVIEW_UpdateSize(infoPtr);
10898 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
10900 /* do not bother with display related stuff if we're not redrawing */
10901 if (!is_redrawing(infoPtr)) return 0;
10903 if (is_autoarrange(infoPtr))
10904 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10906 LISTVIEW_UpdateScroll(infoPtr);
10908 /* refresh all only for lists whose height changed significantly */
10909 if ((infoPtr->uView == LV_VIEW_LIST) &&
10910 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
10911 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
10912 LISTVIEW_InvalidateList(infoPtr);
10919 * Sets the size information.
10922 * [I] infoPtr : valid pointer to the listview structure
10927 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
10929 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
10931 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
10933 if (infoPtr->uView == LV_VIEW_LIST)
10935 /* Apparently the "LIST" style is supposed to have the same
10936 * number of items in a column even if there is no scroll bar.
10937 * Since if a scroll bar already exists then the bottom is already
10938 * reduced, only reduce if the scroll bar does not currently exist.
10939 * The "2" is there to mimic the native control. I think it may be
10940 * related to either padding or edges. (GLA 7/2002)
10942 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
10943 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
10944 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
10947 /* if control created invisible header isn't created */
10948 if (infoPtr->hwndHeader)
10953 hl.prc = &infoPtr->rcList;
10955 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
10956 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
10958 if (LISTVIEW_IsHeaderEnabled(infoPtr))
10959 wp.flags |= SWP_SHOWWINDOW;
10962 wp.flags |= SWP_HIDEWINDOW;
10966 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
10967 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
10969 infoPtr->rcList.top = max(wp.cy, 0);
10971 /* extra padding for grid */
10972 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
10973 infoPtr->rcList.top += 2;
10975 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
10980 * Processes WM_STYLECHANGED messages.
10983 * [I] infoPtr : valid pointer to the listview structure
10984 * [I] wStyleType : window style type (normal or extended)
10985 * [I] lpss : window style information
10990 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
10991 const STYLESTRUCT *lpss)
10993 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
10994 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
10997 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
10998 wStyleType, lpss->styleOld, lpss->styleNew);
11000 if (wStyleType != GWL_STYLE) return 0;
11002 infoPtr->dwStyle = lpss->styleNew;
11003 map_style_view(infoPtr);
11005 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
11006 ((lpss->styleNew & WS_HSCROLL) == 0))
11007 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
11009 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
11010 ((lpss->styleNew & WS_VSCROLL) == 0))
11011 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
11013 if (uNewView != uOldView)
11015 SIZE oldIconSize = infoPtr->iconSize;
11018 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
11019 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
11021 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
11022 SetRectEmpty(&infoPtr->rcFocus);
11024 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
11025 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
11027 if (uNewView == LVS_ICON)
11029 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
11031 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
11032 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
11033 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
11036 else if (uNewView == LVS_REPORT)
11041 LISTVIEW_CreateHeader( infoPtr );
11043 hl.prc = &infoPtr->rcList;
11045 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11046 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
11047 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
11048 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
11051 LISTVIEW_UpdateItemSize(infoPtr);
11054 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
11056 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
11058 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
11060 /* Turn off the header control */
11061 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
11062 TRACE("Hide header control, was 0x%08x\n", style);
11063 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11065 /* Turn on the header control */
11066 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11068 TRACE("Show header control, was 0x%08x\n", style);
11069 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11075 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11076 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11077 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11079 /* update the size of the client area */
11080 LISTVIEW_UpdateSize(infoPtr);
11082 /* add scrollbars if needed */
11083 LISTVIEW_UpdateScroll(infoPtr);
11085 /* invalidate client area + erase background */
11086 LISTVIEW_InvalidateList(infoPtr);
11093 * Processes WM_STYLECHANGING messages.
11096 * [I] wStyleType : window style type (normal or extended)
11097 * [I0] lpss : window style information
11102 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11105 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11106 wStyleType, lpss->styleOld, lpss->styleNew);
11108 /* don't forward LVS_OWNERDATA only if not already set to */
11109 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11111 if (lpss->styleOld & LVS_OWNERDATA)
11112 lpss->styleNew |= LVS_OWNERDATA;
11114 lpss->styleNew &= ~LVS_OWNERDATA;
11122 * Processes WM_SHOWWINDOW messages.
11125 * [I] infoPtr : valid pointer to the listview structure
11126 * [I] bShown : window is being shown (FALSE when hidden)
11127 * [I] iStatus : window show status
11132 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11134 /* header delayed creation */
11135 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11137 LISTVIEW_CreateHeader(infoPtr);
11139 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11140 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11143 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11148 * Processes CCM_GETVERSION messages.
11151 * [I] infoPtr : valid pointer to the listview structure
11156 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11158 return infoPtr->iVersion;
11163 * Processes CCM_SETVERSION messages.
11166 * [I] infoPtr : valid pointer to the listview structure
11167 * [I] iVersion : version to be set
11170 * -1 when requested version is greater than DLL version;
11171 * previous version otherwise
11173 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11175 INT iOldVersion = infoPtr->iVersion;
11177 if (iVersion > COMCTL32_VERSION)
11180 infoPtr->iVersion = iVersion;
11182 TRACE("new version %d\n", iVersion);
11184 return iOldVersion;
11189 * Window procedure of the listview control.
11192 static LRESULT WINAPI
11193 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11195 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11197 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11199 if (!infoPtr && (uMsg != WM_NCCREATE))
11200 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11204 case LVM_APPROXIMATEVIEWRECT:
11205 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11206 LOWORD(lParam), HIWORD(lParam));
11208 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11210 case LVM_CANCELEDITLABEL:
11211 return LISTVIEW_CancelEditLabel(infoPtr);
11213 case LVM_CREATEDRAGIMAGE:
11214 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11216 case LVM_DELETEALLITEMS:
11217 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11219 case LVM_DELETECOLUMN:
11220 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11222 case LVM_DELETEITEM:
11223 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11225 case LVM_EDITLABELA:
11226 case LVM_EDITLABELW:
11227 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11228 uMsg == LVM_EDITLABELW);
11229 /* case LVM_ENABLEGROUPVIEW: */
11231 case LVM_ENSUREVISIBLE:
11232 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11234 case LVM_FINDITEMW:
11235 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11237 case LVM_FINDITEMA:
11238 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11240 case LVM_GETBKCOLOR:
11241 return infoPtr->clrBk;
11243 /* case LVM_GETBKIMAGE: */
11245 case LVM_GETCALLBACKMASK:
11246 return infoPtr->uCallbackMask;
11248 case LVM_GETCOLUMNA:
11249 case LVM_GETCOLUMNW:
11250 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11251 uMsg == LVM_GETCOLUMNW);
11253 case LVM_GETCOLUMNORDERARRAY:
11254 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11256 case LVM_GETCOLUMNWIDTH:
11257 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11259 case LVM_GETCOUNTPERPAGE:
11260 return LISTVIEW_GetCountPerPage(infoPtr);
11262 case LVM_GETEDITCONTROL:
11263 return (LRESULT)infoPtr->hwndEdit;
11265 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11266 return infoPtr->dwLvExStyle;
11268 /* case LVM_GETGROUPINFO: */
11270 /* case LVM_GETGROUPMETRICS: */
11272 case LVM_GETHEADER:
11273 return (LRESULT)infoPtr->hwndHeader;
11275 case LVM_GETHOTCURSOR:
11276 return (LRESULT)infoPtr->hHotCursor;
11278 case LVM_GETHOTITEM:
11279 return infoPtr->nHotItem;
11281 case LVM_GETHOVERTIME:
11282 return infoPtr->dwHoverTime;
11284 case LVM_GETIMAGELIST:
11285 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11287 /* case LVM_GETINSERTMARK: */
11289 /* case LVM_GETINSERTMARKCOLOR: */
11291 /* case LVM_GETINSERTMARKRECT: */
11293 case LVM_GETISEARCHSTRINGA:
11294 case LVM_GETISEARCHSTRINGW:
11295 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11300 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11302 case LVM_GETITEMCOUNT:
11303 return infoPtr->nItemCount;
11305 case LVM_GETITEMPOSITION:
11306 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11308 case LVM_GETITEMRECT:
11309 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11311 case LVM_GETITEMSPACING:
11312 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11314 case LVM_GETITEMSTATE:
11315 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11317 case LVM_GETITEMTEXTA:
11318 case LVM_GETITEMTEXTW:
11319 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11320 uMsg == LVM_GETITEMTEXTW);
11322 case LVM_GETNEXTITEM:
11323 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11325 case LVM_GETNUMBEROFWORKAREAS:
11326 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11329 case LVM_GETORIGIN:
11330 if (!lParam) return FALSE;
11331 if (infoPtr->uView == LV_VIEW_DETAILS ||
11332 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11333 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11336 /* case LVM_GETOUTLINECOLOR: */
11338 /* case LVM_GETSELECTEDCOLUMN: */
11340 case LVM_GETSELECTEDCOUNT:
11341 return LISTVIEW_GetSelectedCount(infoPtr);
11343 case LVM_GETSELECTIONMARK:
11344 return infoPtr->nSelectionMark;
11346 case LVM_GETSTRINGWIDTHA:
11347 case LVM_GETSTRINGWIDTHW:
11348 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11349 uMsg == LVM_GETSTRINGWIDTHW);
11351 case LVM_GETSUBITEMRECT:
11352 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11354 case LVM_GETTEXTBKCOLOR:
11355 return infoPtr->clrTextBk;
11357 case LVM_GETTEXTCOLOR:
11358 return infoPtr->clrText;
11360 /* case LVM_GETTILEINFO: */
11362 /* case LVM_GETTILEVIEWINFO: */
11364 case LVM_GETTOOLTIPS:
11365 if( !infoPtr->hwndToolTip )
11366 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11367 return (LRESULT)infoPtr->hwndToolTip;
11369 case LVM_GETTOPINDEX:
11370 return LISTVIEW_GetTopIndex(infoPtr);
11372 case LVM_GETUNICODEFORMAT:
11373 return (infoPtr->notifyFormat == NFR_UNICODE);
11376 return infoPtr->uView;
11378 case LVM_GETVIEWRECT:
11379 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11381 case LVM_GETWORKAREAS:
11382 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11385 /* case LVM_HASGROUP: */
11388 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11390 case LVM_INSERTCOLUMNA:
11391 case LVM_INSERTCOLUMNW:
11392 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11393 uMsg == LVM_INSERTCOLUMNW);
11395 /* case LVM_INSERTGROUP: */
11397 /* case LVM_INSERTGROUPSORTED: */
11399 case LVM_INSERTITEMA:
11400 case LVM_INSERTITEMW:
11401 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11403 /* case LVM_INSERTMARKHITTEST: */
11405 /* case LVM_ISGROUPVIEWENABLED: */
11407 case LVM_ISITEMVISIBLE:
11408 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11410 case LVM_MAPIDTOINDEX:
11411 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11413 case LVM_MAPINDEXTOID:
11414 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11416 /* case LVM_MOVEGROUP: */
11418 /* case LVM_MOVEITEMTOGROUP: */
11420 case LVM_REDRAWITEMS:
11421 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11423 /* case LVM_REMOVEALLGROUPS: */
11425 /* case LVM_REMOVEGROUP: */
11428 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11430 case LVM_SETBKCOLOR:
11431 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11433 /* case LVM_SETBKIMAGE: */
11435 case LVM_SETCALLBACKMASK:
11436 infoPtr->uCallbackMask = (UINT)wParam;
11439 case LVM_SETCOLUMNA:
11440 case LVM_SETCOLUMNW:
11441 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11442 uMsg == LVM_SETCOLUMNW);
11444 case LVM_SETCOLUMNORDERARRAY:
11445 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11447 case LVM_SETCOLUMNWIDTH:
11448 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11450 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11451 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11453 /* case LVM_SETGROUPINFO: */
11455 /* case LVM_SETGROUPMETRICS: */
11457 case LVM_SETHOTCURSOR:
11458 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11460 case LVM_SETHOTITEM:
11461 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11463 case LVM_SETHOVERTIME:
11464 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11466 case LVM_SETICONSPACING:
11467 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
11469 case LVM_SETIMAGELIST:
11470 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11472 /* case LVM_SETINFOTIP: */
11474 /* case LVM_SETINSERTMARK: */
11476 /* case LVM_SETINSERTMARKCOLOR: */
11481 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11482 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11485 case LVM_SETITEMCOUNT:
11486 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11488 case LVM_SETITEMPOSITION:
11491 pt.x = (short)LOWORD(lParam);
11492 pt.y = (short)HIWORD(lParam);
11493 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11496 case LVM_SETITEMPOSITION32:
11497 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11499 case LVM_SETITEMSTATE:
11500 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11502 case LVM_SETITEMTEXTA:
11503 case LVM_SETITEMTEXTW:
11504 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11505 uMsg == LVM_SETITEMTEXTW);
11507 /* case LVM_SETOUTLINECOLOR: */
11509 /* case LVM_SETSELECTEDCOLUMN: */
11511 case LVM_SETSELECTIONMARK:
11512 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11514 case LVM_SETTEXTBKCOLOR:
11515 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11517 case LVM_SETTEXTCOLOR:
11518 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11520 /* case LVM_SETTILEINFO: */
11522 /* case LVM_SETTILEVIEWINFO: */
11524 /* case LVM_SETTILEWIDTH: */
11526 case LVM_SETTOOLTIPS:
11527 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11529 case LVM_SETUNICODEFORMAT:
11530 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11533 return LISTVIEW_SetView(infoPtr, wParam);
11535 /* case LVM_SETWORKAREAS: */
11537 /* case LVM_SORTGROUPS: */
11539 case LVM_SORTITEMS:
11540 case LVM_SORTITEMSEX:
11541 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11542 uMsg == LVM_SORTITEMSEX);
11543 case LVM_SUBITEMHITTEST:
11544 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11547 return LISTVIEW_Update(infoPtr, (INT)wParam);
11549 case CCM_GETVERSION:
11550 return LISTVIEW_GetVersion(infoPtr);
11552 case CCM_SETVERSION:
11553 return LISTVIEW_SetVersion(infoPtr, wParam);
11556 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11559 return LISTVIEW_Command(infoPtr, wParam, lParam);
11562 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11565 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11568 return LISTVIEW_Destroy(infoPtr);
11571 return LISTVIEW_Enable(infoPtr);
11573 case WM_ERASEBKGND:
11574 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11576 case WM_GETDLGCODE:
11577 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11580 return (LRESULT)infoPtr->hFont;
11583 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11586 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11589 return LISTVIEW_KillFocus(infoPtr);
11591 case WM_LBUTTONDBLCLK:
11592 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11594 case WM_LBUTTONDOWN:
11595 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11598 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11601 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11603 case WM_MOUSEHOVER:
11604 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11607 return LISTVIEW_NCDestroy(infoPtr);
11610 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11613 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11615 case WM_NOTIFYFORMAT:
11616 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11618 case WM_PRINTCLIENT:
11619 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11622 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11624 case WM_RBUTTONDBLCLK:
11625 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11627 case WM_RBUTTONDOWN:
11628 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11631 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11634 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11637 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11640 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11642 case WM_SHOWWINDOW:
11643 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11645 case WM_STYLECHANGED:
11646 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11648 case WM_STYLECHANGING:
11649 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11651 case WM_SYSCOLORCHANGE:
11652 COMCTL32_RefreshSysColors();
11655 /* case WM_TIMER: */
11656 case WM_THEMECHANGED:
11657 return LISTVIEW_ThemeChanged(infoPtr);
11660 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11662 case WM_MOUSEWHEEL:
11663 if (wParam & (MK_SHIFT | MK_CONTROL))
11664 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11665 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11667 case WM_WINDOWPOSCHANGED:
11668 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11670 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11671 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11673 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11675 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11677 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11679 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11681 /* case WM_WININICHANGE: */
11684 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11685 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11687 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11694 * Registers the window class.
11702 void LISTVIEW_Register(void)
11704 WNDCLASSW wndClass;
11706 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11707 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11708 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11709 wndClass.cbClsExtra = 0;
11710 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11711 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11712 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11713 wndClass.lpszClassName = WC_LISTVIEWW;
11714 RegisterClassW(&wndClass);
11719 * Unregisters the window class.
11727 void LISTVIEW_Unregister(void)
11729 UnregisterClassW(WC_LISTVIEWW, NULL);
11734 * Handle any WM_COMMAND messages
11737 * [I] infoPtr : valid pointer to the listview structure
11738 * [I] wParam : the first message parameter
11739 * [I] lParam : the second message parameter
11744 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11747 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11749 if (!infoPtr->hwndEdit) return 0;
11751 switch (HIWORD(wParam))
11756 * Adjust the edit window size
11758 WCHAR buffer[1024];
11759 HDC hdc = GetDC(infoPtr->hwndEdit);
11760 HFONT hFont, hOldFont = 0;
11764 if (!infoPtr->hwndEdit || !hdc) return 0;
11765 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11766 GetWindowRect(infoPtr->hwndEdit, &rect);
11768 /* Select font to get the right dimension of the string */
11769 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11772 hOldFont = SelectObject(hdc, hFont);
11775 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11777 TEXTMETRICW textMetric;
11779 /* Add Extra spacing for the next character */
11780 GetTextMetricsW(hdc, &textMetric);
11781 sz.cx += (textMetric.tmMaxCharWidth * 2);
11783 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11784 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11787 SelectObject(hdc, hOldFont);
11789 ReleaseDC(infoPtr->hwndEdit, hdc);
11795 LISTVIEW_CancelEditLabel(infoPtr);
11800 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);