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-2015 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
11 * Copyright 2012-2013 Daniel Jelinski
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
29 * This code was audited for completeness against the documented features
30 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
32 * Unless otherwise noted, we believe this code to be complete, as per
33 * the specification mentioned above.
34 * If you discover missing features, or bugs, please note them below.
38 * Default Message Processing
39 * -- WM_CREATE: create the icon and small icon image lists at this point only if
40 * the LVS_SHAREIMAGELISTS style is not specified.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode.
67 * This would allow framing items (iterator_frameditems),
68 * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_EX_BORDERSELECT
88 * -- LVS_EX_MULTIWORKAREAS
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
102 * -- LVM_ENABLEGROUPVIEW
103 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
104 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
105 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
106 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
107 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
108 * -- LVM_GETINSERTMARKRECT
109 * -- LVM_GETNUMBEROFWORKAREAS
110 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
111 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
112 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
113 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
114 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
115 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
116 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
117 * -- LVM_INSERTGROUPSORTED
118 * -- LVM_INSERTMARKHITTEST
119 * -- LVM_ISGROUPVIEWENABLED
121 * -- LVM_MOVEITEMTOGROUP
123 * -- LVM_SETTILEWIDTH
127 * -- ListView_GetHoverTime, ListView_SetHoverTime
128 * -- ListView_GetISearchString
129 * -- ListView_GetNumberOfWorkAreas
130 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
136 #include "comctl32.h"
140 WINE_DEFAULT_DEBUG_CHANNEL(listview
);
142 typedef struct tagCOLUMN_INFO
144 RECT rcHeader
; /* tracks the header's rectangle */
145 INT fmt
; /* same as LVCOLUMN.fmt */
149 typedef struct tagITEMHDR
153 } ITEMHDR
, *LPITEMHDR
;
155 typedef struct tagSUBITEM_INFO
161 typedef struct tagITEM_ID ITEM_ID
;
163 typedef struct tagITEM_INFO
174 UINT id
; /* item id */
175 HDPA item
; /* link to item data */
178 typedef struct tagRANGE
184 typedef struct tagRANGES
189 typedef struct tagITERATOR
198 typedef struct tagDELAYED_ITEM_EDIT
204 typedef struct tagLISTVIEW_INFO
208 RECT rcList
; /* This rectangle is really the window
209 * client rectangle possibly reduced by the
210 * horizontal scroll bar and/or header - see
211 * LISTVIEW_UpdateSize. This rectangle offset
212 * by the LISTVIEW_GetOrigin value is in
213 * client coordinates */
215 /* notification window */
218 BOOL bDoChangeNotify
; /* send change notification messages? */
225 INT nItemCount
; /* the number of items in the list */
226 HDPA hdpaItems
; /* array ITEM_INFO pointers */
227 HDPA hdpaItemIds
; /* array of ITEM_ID pointers */
228 HDPA hdpaPosX
; /* maintains the (X, Y) coordinates of the */
229 HDPA hdpaPosY
; /* items in LVS_ICON, and LVS_SMALLICON modes */
230 RANGES selectionRanges
;
231 INT nSelectionMark
; /* item to start next multiselection from */
233 BOOL bAutoarrange
; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
236 HDPA hdpaColumns
; /* array of COLUMN_INFO pointers */
237 BOOL colRectsDirty
; /* trigger column rectangles requery from header */
240 BOOL bNoItemMetrics
; /* flags if item metrics are not yet computed */
245 PFNLVCOMPARE pfnCompare
; /* sorting callback pointer */
249 DWORD dwStyle
; /* the cached window GWL_STYLE */
250 DWORD dwLvExStyle
; /* extended listview style */
251 DWORD uView
; /* current view available through LVM_[G,S]ETVIEW */
257 DELAYED_ITEM_EDIT itemEdit
; /* Pointer to this structure will be the timer ID */
260 HIMAGELIST himlNormal
;
261 HIMAGELIST himlSmall
;
262 HIMAGELIST himlState
;
267 POINT currIconPos
; /* this is the position next icon will be placed */
271 INT xTrackLine
; /* The x coefficient of the track line or -1 if none */
273 /* marquee selection */
274 BOOL bMarqueeSelect
; /* marquee selection/highlight underway */
276 RECT marqueeRect
; /* absolute coordinates of marquee selection */
277 RECT marqueeDrawRect
; /* relative coordinates for drawing marquee */
278 POINT marqueeOrigin
; /* absolute coordinates of marquee click origin */
281 BOOL bFocus
; /* control has focus */
283 RECT rcFocus
; /* focus bounds */
291 BOOL bDefaultBkColor
;
297 INT ntmHeight
; /* Some cached metrics of the font used */
298 INT ntmMaxCharWidth
; /* by the listview to draw items */
301 /* mouse operation */
304 POINT ptClickPos
; /* point where the user clicked */
305 INT nLButtonDownItem
; /* tracks item to reset multiselection on WM_LBUTTONUP */
310 /* keyboard operation */
311 DWORD lastKeyPressTimestamp
;
313 INT nSearchParamLength
;
314 WCHAR szSearchParam
[ MAX_PATH
];
317 BOOL bIsDrawing
; /* Drawing in progress */
318 INT nMeasureItemHeight
; /* WM_MEASUREITEM result */
319 BOOL bRedraw
; /* WM_SETREDRAW switch */
322 DWORD iVersion
; /* CCM_[G,S]ETVERSION */
328 /* How many we debug buffer to allocate */
329 #define DEBUG_BUFFERS 20
330 /* The size of a single debug buffer */
331 #define DEBUG_BUFFER_SIZE 256
333 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
334 #define SB_INTERNAL -1
336 /* maximum size of a label */
337 #define DISP_TEXT_SIZE 260
339 /* padding for items in list and small icon display modes */
340 #define WIDTH_PADDING 12
342 /* padding for items in list, report and small icon display modes */
343 #define HEIGHT_PADDING 1
345 /* offset of items in report display mode */
346 #define REPORT_MARGINX 2
348 /* padding for icon in large icon display mode
349 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
350 * that HITTEST will see.
351 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
352 * ICON_TOP_PADDING - sum of the two above.
353 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
354 * LABEL_HOR_PADDING - between text and sides of box
355 * LABEL_VERT_PADDING - between bottom of text and end of box
357 * ICON_LR_PADDING - additional width above icon size.
358 * ICON_LR_HALF - half of the above value
360 #define ICON_TOP_PADDING_NOTHITABLE 2
361 #define ICON_TOP_PADDING_HITABLE 2
362 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
363 #define ICON_BOTTOM_PADDING 4
364 #define LABEL_HOR_PADDING 5
365 #define LABEL_VERT_PADDING 7
366 #define ICON_LR_PADDING 16
367 #define ICON_LR_HALF (ICON_LR_PADDING/2)
369 /* default label width for items in list and small icon display modes */
370 #define DEFAULT_LABEL_WIDTH 40
371 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
372 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
374 /* default column width for items in list display mode */
375 #define DEFAULT_COLUMN_WIDTH 128
377 /* Size of "line" scroll for V & H scrolls */
378 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
380 /* Padding between image and label */
381 #define IMAGE_PADDING 2
383 /* Padding behind the label */
384 #define TRAILING_LABEL_PADDING 12
385 #define TRAILING_HEADER_PADDING 11
387 /* Border for the icon caption */
388 #define CAPTION_BORDER 2
390 /* Standard DrawText flags */
391 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
392 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
393 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
395 /* Image index from state */
396 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
398 /* The time in milliseconds to reset the search in the list */
399 #define KEY_DELAY 450
401 /* Dump the LISTVIEW_INFO structure to the debug channel */
402 #define LISTVIEW_DUMP(iP) do { \
403 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
404 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
405 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
406 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
407 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
408 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
409 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
410 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
411 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
412 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
415 static const WCHAR themeClass
[] = {'L','i','s','t','V','i','e','w',0};
418 * forward declarations
420 static BOOL
LISTVIEW_GetItemT(const LISTVIEW_INFO
*, LPLVITEMW
, BOOL
);
421 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO
*, INT
, LPRECT
);
422 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO
*, INT
, LPPOINT
);
423 static BOOL
LISTVIEW_GetItemPosition(const LISTVIEW_INFO
*, INT
, LPPOINT
);
424 static BOOL
LISTVIEW_GetItemRect(const LISTVIEW_INFO
*, INT
, LPRECT
);
425 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO
*, LPPOINT
);
426 static BOOL
LISTVIEW_GetViewRect(const LISTVIEW_INFO
*, LPRECT
);
427 static void LISTVIEW_UpdateSize(LISTVIEW_INFO
*);
428 static LRESULT
LISTVIEW_Command(LISTVIEW_INFO
*, WPARAM
, LPARAM
);
429 static INT
LISTVIEW_GetStringWidthT(const LISTVIEW_INFO
*, LPCWSTR
, BOOL
);
430 static BOOL
LISTVIEW_KeySelection(LISTVIEW_INFO
*, INT
, BOOL
);
431 static UINT
LISTVIEW_GetItemState(const LISTVIEW_INFO
*, INT
, UINT
);
432 static BOOL
LISTVIEW_SetItemState(LISTVIEW_INFO
*, INT
, const LVITEMW
*);
433 static LRESULT
LISTVIEW_VScroll(LISTVIEW_INFO
*, INT
, INT
);
434 static LRESULT
LISTVIEW_HScroll(LISTVIEW_INFO
*, INT
, INT
);
435 static BOOL
LISTVIEW_EnsureVisible(LISTVIEW_INFO
*, INT
, BOOL
);
436 static HIMAGELIST
LISTVIEW_SetImageList(LISTVIEW_INFO
*, INT
, HIMAGELIST
);
437 static INT
LISTVIEW_HitTest(const LISTVIEW_INFO
*, LPLVHITTESTINFO
, BOOL
, BOOL
);
438 static BOOL
LISTVIEW_EndEditLabelT(LISTVIEW_INFO
*, BOOL
, BOOL
);
439 static BOOL
LISTVIEW_Scroll(LISTVIEW_INFO
*, INT
, INT
);
441 /******** Text handling functions *************************************/
443 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
444 * text string. The string may be ANSI or Unicode, in which case
445 * the boolean isW tells us the type of the string.
447 * The name of the function tell what type of strings it expects:
448 * W: Unicode, T: ANSI/Unicode - function of isW
451 static inline BOOL
is_text(LPCWSTR text
)
453 return text
!= NULL
&& text
!= LPSTR_TEXTCALLBACKW
;
456 static inline int textlenT(LPCWSTR text
, BOOL isW
)
458 return !is_text(text
) ? 0 :
459 isW
? lstrlenW(text
) : lstrlenA((LPCSTR
)text
);
462 static inline void textcpynT(LPWSTR dest
, BOOL isDestW
, LPCWSTR src
, BOOL isSrcW
, INT max
)
465 if (isSrcW
) lstrcpynW(dest
, src
, max
);
466 else MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)src
, -1, dest
, max
);
468 if (isSrcW
) WideCharToMultiByte(CP_ACP
, 0, src
, -1, (LPSTR
)dest
, max
, NULL
, NULL
);
469 else lstrcpynA((LPSTR
)dest
, (LPCSTR
)src
, max
);
472 static inline LPWSTR
textdupTtoW(LPCWSTR text
, BOOL isW
)
474 LPWSTR wstr
= (LPWSTR
)text
;
476 if (!isW
&& is_text(text
))
478 INT len
= MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)text
, -1, NULL
, 0);
479 wstr
= Alloc(len
* sizeof(WCHAR
));
480 if (wstr
) MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)text
, -1, wstr
, len
);
482 TRACE(" wstr=%s\n", text
== LPSTR_TEXTCALLBACKW
? "(callback)" : debugstr_w(wstr
));
486 static inline void textfreeT(LPWSTR wstr
, BOOL isW
)
488 if (!isW
&& is_text(wstr
)) Free (wstr
);
492 * dest is a pointer to a Unicode string
493 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
495 static BOOL
textsetptrT(LPWSTR
*dest
, LPCWSTR src
, BOOL isW
)
499 if (src
== LPSTR_TEXTCALLBACKW
)
501 if (is_text(*dest
)) Free(*dest
);
502 *dest
= LPSTR_TEXTCALLBACKW
;
506 LPWSTR pszText
= textdupTtoW(src
, isW
);
507 if (*dest
== LPSTR_TEXTCALLBACKW
) *dest
= NULL
;
508 bResult
= Str_SetPtrW(dest
, pszText
);
509 textfreeT(pszText
, isW
);
515 * compares a Unicode to a Unicode/ANSI text string
517 static inline int textcmpWT(LPCWSTR aw
, LPCWSTR bt
, BOOL isW
)
519 if (!aw
) return bt
? -1 : 0;
521 if (aw
== LPSTR_TEXTCALLBACKW
)
522 return bt
== LPSTR_TEXTCALLBACKW
? 1 : -1;
523 if (bt
!= LPSTR_TEXTCALLBACKW
)
525 LPWSTR bw
= textdupTtoW(bt
, isW
);
526 int r
= bw
? lstrcmpW(aw
, bw
) : 1;
534 static inline int lstrncmpiW(LPCWSTR s1
, LPCWSTR s2
, int n
)
536 n
= min(min(n
, lstrlenW(s1
)), lstrlenW(s2
));
537 return CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
, s1
, n
, s2
, n
) - CSTR_EQUAL
;
540 /******** Debugging functions *****************************************/
542 static inline LPCSTR
debugtext_t(LPCWSTR text
, BOOL isW
)
544 if (text
== LPSTR_TEXTCALLBACKW
) return "(callback)";
545 return isW
? debugstr_w(text
) : debugstr_a((LPCSTR
)text
);
548 static inline LPCSTR
debugtext_tn(LPCWSTR text
, BOOL isW
, INT n
)
550 if (text
== LPSTR_TEXTCALLBACKW
) return "(callback)";
551 n
= min(textlenT(text
, isW
), n
);
552 return isW
? debugstr_wn(text
, n
) : debugstr_an((LPCSTR
)text
, n
);
555 static char* debug_getbuf(void)
557 static int index
= 0;
558 static char buffers
[DEBUG_BUFFERS
][DEBUG_BUFFER_SIZE
];
559 return buffers
[index
++ % DEBUG_BUFFERS
];
562 static inline const char* debugrange(const RANGE
*lprng
)
564 if (!lprng
) return "(null)";
565 return wine_dbg_sprintf("[%d, %d]", lprng
->lower
, lprng
->upper
);
568 static const char* debugscrollinfo(const SCROLLINFO
*pScrollInfo
)
570 char* buf
= debug_getbuf(), *text
= buf
;
571 int len
, size
= DEBUG_BUFFER_SIZE
;
573 if (pScrollInfo
== NULL
) return "(null)";
574 len
= snprintf(buf
, size
, "{cbSize=%u, ", pScrollInfo
->cbSize
);
575 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
576 if (pScrollInfo
->fMask
& SIF_RANGE
)
577 len
= snprintf(buf
, size
, "nMin=%d, nMax=%d, ", pScrollInfo
->nMin
, pScrollInfo
->nMax
);
579 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
580 if (pScrollInfo
->fMask
& SIF_PAGE
)
581 len
= snprintf(buf
, size
, "nPage=%u, ", pScrollInfo
->nPage
);
583 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
584 if (pScrollInfo
->fMask
& SIF_POS
)
585 len
= snprintf(buf
, size
, "nPos=%d, ", pScrollInfo
->nPos
);
587 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
588 if (pScrollInfo
->fMask
& SIF_TRACKPOS
)
589 len
= snprintf(buf
, size
, "nTrackPos=%d, ", pScrollInfo
->nTrackPos
);
591 if (len
== -1) goto end
; buf
+= len
;
594 buf
= text
+ strlen(text
);
596 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
600 static const char* debugnmlistview(const NMLISTVIEW
*plvnm
)
602 if (!plvnm
) return "(null)";
603 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
604 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
605 plvnm
->iItem
, plvnm
->iSubItem
, plvnm
->uNewState
, plvnm
->uOldState
,
606 plvnm
->uChanged
, wine_dbgstr_point(&plvnm
->ptAction
), plvnm
->lParam
);
609 static const char* debuglvitem_t(const LVITEMW
*lpLVItem
, BOOL isW
)
611 char* buf
= debug_getbuf(), *text
= buf
;
612 int len
, size
= DEBUG_BUFFER_SIZE
;
614 if (lpLVItem
== NULL
) return "(null)";
615 len
= snprintf(buf
, size
, "{iItem=%d, iSubItem=%d, ", lpLVItem
->iItem
, lpLVItem
->iSubItem
);
616 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
617 if (lpLVItem
->mask
& LVIF_STATE
)
618 len
= snprintf(buf
, size
, "state=%x, stateMask=%x, ", lpLVItem
->state
, lpLVItem
->stateMask
);
620 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
621 if (lpLVItem
->mask
& LVIF_TEXT
)
622 len
= snprintf(buf
, size
, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem
->pszText
, isW
, 80), lpLVItem
->cchTextMax
);
624 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
625 if (lpLVItem
->mask
& LVIF_IMAGE
)
626 len
= snprintf(buf
, size
, "iImage=%d, ", lpLVItem
->iImage
);
628 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
629 if (lpLVItem
->mask
& LVIF_PARAM
)
630 len
= snprintf(buf
, size
, "lParam=%lx, ", lpLVItem
->lParam
);
632 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
633 if (lpLVItem
->mask
& LVIF_INDENT
)
634 len
= snprintf(buf
, size
, "iIndent=%d, ", lpLVItem
->iIndent
);
636 if (len
== -1) goto end
; buf
+= len
;
639 buf
= text
+ strlen(text
);
641 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
645 static const char* debuglvcolumn_t(const LVCOLUMNW
*lpColumn
, BOOL isW
)
647 char* buf
= debug_getbuf(), *text
= buf
;
648 int len
, size
= DEBUG_BUFFER_SIZE
;
650 if (lpColumn
== NULL
) return "(null)";
651 len
= snprintf(buf
, size
, "{");
652 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
653 if (lpColumn
->mask
& LVCF_SUBITEM
)
654 len
= snprintf(buf
, size
, "iSubItem=%d, ", lpColumn
->iSubItem
);
656 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
657 if (lpColumn
->mask
& LVCF_FMT
)
658 len
= snprintf(buf
, size
, "fmt=%x, ", lpColumn
->fmt
);
660 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
661 if (lpColumn
->mask
& LVCF_WIDTH
)
662 len
= snprintf(buf
, size
, "cx=%d, ", lpColumn
->cx
);
664 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
665 if (lpColumn
->mask
& LVCF_TEXT
)
666 len
= snprintf(buf
, size
, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn
->pszText
, isW
, 80), lpColumn
->cchTextMax
);
668 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
669 if (lpColumn
->mask
& LVCF_IMAGE
)
670 len
= snprintf(buf
, size
, "iImage=%d, ", lpColumn
->iImage
);
672 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
673 if (lpColumn
->mask
& LVCF_ORDER
)
674 len
= snprintf(buf
, size
, "iOrder=%d, ", lpColumn
->iOrder
);
676 if (len
== -1) goto end
; buf
+= len
;
679 buf
= text
+ strlen(text
);
681 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
685 static const char* debuglvhittestinfo(const LVHITTESTINFO
*lpht
)
687 if (!lpht
) return "(null)";
689 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
690 wine_dbgstr_point(&lpht
->pt
), lpht
->flags
, lpht
->iItem
, lpht
->iSubItem
);
693 /* Return the corresponding text for a given scroll value */
694 static inline LPCSTR
debugscrollcode(int nScrollCode
)
698 case SB_LINELEFT
: return "SB_LINELEFT";
699 case SB_LINERIGHT
: return "SB_LINERIGHT";
700 case SB_PAGELEFT
: return "SB_PAGELEFT";
701 case SB_PAGERIGHT
: return "SB_PAGERIGHT";
702 case SB_THUMBPOSITION
: return "SB_THUMBPOSITION";
703 case SB_THUMBTRACK
: return "SB_THUMBTRACK";
704 case SB_ENDSCROLL
: return "SB_ENDSCROLL";
705 case SB_INTERNAL
: return "SB_INTERNAL";
706 default: return "unknown";
711 /******** Notification functions ************************************/
713 static int get_ansi_notification(UINT unicodeNotificationCode
)
715 switch (unicodeNotificationCode
)
717 case LVN_BEGINLABELEDITA
:
718 case LVN_BEGINLABELEDITW
: return LVN_BEGINLABELEDITA
;
719 case LVN_ENDLABELEDITA
:
720 case LVN_ENDLABELEDITW
: return LVN_ENDLABELEDITA
;
721 case LVN_GETDISPINFOA
:
722 case LVN_GETDISPINFOW
: return LVN_GETDISPINFOA
;
723 case LVN_SETDISPINFOA
:
724 case LVN_SETDISPINFOW
: return LVN_SETDISPINFOA
;
725 case LVN_ODFINDITEMA
:
726 case LVN_ODFINDITEMW
: return LVN_ODFINDITEMA
;
727 case LVN_GETINFOTIPA
:
728 case LVN_GETINFOTIPW
: return LVN_GETINFOTIPA
;
729 /* header forwards */
731 case HDN_TRACKW
: return HDN_TRACKA
;
733 case HDN_ENDTRACKW
: return HDN_ENDTRACKA
;
734 case HDN_BEGINDRAG
: return HDN_BEGINDRAG
;
735 case HDN_ENDDRAG
: return HDN_ENDDRAG
;
736 case HDN_ITEMCHANGINGA
:
737 case HDN_ITEMCHANGINGW
: return HDN_ITEMCHANGINGA
;
738 case HDN_ITEMCHANGEDA
:
739 case HDN_ITEMCHANGEDW
: return HDN_ITEMCHANGEDA
;
741 case HDN_ITEMCLICKW
: return HDN_ITEMCLICKA
;
742 case HDN_DIVIDERDBLCLICKA
:
743 case HDN_DIVIDERDBLCLICKW
: return HDN_DIVIDERDBLCLICKA
;
746 FIXME("unknown notification %x\n", unicodeNotificationCode
);
747 return unicodeNotificationCode
;
750 /* forwards header notifications to listview parent */
751 static LRESULT
notify_forward_header(const LISTVIEW_INFO
*infoPtr
, NMHEADERW
*lpnmhW
)
753 LPCWSTR text
= NULL
, filter
= NULL
;
755 NMHEADERA
*lpnmh
= (NMHEADERA
*) lpnmhW
;
757 /* on unicode format exit earlier */
758 if (infoPtr
->notifyFormat
== NFR_UNICODE
)
759 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, lpnmh
->hdr
.idFrom
,
762 /* header always supplies unicode notifications,
763 all we have to do is to convert strings to ANSI */
766 /* convert item text */
767 if (lpnmh
->pitem
->mask
& HDI_TEXT
)
769 text
= (LPCWSTR
)lpnmh
->pitem
->pszText
;
770 lpnmh
->pitem
->pszText
= NULL
;
771 Str_SetPtrWtoA(&lpnmh
->pitem
->pszText
, text
);
773 /* convert filter text */
774 if ((lpnmh
->pitem
->mask
& HDI_FILTER
) && (lpnmh
->pitem
->type
== HDFT_ISSTRING
) &&
775 lpnmh
->pitem
->pvFilter
)
777 filter
= (LPCWSTR
)((HD_TEXTFILTERA
*)lpnmh
->pitem
->pvFilter
)->pszText
;
778 ((HD_TEXTFILTERA
*)lpnmh
->pitem
->pvFilter
)->pszText
= NULL
;
779 Str_SetPtrWtoA(&((HD_TEXTFILTERA
*)lpnmh
->pitem
->pvFilter
)->pszText
, filter
);
782 lpnmh
->hdr
.code
= get_ansi_notification(lpnmh
->hdr
.code
);
784 ret
= SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, lpnmh
->hdr
.idFrom
,
790 Free(lpnmh
->pitem
->pszText
);
791 lpnmh
->pitem
->pszText
= (LPSTR
)text
;
795 Free(((HD_TEXTFILTERA
*)lpnmh
->pitem
->pvFilter
)->pszText
);
796 ((HD_TEXTFILTERA
*)lpnmh
->pitem
->pvFilter
)->pszText
= (LPSTR
)filter
;
802 static LRESULT
notify_hdr(const LISTVIEW_INFO
*infoPtr
, INT code
, LPNMHDR pnmh
)
806 TRACE("(code=%d)\n", code
);
808 pnmh
->hwndFrom
= infoPtr
->hwndSelf
;
809 pnmh
->idFrom
= GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
811 result
= SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, pnmh
->idFrom
, (LPARAM
)pnmh
);
813 TRACE(" <= %ld\n", result
);
818 static inline BOOL
notify(const LISTVIEW_INFO
*infoPtr
, INT code
)
821 HWND hwnd
= infoPtr
->hwndSelf
;
822 notify_hdr(infoPtr
, code
, &nmh
);
823 return IsWindow(hwnd
);
826 static inline void notify_itemactivate(const LISTVIEW_INFO
*infoPtr
, const LVHITTESTINFO
*htInfo
)
837 item
.mask
= LVIF_PARAM
|LVIF_STATE
;
838 item
.iItem
= htInfo
->iItem
;
840 item
.stateMask
= (UINT
)-1;
841 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) {
842 nmia
.lParam
= item
.lParam
;
843 nmia
.uOldState
= item
.state
;
844 nmia
.uNewState
= item
.state
| LVIS_ACTIVATING
;
845 nmia
.uChanged
= LVIF_STATE
;
848 nmia
.iItem
= htInfo
->iItem
;
849 nmia
.iSubItem
= htInfo
->iSubItem
;
850 nmia
.ptAction
= htInfo
->pt
;
852 if (GetKeyState(VK_SHIFT
) & 0x8000) nmia
.uKeyFlags
|= LVKF_SHIFT
;
853 if (GetKeyState(VK_CONTROL
) & 0x8000) nmia
.uKeyFlags
|= LVKF_CONTROL
;
854 if (GetKeyState(VK_MENU
) & 0x8000) nmia
.uKeyFlags
|= LVKF_ALT
;
856 notify_hdr(infoPtr
, LVN_ITEMACTIVATE
, (LPNMHDR
)&nmia
);
859 static inline LRESULT
notify_listview(const LISTVIEW_INFO
*infoPtr
, INT code
, LPNMLISTVIEW plvnm
)
861 TRACE("(code=%d, plvnm=%s)\n", code
, debugnmlistview(plvnm
));
862 return notify_hdr(infoPtr
, code
, (LPNMHDR
)plvnm
);
865 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
866 static BOOL
notify_click(const LISTVIEW_INFO
*infoPtr
, INT code
, const LVHITTESTINFO
*lvht
)
870 HWND hwnd
= infoPtr
->hwndSelf
;
873 TRACE("code=%d, lvht=%s\n", code
, debuglvhittestinfo(lvht
));
874 ZeroMemory(&nmia
, sizeof(nmia
));
875 nmia
.iItem
= lvht
->iItem
;
876 nmia
.iSubItem
= lvht
->iSubItem
;
877 nmia
.ptAction
= lvht
->pt
;
878 item
.mask
= LVIF_PARAM
;
879 item
.iItem
= lvht
->iItem
;
881 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) nmia
.lParam
= item
.lParam
;
882 ret
= notify_hdr(infoPtr
, code
, (NMHDR
*)&nmia
);
883 return IsWindow(hwnd
) && (code
== NM_RCLICK
? !ret
: TRUE
);
886 static BOOL
notify_deleteitem(const LISTVIEW_INFO
*infoPtr
, INT nItem
)
890 HWND hwnd
= infoPtr
->hwndSelf
;
892 ZeroMemory(&nmlv
, sizeof (NMLISTVIEW
));
894 item
.mask
= LVIF_PARAM
;
897 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) nmlv
.lParam
= item
.lParam
;
898 notify_listview(infoPtr
, LVN_DELETEITEM
, &nmlv
);
899 return IsWindow(hwnd
);
903 Send notification. depends on dispinfoW having same
904 structure as dispinfoA.
905 infoPtr : listview struct
906 code : *Unicode* notification code
907 pdi : dispinfo structure (can be unicode or ansi)
908 isW : TRUE if dispinfo is Unicode
910 static BOOL
notify_dispinfoT(const LISTVIEW_INFO
*infoPtr
, UINT code
, LPNMLVDISPINFOW pdi
, BOOL isW
)
912 INT length
= 0, ret_length
;
913 LPWSTR buffer
= NULL
, ret_text
;
914 BOOL return_ansi
= FALSE
;
915 BOOL return_unicode
= FALSE
;
918 if ((pdi
->item
.mask
& LVIF_TEXT
) && is_text(pdi
->item
.pszText
))
920 return_unicode
= ( isW
&& infoPtr
->notifyFormat
== NFR_ANSI
);
921 return_ansi
= (!isW
&& infoPtr
->notifyFormat
== NFR_UNICODE
);
924 ret_length
= pdi
->item
.cchTextMax
;
925 ret_text
= pdi
->item
.pszText
;
927 if (return_unicode
|| return_ansi
)
929 if (code
!= LVN_GETDISPINFOW
)
931 length
= return_ansi
?
932 MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)pdi
->item
.pszText
, -1, NULL
, 0):
933 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, NULL
, 0, NULL
, NULL
);
937 length
= pdi
->item
.cchTextMax
;
938 *pdi
->item
.pszText
= 0; /* make sure we don't process garbage */
941 buffer
= Alloc( (return_ansi
? sizeof(WCHAR
) : sizeof(CHAR
)) * length
);
942 if (!buffer
) return FALSE
;
945 MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)pdi
->item
.pszText
, -1,
948 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) buffer
,
951 pdi
->item
.pszText
= buffer
;
952 pdi
->item
.cchTextMax
= length
;
955 if (infoPtr
->notifyFormat
== NFR_ANSI
)
956 code
= get_ansi_notification(code
);
958 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi
->item
, infoPtr
->notifyFormat
!= NFR_ANSI
));
959 ret
= notify_hdr(infoPtr
, code
, &pdi
->hdr
);
960 TRACE(" resulting code=%d\n", pdi
->hdr
.code
);
962 if (return_ansi
|| return_unicode
)
964 if (return_ansi
&& (pdi
->hdr
.code
== LVN_GETDISPINFOA
))
966 strcpy((char*)ret_text
, (char*)pdi
->item
.pszText
);
968 else if (return_unicode
&& (pdi
->hdr
.code
== LVN_GETDISPINFOW
))
970 strcpyW(ret_text
, pdi
->item
.pszText
);
972 else if (return_ansi
) /* note : pointer can be changed by app ! */
974 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) ret_text
,
975 ret_length
, NULL
, NULL
);
978 MultiByteToWideChar(CP_ACP
, 0, (LPSTR
) pdi
->item
.pszText
, -1,
979 ret_text
, ret_length
);
981 pdi
->item
.pszText
= ret_text
; /* restores our buffer */
982 pdi
->item
.cchTextMax
= ret_length
;
988 /* if dipsinfo holder changed notification code then convert */
989 if (!isW
&& (pdi
->hdr
.code
== LVN_GETDISPINFOW
) && (pdi
->item
.mask
& LVIF_TEXT
))
991 length
= WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, NULL
, 0, NULL
, NULL
);
993 buffer
= Alloc(length
* sizeof(CHAR
));
994 if (!buffer
) return FALSE
;
996 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) buffer
,
997 ret_length
, NULL
, NULL
);
999 strcpy((LPSTR
)pdi
->item
.pszText
, (LPSTR
)buffer
);
1006 static void customdraw_fill(NMLVCUSTOMDRAW
*lpnmlvcd
, const LISTVIEW_INFO
*infoPtr
, HDC hdc
,
1007 const RECT
*rcBounds
, const LVITEMW
*lplvItem
)
1009 ZeroMemory(lpnmlvcd
, sizeof(NMLVCUSTOMDRAW
));
1010 lpnmlvcd
->nmcd
.hdc
= hdc
;
1011 lpnmlvcd
->nmcd
.rc
= *rcBounds
;
1012 lpnmlvcd
->clrTextBk
= infoPtr
->clrTextBk
;
1013 lpnmlvcd
->clrText
= infoPtr
->clrText
;
1014 if (!lplvItem
) return;
1015 lpnmlvcd
->nmcd
.dwItemSpec
= lplvItem
->iItem
+ 1;
1016 lpnmlvcd
->iSubItem
= lplvItem
->iSubItem
;
1017 if (lplvItem
->state
& LVIS_SELECTED
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_SELECTED
;
1018 if (lplvItem
->state
& LVIS_FOCUSED
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_FOCUS
;
1019 if (lplvItem
->iItem
== infoPtr
->nHotItem
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_HOT
;
1020 lpnmlvcd
->nmcd
.lItemlParam
= lplvItem
->lParam
;
1023 static inline DWORD
notify_customdraw (const LISTVIEW_INFO
*infoPtr
, DWORD dwDrawStage
, NMLVCUSTOMDRAW
*lpnmlvcd
)
1025 BOOL isForItem
= (lpnmlvcd
->nmcd
.dwItemSpec
!= 0);
1028 lpnmlvcd
->nmcd
.dwDrawStage
= dwDrawStage
;
1029 if (isForItem
) lpnmlvcd
->nmcd
.dwDrawStage
|= CDDS_ITEM
;
1030 if (lpnmlvcd
->iSubItem
) lpnmlvcd
->nmcd
.dwDrawStage
|= CDDS_SUBITEM
;
1031 if (isForItem
) lpnmlvcd
->nmcd
.dwItemSpec
--;
1032 result
= notify_hdr(infoPtr
, NM_CUSTOMDRAW
, &lpnmlvcd
->nmcd
.hdr
);
1033 if (isForItem
) lpnmlvcd
->nmcd
.dwItemSpec
++;
1037 static void prepaint_setup (const LISTVIEW_INFO
*infoPtr
, HDC hdc
, NMLVCUSTOMDRAW
*lpnmlvcd
, BOOL SubItem
)
1039 COLORREF backcolor
, textcolor
;
1041 /* apparently, for selected items, we have to override the returned values */
1044 if (lpnmlvcd
->nmcd
.uItemState
& CDIS_SELECTED
)
1046 if (infoPtr
->bFocus
)
1048 lpnmlvcd
->clrTextBk
= comctl32_color
.clrHighlight
;
1049 lpnmlvcd
->clrText
= comctl32_color
.clrHighlightText
;
1051 else if (infoPtr
->dwStyle
& LVS_SHOWSELALWAYS
)
1053 lpnmlvcd
->clrTextBk
= comctl32_color
.clr3dFace
;
1054 lpnmlvcd
->clrText
= comctl32_color
.clrBtnText
;
1059 backcolor
= lpnmlvcd
->clrTextBk
;
1060 textcolor
= lpnmlvcd
->clrText
;
1062 if (backcolor
== CLR_DEFAULT
)
1063 backcolor
= comctl32_color
.clrWindow
;
1064 if (textcolor
== CLR_DEFAULT
)
1065 textcolor
= comctl32_color
.clrWindowText
;
1067 /* Set the text attributes */
1068 if (backcolor
!= CLR_NONE
)
1070 SetBkMode(hdc
, OPAQUE
);
1071 SetBkColor(hdc
, backcolor
);
1074 SetBkMode(hdc
, TRANSPARENT
);
1075 SetTextColor(hdc
, textcolor
);
1078 static inline DWORD
notify_postpaint (const LISTVIEW_INFO
*infoPtr
, NMLVCUSTOMDRAW
*lpnmlvcd
)
1080 return notify_customdraw(infoPtr
, CDDS_POSTPAINT
, lpnmlvcd
);
1083 /* returns TRUE when repaint needed, FALSE otherwise */
1084 static BOOL
notify_measureitem(LISTVIEW_INFO
*infoPtr
)
1086 MEASUREITEMSTRUCT mis
;
1087 mis
.CtlType
= ODT_LISTVIEW
;
1088 mis
.CtlID
= GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
1092 mis
.itemHeight
= infoPtr
->nItemHeight
;
1093 SendMessageW(infoPtr
->hwndNotify
, WM_MEASUREITEM
, mis
.CtlID
, (LPARAM
)&mis
);
1094 if (infoPtr
->nItemHeight
!= max(mis
.itemHeight
, 1))
1096 infoPtr
->nMeasureItemHeight
= infoPtr
->nItemHeight
= max(mis
.itemHeight
, 1);
1102 /******** Item iterator functions **********************************/
1104 static RANGES
ranges_create(int count
);
1105 static void ranges_destroy(RANGES ranges
);
1106 static BOOL
ranges_add(RANGES ranges
, RANGE range
);
1107 static BOOL
ranges_del(RANGES ranges
, RANGE range
);
1108 static void ranges_dump(RANGES ranges
);
1110 static inline BOOL
ranges_additem(RANGES ranges
, INT nItem
)
1112 RANGE range
= { nItem
, nItem
+ 1 };
1114 return ranges_add(ranges
, range
);
1117 static inline BOOL
ranges_delitem(RANGES ranges
, INT nItem
)
1119 RANGE range
= { nItem
, nItem
+ 1 };
1121 return ranges_del(ranges
, range
);
1125 * ITERATOR DOCUMENTATION
1127 * The iterator functions allow for easy, and convenient iteration
1128 * over items of interest in the list. Typically, you create an
1129 * iterator, use it, and destroy it, as such:
1132 * iterator_xxxitems(&i, ...);
1133 * while (iterator_{prev,next}(&i)
1135 * //code which uses i.nItem
1137 * iterator_destroy(&i);
1139 * where xxx is either: framed, or visible.
1140 * Note that it is important that the code destroys the iterator
1141 * after it's done with it, as the creation of the iterator may
1142 * allocate memory, which thus needs to be freed.
1144 * You can iterate both forwards, and backwards through the list,
1145 * by using iterator_next or iterator_prev respectively.
1147 * Lower numbered items are draw on top of higher number items in
1148 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1149 * items may overlap). So, to test items, you should use
1151 * which lists the items top to bottom (in Z-order).
1152 * For drawing items, you should use
1154 * which lists the items bottom to top (in Z-order).
1155 * If you keep iterating over the items after the end-of-items
1156 * marker (-1) is returned, the iterator will start from the
1157 * beginning. Typically, you don't need to test for -1,
1158 * because iterator_{next,prev} will return TRUE if more items
1159 * are to be iterated over, or FALSE otherwise.
1161 * Note: the iterator is defined to be bidirectional. That is,
1162 * any number of prev followed by any number of next, or
1163 * five versa, should leave the iterator at the same item:
1164 * prev * n, next * n = next * n, prev * n
1166 * The iterator has a notion of an out-of-order, special item,
1167 * which sits at the start of the list. This is used in
1168 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1169 * which needs to be first, as it may overlap other items.
1171 * The code is a bit messy because we have:
1172 * - a special item to deal with
1173 * - simple range, or composite range
1175 * If you find bugs, or want to add features, please make sure you
1176 * always check/modify *both* iterator_prev, and iterator_next.
1180 * This function iterates through the items in increasing order,
1181 * but prefixed by the special item, then -1. That is:
1182 * special, 1, 2, 3, ..., n, -1.
1183 * Each item is listed only once.
1185 static inline BOOL
iterator_next(ITERATOR
* i
)
1189 i
->nItem
= i
->nSpecial
;
1190 if (i
->nItem
!= -1) return TRUE
;
1192 if (i
->nItem
== i
->nSpecial
)
1194 if (i
->ranges
) i
->index
= 0;
1200 if (i
->nItem
== i
->nSpecial
) i
->nItem
++;
1201 if (i
->nItem
< i
->range
.upper
) return TRUE
;
1206 if (i
->index
< DPA_GetPtrCount(i
->ranges
->hdpa
))
1207 i
->range
= *(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, i
->index
++);
1210 else if (i
->nItem
>= i
->range
.upper
) goto end
;
1212 i
->nItem
= i
->range
.lower
;
1213 if (i
->nItem
>= 0) goto testitem
;
1220 * This function iterates through the items in decreasing order,
1221 * followed by the special item, then -1. That is:
1222 * n, n-1, ..., 3, 2, 1, special, -1.
1223 * Each item is listed only once.
1225 static inline BOOL
iterator_prev(ITERATOR
* i
)
1232 if (i
->ranges
) i
->index
= DPA_GetPtrCount(i
->ranges
->hdpa
);
1235 if (i
->nItem
== i
->nSpecial
)
1243 if (i
->nItem
== i
->nSpecial
) i
->nItem
--;
1244 if (i
->nItem
>= i
->range
.lower
) return TRUE
;
1250 i
->range
= *(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, --i
->index
);
1253 else if (!start
&& i
->nItem
< i
->range
.lower
) goto end
;
1255 i
->nItem
= i
->range
.upper
;
1256 if (i
->nItem
> 0) goto testitem
;
1258 return (i
->nItem
= i
->nSpecial
) != -1;
1261 static RANGE
iterator_range(const ITERATOR
*i
)
1265 if (!i
->ranges
) return i
->range
;
1267 if (DPA_GetPtrCount(i
->ranges
->hdpa
) > 0)
1269 range
.lower
= (*(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, 0)).lower
;
1270 range
.upper
= (*(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, DPA_GetPtrCount(i
->ranges
->hdpa
) - 1)).upper
;
1272 else range
.lower
= range
.upper
= 0;
1278 * Releases resources associated with this iterator.
1280 static inline void iterator_destroy(const ITERATOR
*i
)
1282 ranges_destroy(i
->ranges
);
1286 * Create an empty iterator.
1288 static inline BOOL
iterator_empty(ITERATOR
* i
)
1290 ZeroMemory(i
, sizeof(*i
));
1291 i
->nItem
= i
->nSpecial
= i
->range
.lower
= i
->range
.upper
= -1;
1296 * Create an iterator over a range.
1298 static inline BOOL
iterator_rangeitems(ITERATOR
* i
, RANGE range
)
1306 * Create an iterator over a bunch of ranges.
1307 * Please note that the iterator will take ownership of the ranges,
1308 * and will free them upon destruction.
1310 static inline BOOL
iterator_rangesitems(ITERATOR
* i
, RANGES ranges
)
1318 * Creates an iterator over the items which intersect frame.
1319 * Uses absolute coordinates rather than compensating for the current offset.
1321 static BOOL
iterator_frameditems_absolute(ITERATOR
* i
, const LISTVIEW_INFO
* infoPtr
, const RECT
*frame
)
1323 RECT rcItem
, rcTemp
;
1325 /* in case we fail, we want to return an empty iterator */
1326 if (!iterator_empty(i
)) return FALSE
;
1328 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame
));
1330 if (infoPtr
->uView
== LV_VIEW_ICON
|| infoPtr
->uView
== LV_VIEW_SMALLICON
)
1334 if (infoPtr
->uView
== LV_VIEW_ICON
&& infoPtr
->nFocusedItem
!= -1)
1336 LISTVIEW_GetItemBox(infoPtr
, infoPtr
->nFocusedItem
, &rcItem
);
1337 if (IntersectRect(&rcTemp
, &rcItem
, frame
))
1338 i
->nSpecial
= infoPtr
->nFocusedItem
;
1340 if (!(iterator_rangesitems(i
, ranges_create(50)))) return FALSE
;
1341 /* to do better here, we need to have PosX, and PosY sorted */
1342 TRACE("building icon ranges:\n");
1343 for (nItem
= 0; nItem
< infoPtr
->nItemCount
; nItem
++)
1345 rcItem
.left
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
1346 rcItem
.top
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
1347 rcItem
.right
= rcItem
.left
+ infoPtr
->nItemWidth
;
1348 rcItem
.bottom
= rcItem
.top
+ infoPtr
->nItemHeight
;
1349 if (IntersectRect(&rcTemp
, &rcItem
, frame
))
1350 ranges_additem(i
->ranges
, nItem
);
1354 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
1358 if (frame
->left
>= infoPtr
->nItemWidth
) return TRUE
;
1359 if (frame
->top
>= infoPtr
->nItemHeight
* infoPtr
->nItemCount
) return TRUE
;
1361 range
.lower
= max(frame
->top
/ infoPtr
->nItemHeight
, 0);
1362 range
.upper
= min((frame
->bottom
- 1) / infoPtr
->nItemHeight
, infoPtr
->nItemCount
- 1) + 1;
1363 if (range
.upper
<= range
.lower
) return TRUE
;
1364 if (!iterator_rangeitems(i
, range
)) return FALSE
;
1365 TRACE(" report=%s\n", debugrange(&i
->range
));
1369 INT nPerCol
= max((infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
) / infoPtr
->nItemHeight
, 1);
1370 INT nFirstRow
= max(frame
->top
/ infoPtr
->nItemHeight
, 0);
1371 INT nLastRow
= min((frame
->bottom
- 1) / infoPtr
->nItemHeight
, nPerCol
- 1);
1378 if (infoPtr
->nItemWidth
)
1380 nFirstCol
= max(frame
->left
/ infoPtr
->nItemWidth
, 0);
1381 nLastCol
= min((frame
->right
- 1) / infoPtr
->nItemWidth
, (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
);
1385 nFirstCol
= max(frame
->left
, 0);
1386 nLastCol
= min(frame
->right
- 1, (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
);
1389 lower
= nFirstCol
* nPerCol
+ nFirstRow
;
1391 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1392 nPerCol
, nFirstRow
, nLastRow
, nFirstCol
, nLastCol
, lower
);
1394 if (nLastCol
< nFirstCol
|| nLastRow
< nFirstRow
) return TRUE
;
1396 if (!(iterator_rangesitems(i
, ranges_create(nLastCol
- nFirstCol
+ 1)))) return FALSE
;
1397 TRACE("building list ranges:\n");
1398 for (nCol
= nFirstCol
; nCol
<= nLastCol
; nCol
++)
1400 item_range
.lower
= nCol
* nPerCol
+ nFirstRow
;
1401 if(item_range
.lower
>= infoPtr
->nItemCount
) break;
1402 item_range
.upper
= min(nCol
* nPerCol
+ nLastRow
+ 1, infoPtr
->nItemCount
);
1403 TRACE(" list=%s\n", debugrange(&item_range
));
1404 ranges_add(i
->ranges
, item_range
);
1412 * Creates an iterator over the items which intersect lprc.
1414 static BOOL
iterator_frameditems(ITERATOR
* i
, const LISTVIEW_INFO
* infoPtr
, const RECT
*lprc
)
1419 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc
));
1421 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1422 OffsetRect(&frame
, -Origin
.x
, -Origin
.y
);
1424 return iterator_frameditems_absolute(i
, infoPtr
, &frame
);
1428 * Creates an iterator over the items which intersect the visible region of hdc.
1430 static BOOL
iterator_visibleitems(ITERATOR
*i
, const LISTVIEW_INFO
*infoPtr
, HDC hdc
)
1432 POINT Origin
, Position
;
1433 RECT rcItem
, rcClip
;
1436 rgntype
= GetClipBox(hdc
, &rcClip
);
1437 if (rgntype
== NULLREGION
) return iterator_empty(i
);
1438 if (!iterator_frameditems(i
, infoPtr
, &rcClip
)) return FALSE
;
1439 if (rgntype
== SIMPLEREGION
) return TRUE
;
1441 /* first deal with the special item */
1442 if (i
->nSpecial
!= -1)
1444 LISTVIEW_GetItemBox(infoPtr
, i
->nSpecial
, &rcItem
);
1445 if (!RectVisible(hdc
, &rcItem
)) i
->nSpecial
= -1;
1448 /* if we can't deal with the region, we'll just go with the simple range */
1449 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1450 TRACE("building visible range:\n");
1451 if (!i
->ranges
&& i
->range
.lower
< i
->range
.upper
)
1453 if (!(i
->ranges
= ranges_create(50))) return TRUE
;
1454 if (!ranges_add(i
->ranges
, i
->range
))
1456 ranges_destroy(i
->ranges
);
1462 /* now delete the invisible items from the list */
1463 while(iterator_next(i
))
1465 LISTVIEW_GetItemOrigin(infoPtr
, i
->nItem
, &Position
);
1466 rcItem
.left
= (infoPtr
->uView
== LV_VIEW_DETAILS
) ? Origin
.x
: Position
.x
+ Origin
.x
;
1467 rcItem
.top
= Position
.y
+ Origin
.y
;
1468 rcItem
.right
= rcItem
.left
+ infoPtr
->nItemWidth
;
1469 rcItem
.bottom
= rcItem
.top
+ infoPtr
->nItemHeight
;
1470 if (!RectVisible(hdc
, &rcItem
))
1471 ranges_delitem(i
->ranges
, i
->nItem
);
1473 /* the iterator should restart on the next iterator_next */
1479 /* Remove common elements from two iterators */
1480 /* Passed iterators have to point on the first elements */
1481 static BOOL
iterator_remove_common_items(ITERATOR
*iter1
, ITERATOR
*iter2
)
1483 if(!iter1
->ranges
|| !iter2
->ranges
) {
1486 if(iter1
->ranges
|| iter2
->ranges
||
1487 (iter1
->range
.lower
<iter2
->range
.lower
&& iter1
->range
.upper
>iter2
->range
.upper
) ||
1488 (iter1
->range
.lower
>iter2
->range
.lower
&& iter1
->range
.upper
<iter2
->range
.upper
)) {
1489 ERR("result is not a one range iterator\n");
1493 if(iter1
->range
.lower
==-1 || iter2
->range
.lower
==-1)
1496 lower
= iter1
->range
.lower
;
1497 upper
= iter1
->range
.upper
;
1499 if(lower
< iter2
->range
.lower
)
1500 iter1
->range
.upper
= iter2
->range
.lower
;
1501 else if(upper
> iter2
->range
.upper
)
1502 iter1
->range
.lower
= iter2
->range
.upper
;
1504 iter1
->range
.lower
= iter1
->range
.upper
= -1;
1506 if(iter2
->range
.lower
< lower
)
1507 iter2
->range
.upper
= lower
;
1508 else if(iter2
->range
.upper
> upper
)
1509 iter2
->range
.lower
= upper
;
1511 iter2
->range
.lower
= iter2
->range
.upper
= -1;
1516 iterator_next(iter1
);
1517 iterator_next(iter2
);
1520 if(iter1
->nItem
==-1 || iter2
->nItem
==-1)
1523 if(iter1
->nItem
== iter2
->nItem
) {
1524 int delete = iter1
->nItem
;
1526 iterator_prev(iter1
);
1527 iterator_prev(iter2
);
1528 ranges_delitem(iter1
->ranges
, delete);
1529 ranges_delitem(iter2
->ranges
, delete);
1530 iterator_next(iter1
);
1531 iterator_next(iter2
);
1532 } else if(iter1
->nItem
> iter2
->nItem
)
1533 iterator_next(iter2
);
1535 iterator_next(iter1
);
1538 iter1
->nItem
= iter1
->range
.lower
= iter1
->range
.upper
= -1;
1539 iter2
->nItem
= iter2
->range
.lower
= iter2
->range
.upper
= -1;
1543 /******** Misc helper functions ************************************/
1545 static inline LRESULT
CallWindowProcT(WNDPROC proc
, HWND hwnd
, UINT uMsg
,
1546 WPARAM wParam
, LPARAM lParam
, BOOL isW
)
1548 if (isW
) return CallWindowProcW(proc
, hwnd
, uMsg
, wParam
, lParam
);
1549 else return CallWindowProcA(proc
, hwnd
, uMsg
, wParam
, lParam
);
1552 static inline BOOL
is_autoarrange(const LISTVIEW_INFO
*infoPtr
)
1554 return ((infoPtr
->dwStyle
& LVS_AUTOARRANGE
) || infoPtr
->bAutoarrange
) &&
1555 (infoPtr
->uView
== LV_VIEW_ICON
|| infoPtr
->uView
== LV_VIEW_SMALLICON
);
1558 static void toggle_checkbox_state(LISTVIEW_INFO
*infoPtr
, INT nItem
)
1560 DWORD state
= STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr
, nItem
, LVIS_STATEIMAGEMASK
));
1561 if(state
== 1 || state
== 2)
1565 lvitem
.state
= INDEXTOSTATEIMAGEMASK(state
);
1566 lvitem
.stateMask
= LVIS_STATEIMAGEMASK
;
1567 LISTVIEW_SetItemState(infoPtr
, nItem
, &lvitem
);
1571 /* this should be called after window style got updated,
1572 it used to reset view state to match current window style */
1573 static inline void map_style_view(LISTVIEW_INFO
*infoPtr
)
1575 switch (infoPtr
->dwStyle
& LVS_TYPEMASK
)
1578 infoPtr
->uView
= LV_VIEW_ICON
;
1581 infoPtr
->uView
= LV_VIEW_DETAILS
;
1584 infoPtr
->uView
= LV_VIEW_SMALLICON
;
1587 infoPtr
->uView
= LV_VIEW_LIST
;
1591 /* computes next item id value */
1592 static DWORD
get_next_itemid(const LISTVIEW_INFO
*infoPtr
)
1594 INT count
= DPA_GetPtrCount(infoPtr
->hdpaItemIds
);
1598 ITEM_ID
*lpID
= DPA_GetPtr(infoPtr
->hdpaItemIds
, count
- 1);
1599 return lpID
->id
+ 1;
1604 /******** Internal API functions ************************************/
1606 static inline COLUMN_INFO
* LISTVIEW_GetColumnInfo(const LISTVIEW_INFO
*infoPtr
, INT nSubItem
)
1608 static COLUMN_INFO mainItem
;
1610 if (nSubItem
== 0 && DPA_GetPtrCount(infoPtr
->hdpaColumns
) == 0) return &mainItem
;
1611 assert (nSubItem
>= 0 && nSubItem
< DPA_GetPtrCount(infoPtr
->hdpaColumns
));
1613 /* update cached column rectangles */
1614 if (infoPtr
->colRectsDirty
)
1617 LISTVIEW_INFO
*Ptr
= (LISTVIEW_INFO
*)infoPtr
;
1620 for (i
= 0; i
< DPA_GetPtrCount(infoPtr
->hdpaColumns
); i
++) {
1621 info
= DPA_GetPtr(infoPtr
->hdpaColumns
, i
);
1622 SendMessageW(infoPtr
->hwndHeader
, HDM_GETITEMRECT
, i
, (LPARAM
)&info
->rcHeader
);
1624 Ptr
->colRectsDirty
= FALSE
;
1627 return DPA_GetPtr(infoPtr
->hdpaColumns
, nSubItem
);
1630 static INT
LISTVIEW_CreateHeader(LISTVIEW_INFO
*infoPtr
)
1632 DWORD dFlags
= WS_CHILD
| HDS_HORZ
| HDS_FULLDRAG
| HDS_DRAGDROP
;
1635 if (infoPtr
->hwndHeader
) return 0;
1637 TRACE("Creating header for list %p\n", infoPtr
->hwndSelf
);
1639 /* setup creation flags */
1640 dFlags
|= (LVS_NOSORTHEADER
& infoPtr
->dwStyle
) ? 0 : HDS_BUTTONS
;
1641 dFlags
|= (LVS_NOCOLUMNHEADER
& infoPtr
->dwStyle
) ? HDS_HIDDEN
: 0;
1643 hInst
= (HINSTANCE
)GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_HINSTANCE
);
1646 infoPtr
->hwndHeader
= CreateWindowW(WC_HEADERW
, NULL
, dFlags
,
1647 0, 0, 0, 0, infoPtr
->hwndSelf
, NULL
, hInst
, NULL
);
1648 if (!infoPtr
->hwndHeader
) return -1;
1650 /* set header unicode format */
1651 SendMessageW(infoPtr
->hwndHeader
, HDM_SETUNICODEFORMAT
, TRUE
, 0);
1653 /* set header font */
1654 SendMessageW(infoPtr
->hwndHeader
, WM_SETFONT
, (WPARAM
)infoPtr
->hFont
, TRUE
);
1656 /* set header image list */
1657 if (infoPtr
->himlSmall
)
1658 SendMessageW(infoPtr
->hwndHeader
, HDM_SETIMAGELIST
, 0, (LPARAM
)infoPtr
->himlSmall
);
1660 LISTVIEW_UpdateSize(infoPtr
);
1665 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO
*infoPtr
, INT nSubItem
, LPRECT lprc
)
1667 *lprc
= LISTVIEW_GetColumnInfo(infoPtr
, nSubItem
)->rcHeader
;
1670 static inline BOOL
LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO
*infoPtr
)
1672 return (infoPtr
->uView
== LV_VIEW_DETAILS
||
1673 infoPtr
->dwLvExStyle
& LVS_EX_HEADERINALLVIEWS
) &&
1674 !(infoPtr
->dwStyle
& LVS_NOCOLUMNHEADER
);
1677 static inline BOOL
LISTVIEW_GetItemW(const LISTVIEW_INFO
*infoPtr
, LPLVITEMW lpLVItem
)
1679 return LISTVIEW_GetItemT(infoPtr
, lpLVItem
, TRUE
);
1682 /* used to handle collapse main item column case */
1683 static inline BOOL
LISTVIEW_DrawFocusRect(const LISTVIEW_INFO
*infoPtr
, HDC hdc
)
1688 if (infoPtr
->rcFocus
.left
< infoPtr
->rcFocus
.right
)
1690 DWORD dwOldBkColor
, dwOldTextColor
;
1692 dwOldBkColor
= SetBkColor(hdc
, RGB(255, 255, 255));
1693 dwOldTextColor
= SetBkColor(hdc
, RGB(0, 0, 0));
1694 Ret
= DrawFocusRect(hdc
, &infoPtr
->rcFocus
);
1695 SetBkColor(hdc
, dwOldBkColor
);
1696 SetBkColor(hdc
, dwOldTextColor
);
1700 return (infoPtr
->rcFocus
.left
< infoPtr
->rcFocus
.right
) ?
1701 DrawFocusRect(hdc
, &infoPtr
->rcFocus
) : FALSE
;
1705 /* Listview invalidation functions: use _only_ these functions to invalidate */
1707 static inline BOOL
is_redrawing(const LISTVIEW_INFO
*infoPtr
)
1709 return infoPtr
->bRedraw
;
1712 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO
*infoPtr
, const RECT
* rect
)
1714 if(!is_redrawing(infoPtr
)) return;
1715 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect
));
1716 InvalidateRect(infoPtr
->hwndSelf
, rect
, TRUE
);
1719 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO
*infoPtr
, INT nItem
)
1723 if(!is_redrawing(infoPtr
)) return;
1724 LISTVIEW_GetItemBox(infoPtr
, nItem
, &rcBox
);
1725 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
1728 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO
*infoPtr
, INT nItem
, INT nSubItem
)
1730 POINT Origin
, Position
;
1733 if(!is_redrawing(infoPtr
)) return;
1734 assert (infoPtr
->uView
== LV_VIEW_DETAILS
);
1735 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1736 LISTVIEW_GetItemOrigin(infoPtr
, nItem
, &Position
);
1737 LISTVIEW_GetHeaderRect(infoPtr
, nSubItem
, &rcBox
);
1739 rcBox
.bottom
= infoPtr
->nItemHeight
;
1740 OffsetRect(&rcBox
, Origin
.x
+ Position
.x
, Origin
.y
+ Position
.y
);
1741 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
1744 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO
*infoPtr
)
1746 LISTVIEW_InvalidateRect(infoPtr
, NULL
);
1749 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO
*infoPtr
, INT nColumn
)
1753 if(!is_redrawing(infoPtr
)) return;
1754 LISTVIEW_GetHeaderRect(infoPtr
, nColumn
, &rcCol
);
1755 rcCol
.top
= infoPtr
->rcList
.top
;
1756 rcCol
.bottom
= infoPtr
->rcList
.bottom
;
1757 LISTVIEW_InvalidateRect(infoPtr
, &rcCol
);
1762 * Retrieves the number of items that can fit vertically in the client area.
1765 * [I] infoPtr : valid pointer to the listview structure
1768 * Number of items per row.
1770 static inline INT
LISTVIEW_GetCountPerRow(const LISTVIEW_INFO
*infoPtr
)
1772 INT nListWidth
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
1774 return max(nListWidth
/(infoPtr
->nItemWidth
? infoPtr
->nItemWidth
: 1), 1);
1779 * Retrieves the number of items that can fit horizontally in the client
1783 * [I] infoPtr : valid pointer to the listview structure
1786 * Number of items per column.
1788 static inline INT
LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO
*infoPtr
)
1790 INT nListHeight
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
1792 return max(nListHeight
/ infoPtr
->nItemHeight
, 1);
1796 /*************************************************************************
1797 * LISTVIEW_ProcessLetterKeys
1799 * Processes keyboard messages generated by pressing the letter keys
1801 * What this does is perform a case insensitive search from the
1802 * current position with the following quirks:
1803 * - If two chars or more are pressed in quick succession we search
1804 * for the corresponding string (e.g. 'abc').
1805 * - If there is a delay we wipe away the current search string and
1806 * restart with just that char.
1807 * - If the user keeps pressing the same character, whether slowly or
1808 * fast, so that the search string is entirely composed of this
1809 * character ('aaaaa' for instance), then we search for first item
1810 * that starting with that character.
1811 * - If the user types the above character in quick succession, then
1812 * we must also search for the corresponding string ('aaaaa'), and
1813 * go to that string if there is a match.
1816 * [I] hwnd : handle to the window
1817 * [I] charCode : the character code, the actual character
1818 * [I] keyData : key data
1826 * - The current implementation has a list of characters it will
1827 * accept and it ignores everything else. In particular it will
1828 * ignore accentuated characters which seems to match what
1829 * Windows does. But I'm not sure it makes sense to follow
1831 * - We don't sound a beep when the search fails.
1835 * TREEVIEW_ProcessLetterKeys
1837 static INT
LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO
*infoPtr
, WPARAM charCode
, LPARAM keyData
)
1839 WCHAR buffer
[MAX_PATH
];
1846 /* simple parameter checking */
1847 if (!charCode
|| !keyData
|| infoPtr
->nItemCount
== 0) return 0;
1849 /* only allow the valid WM_CHARs through */
1850 if (!isalnumW(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
!= '>' &&
1859 charCode
!= '<' && charCode
!= ',' && charCode
!= '~')
1862 /* update the search parameters */
1863 prevTime
= infoPtr
->lastKeyPressTimestamp
;
1864 infoPtr
->lastKeyPressTimestamp
= GetTickCount();
1865 diff
= infoPtr
->lastKeyPressTimestamp
- prevTime
;
1867 if (diff
>= 0 && diff
< KEY_DELAY
)
1869 if (infoPtr
->nSearchParamLength
< MAX_PATH
- 1)
1870 infoPtr
->szSearchParam
[infoPtr
->nSearchParamLength
++] = charCode
;
1872 if (infoPtr
->charCode
!= charCode
)
1873 infoPtr
->charCode
= charCode
= 0;
1877 infoPtr
->charCode
= charCode
;
1878 infoPtr
->szSearchParam
[0] = charCode
;
1879 infoPtr
->nSearchParamLength
= 1;
1882 /* should start from next after focused item, so next item that matches
1883 will be selected, if there isn't any and focused matches it will be selected
1884 on second search stage from beginning of the list */
1885 if (infoPtr
->nFocusedItem
>= 0 && infoPtr
->nItemCount
> 1)
1887 /* with some accumulated search data available start with current focus, otherwise
1888 it's excluded from search */
1889 startidx
= infoPtr
->nSearchParamLength
> 1 ? infoPtr
->nFocusedItem
: infoPtr
->nFocusedItem
+ 1;
1890 if (startidx
== infoPtr
->nItemCount
) startidx
= 0;
1895 /* let application handle this for virtual listview */
1896 if (infoPtr
->dwStyle
& LVS_OWNERDATA
)
1900 memset(&nmlv
.lvfi
, 0, sizeof(nmlv
.lvfi
));
1901 nmlv
.lvfi
.flags
= (LVFI_WRAP
| LVFI_PARTIAL
);
1902 nmlv
.lvfi
.psz
= infoPtr
->szSearchParam
;
1903 nmlv
.iStart
= startidx
;
1905 infoPtr
->szSearchParam
[infoPtr
->nSearchParamLength
] = 0;
1907 nItem
= notify_hdr(infoPtr
, LVN_ODFINDITEMW
, (LPNMHDR
)&nmlv
.hdr
);
1911 int i
= startidx
, endidx
;
1913 /* and search from the current position */
1915 endidx
= infoPtr
->nItemCount
;
1917 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1920 /* start from first item if not found with >= startidx */
1921 if (i
== infoPtr
->nItemCount
&& startidx
> 0)
1927 for (i
= startidx
; i
< endidx
; i
++)
1930 item
.mask
= LVIF_TEXT
;
1933 item
.pszText
= buffer
;
1934 item
.cchTextMax
= MAX_PATH
;
1935 if (!LISTVIEW_GetItemW(infoPtr
, &item
)) return 0;
1937 if (!lstrncmpiW(item
.pszText
, infoPtr
->szSearchParam
, infoPtr
->nSearchParamLength
))
1942 /* this is used to find first char match when search string is not available yet,
1943 otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1944 already waiting for user to complete a string */
1945 else if (nItem
== -1 && infoPtr
->nSearchParamLength
== 1 && !lstrncmpiW(item
.pszText
, infoPtr
->szSearchParam
, 1))
1947 /* this would work but we must keep looking for a longer match */
1952 if ( nItem
!= -1 || /* found something */
1953 endidx
!= infoPtr
->nItemCount
|| /* second search done */
1954 (startidx
== 0 && endidx
== infoPtr
->nItemCount
) /* full range for first search */ )
1960 LISTVIEW_KeySelection(infoPtr
, nItem
, FALSE
);
1965 /*************************************************************************
1966 * LISTVIEW_UpdateHeaderSize [Internal]
1968 * Function to resize the header control
1971 * [I] hwnd : handle to a window
1972 * [I] nNewScrollPos : scroll pos to set
1977 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO
*infoPtr
, INT nNewScrollPos
)
1982 TRACE("nNewScrollPos=%d\n", nNewScrollPos
);
1984 if (!infoPtr
->hwndHeader
) return;
1986 GetWindowRect(infoPtr
->hwndHeader
, &winRect
);
1987 point
[0].x
= winRect
.left
;
1988 point
[0].y
= winRect
.top
;
1989 point
[1].x
= winRect
.right
;
1990 point
[1].y
= winRect
.bottom
;
1992 MapWindowPoints(HWND_DESKTOP
, infoPtr
->hwndSelf
, point
, 2);
1993 point
[0].x
= -nNewScrollPos
;
1994 point
[1].x
+= nNewScrollPos
;
1996 SetWindowPos(infoPtr
->hwndHeader
,0,
1997 point
[0].x
,point
[0].y
,point
[1].x
,point
[1].y
,
1998 (infoPtr
->dwStyle
& LVS_NOCOLUMNHEADER
) ? SWP_HIDEWINDOW
: SWP_SHOWWINDOW
|
1999 SWP_NOZORDER
| SWP_NOACTIVATE
);
2004 * Update the scrollbars. This functions should be called whenever
2005 * the content, size or view changes.
2008 * [I] infoPtr : valid pointer to the listview structure
2013 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO
*infoPtr
)
2015 SCROLLINFO horzInfo
, vertInfo
;
2018 if ((infoPtr
->dwStyle
& LVS_NOSCROLL
) || !is_redrawing(infoPtr
)) return;
2020 ZeroMemory(&horzInfo
, sizeof(SCROLLINFO
));
2021 horzInfo
.cbSize
= sizeof(SCROLLINFO
);
2022 horzInfo
.nPage
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
2024 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2025 if (infoPtr
->uView
== LV_VIEW_LIST
)
2027 INT nPerCol
= LISTVIEW_GetCountPerColumn(infoPtr
);
2028 horzInfo
.nMax
= (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
;
2030 /* scroll by at least one column per page */
2031 if(horzInfo
.nPage
< infoPtr
->nItemWidth
)
2032 horzInfo
.nPage
= infoPtr
->nItemWidth
;
2034 if (infoPtr
->nItemWidth
)
2035 horzInfo
.nPage
/= infoPtr
->nItemWidth
;
2037 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2039 horzInfo
.nMax
= infoPtr
->nItemWidth
;
2041 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2045 if (LISTVIEW_GetViewRect(infoPtr
, &rcView
)) horzInfo
.nMax
= rcView
.right
- rcView
.left
;
2048 if (LISTVIEW_IsHeaderEnabled(infoPtr
))
2050 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
))
2055 index
= SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
,
2056 DPA_GetPtrCount(infoPtr
->hdpaColumns
) - 1, 0);
2058 LISTVIEW_GetHeaderRect(infoPtr
, index
, &rcHeader
);
2059 horzInfo
.nMax
= rcHeader
.right
;
2060 TRACE("horzInfo.nMax=%d\n", horzInfo
.nMax
);
2064 horzInfo
.fMask
= SIF_RANGE
| SIF_PAGE
;
2065 horzInfo
.nMax
= max(horzInfo
.nMax
- 1, 0);
2066 dx
= GetScrollPos(infoPtr
->hwndSelf
, SB_HORZ
);
2067 dx
-= SetScrollInfo(infoPtr
->hwndSelf
, SB_HORZ
, &horzInfo
, TRUE
);
2068 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo
));
2070 /* Setting the horizontal scroll can change the listview size
2071 * (and potentially everything else) so we need to recompute
2072 * everything again for the vertical scroll
2075 ZeroMemory(&vertInfo
, sizeof(SCROLLINFO
));
2076 vertInfo
.cbSize
= sizeof(SCROLLINFO
);
2077 vertInfo
.nPage
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
2079 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2081 vertInfo
.nMax
= infoPtr
->nItemCount
;
2083 /* scroll by at least one page */
2084 if(vertInfo
.nPage
< infoPtr
->nItemHeight
)
2085 vertInfo
.nPage
= infoPtr
->nItemHeight
;
2087 if (infoPtr
->nItemHeight
> 0)
2088 vertInfo
.nPage
/= infoPtr
->nItemHeight
;
2090 else if (infoPtr
->uView
!= LV_VIEW_LIST
) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2094 if (LISTVIEW_GetViewRect(infoPtr
, &rcView
)) vertInfo
.nMax
= rcView
.bottom
- rcView
.top
;
2097 vertInfo
.fMask
= SIF_RANGE
| SIF_PAGE
;
2098 vertInfo
.nMax
= max(vertInfo
.nMax
- 1, 0);
2099 dy
= GetScrollPos(infoPtr
->hwndSelf
, SB_VERT
);
2100 dy
-= SetScrollInfo(infoPtr
->hwndSelf
, SB_VERT
, &vertInfo
, TRUE
);
2101 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo
));
2103 /* Change of the range may have changed the scroll pos. If so move the content */
2104 if (dx
!= 0 || dy
!= 0)
2107 listRect
= infoPtr
->rcList
;
2108 ScrollWindowEx(infoPtr
->hwndSelf
, dx
, dy
, &listRect
, &listRect
, 0, 0,
2109 SW_ERASE
| SW_INVALIDATE
);
2112 /* Update the Header Control */
2113 if (infoPtr
->hwndHeader
)
2115 horzInfo
.fMask
= SIF_POS
;
2116 GetScrollInfo(infoPtr
->hwndSelf
, SB_HORZ
, &horzInfo
);
2117 LISTVIEW_UpdateHeaderSize(infoPtr
, horzInfo
.nPos
);
2124 * Shows/hides the focus rectangle.
2127 * [I] infoPtr : valid pointer to the listview structure
2128 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2133 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO
*infoPtr
, BOOL fShow
)
2137 TRACE("fShow=%d, nItem=%d\n", fShow
, infoPtr
->nFocusedItem
);
2139 if (infoPtr
->nFocusedItem
< 0) return;
2141 /* we need some gymnastics in ICON mode to handle large items */
2142 if (infoPtr
->uView
== LV_VIEW_ICON
)
2146 LISTVIEW_GetItemBox(infoPtr
, infoPtr
->nFocusedItem
, &rcBox
);
2147 if ((rcBox
.bottom
- rcBox
.top
) > infoPtr
->nItemHeight
)
2149 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
2154 if (!(hdc
= GetDC(infoPtr
->hwndSelf
))) return;
2156 /* for some reason, owner draw should work only in report mode */
2157 if ((infoPtr
->dwStyle
& LVS_OWNERDRAWFIXED
) && (infoPtr
->uView
== LV_VIEW_DETAILS
))
2162 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
2163 HFONT hOldFont
= SelectObject(hdc
, hFont
);
2165 item
.iItem
= infoPtr
->nFocusedItem
;
2167 item
.mask
= LVIF_PARAM
;
2168 if (!LISTVIEW_GetItemW(infoPtr
, &item
)) goto done
;
2170 ZeroMemory(&dis
, sizeof(dis
));
2171 dis
.CtlType
= ODT_LISTVIEW
;
2172 dis
.CtlID
= (UINT
)GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
2173 dis
.itemID
= item
.iItem
;
2174 dis
.itemAction
= ODA_FOCUS
;
2175 if (fShow
) dis
.itemState
|= ODS_FOCUS
;
2176 dis
.hwndItem
= infoPtr
->hwndSelf
;
2178 LISTVIEW_GetItemBox(infoPtr
, dis
.itemID
, &dis
.rcItem
);
2179 dis
.itemData
= item
.lParam
;
2181 SendMessageW(infoPtr
->hwndNotify
, WM_DRAWITEM
, dis
.CtlID
, (LPARAM
)&dis
);
2183 SelectObject(hdc
, hOldFont
);
2187 LISTVIEW_DrawFocusRect(infoPtr
, hdc
);
2190 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
2194 * Invalidates all visible selected items.
2196 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO
*infoPtr
)
2200 iterator_frameditems(&i
, infoPtr
, &infoPtr
->rcList
);
2201 while(iterator_next(&i
))
2203 if (LISTVIEW_GetItemState(infoPtr
, i
.nItem
, LVIS_SELECTED
))
2204 LISTVIEW_InvalidateItem(infoPtr
, i
.nItem
);
2206 iterator_destroy(&i
);
2211 * DESCRIPTION: [INTERNAL]
2212 * Computes an item's (left,top) corner, relative to rcView.
2213 * That is, the position has NOT been made relative to the Origin.
2214 * This is deliberate, to avoid computing the Origin over, and
2215 * over again, when this function is called in a loop. Instead,
2216 * one can factor the computation of the Origin before the loop,
2217 * and offset the value returned by this function, on every iteration.
2220 * [I] infoPtr : valid pointer to the listview structure
2221 * [I] nItem : item number
2222 * [O] lpptOrig : item top, left corner
2227 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO
*infoPtr
, INT nItem
, LPPOINT lpptPosition
)
2229 assert(nItem
>= 0 && nItem
< infoPtr
->nItemCount
);
2231 if ((infoPtr
->uView
== LV_VIEW_SMALLICON
) || (infoPtr
->uView
== LV_VIEW_ICON
))
2233 lpptPosition
->x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
2234 lpptPosition
->y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
2236 else if (infoPtr
->uView
== LV_VIEW_LIST
)
2238 INT nCountPerColumn
= LISTVIEW_GetCountPerColumn(infoPtr
);
2239 lpptPosition
->x
= nItem
/ nCountPerColumn
* infoPtr
->nItemWidth
;
2240 lpptPosition
->y
= nItem
% nCountPerColumn
* infoPtr
->nItemHeight
;
2242 else /* LV_VIEW_DETAILS */
2244 lpptPosition
->x
= REPORT_MARGINX
;
2245 /* item is always at zero indexed column */
2246 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
) > 0)
2247 lpptPosition
->x
+= LISTVIEW_GetColumnInfo(infoPtr
, 0)->rcHeader
.left
;
2248 lpptPosition
->y
= nItem
* infoPtr
->nItemHeight
;
2253 * DESCRIPTION: [INTERNAL]
2254 * Compute the rectangles of an item. This is to localize all
2255 * the computations in one place. If you are not interested in some
2256 * of these values, simply pass in a NULL -- the function is smart
2257 * enough to compute only what's necessary. The function computes
2258 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2259 * one, the BOX rectangle. This rectangle is very cheap to compute,
2260 * and is guaranteed to contain all the other rectangles. Computing
2261 * the ICON rect is also cheap, but all the others are potentially
2262 * expensive. This gives an easy and effective optimization when
2263 * searching (like point inclusion, or rectangle intersection):
2264 * first test against the BOX, and if TRUE, test against the desired
2266 * If the function does not have all the necessary information
2267 * to computed the requested rectangles, will crash with a
2268 * failed assertion. This is done so we catch all programming
2269 * errors, given that the function is called only from our code.
2271 * We have the following 'special' meanings for a few fields:
2272 * * If LVIS_FOCUSED is set, we assume the item has the focus
2273 * This is important in ICON mode, where it might get a larger
2274 * then usual rectangle
2276 * Please note that subitem support works only in REPORT mode.
2279 * [I] infoPtr : valid pointer to the listview structure
2280 * [I] lpLVItem : item to compute the measures for
2281 * [O] lprcBox : ptr to Box rectangle
2282 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2283 * [0] lprcSelectBox : ptr to select box rectangle
2284 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2285 * [O] lprcIcon : ptr to Icon rectangle
2286 * Same as LVM_GETITEMRECT with LVIR_ICON
2287 * [O] lprcStateIcon: ptr to State Icon rectangle
2288 * [O] lprcLabel : ptr to Label rectangle
2289 * Same as LVM_GETITEMRECT with LVIR_LABEL
2294 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO
*infoPtr
, const LVITEMW
*lpLVItem
,
2295 LPRECT lprcBox
, LPRECT lprcSelectBox
,
2296 LPRECT lprcIcon
, LPRECT lprcStateIcon
, LPRECT lprcLabel
)
2298 BOOL doSelectBox
= FALSE
, doIcon
= FALSE
, doLabel
= FALSE
, oversizedBox
= FALSE
;
2299 RECT Box
, SelectBox
, Icon
, Label
;
2300 COLUMN_INFO
*lpColumnInfo
= NULL
;
2301 SIZE labelSize
= { 0, 0 };
2303 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem
, TRUE
));
2305 /* Be smart and try to figure out the minimum we have to do */
2306 if (lpLVItem
->iSubItem
) assert(infoPtr
->uView
== LV_VIEW_DETAILS
);
2307 if (infoPtr
->uView
== LV_VIEW_ICON
&& (lprcBox
|| lprcLabel
))
2309 assert((lpLVItem
->mask
& LVIF_STATE
) && (lpLVItem
->stateMask
& LVIS_FOCUSED
));
2310 if (lpLVItem
->state
& LVIS_FOCUSED
) oversizedBox
= doLabel
= TRUE
;
2312 if (lprcSelectBox
) doSelectBox
= TRUE
;
2313 if (lprcLabel
) doLabel
= TRUE
;
2314 if (doLabel
|| lprcIcon
|| lprcStateIcon
) doIcon
= TRUE
;
2321 /************************************************************/
2322 /* compute the box rectangle (it should be cheap to do) */
2323 /************************************************************/
2324 if (lpLVItem
->iSubItem
|| infoPtr
->uView
== LV_VIEW_DETAILS
)
2325 lpColumnInfo
= LISTVIEW_GetColumnInfo(infoPtr
, lpLVItem
->iSubItem
);
2327 if (lpLVItem
->iSubItem
)
2329 Box
= lpColumnInfo
->rcHeader
;
2334 Box
.right
= infoPtr
->nItemWidth
;
2337 Box
.bottom
= infoPtr
->nItemHeight
;
2339 /******************************************************************/
2340 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2341 /******************************************************************/
2344 LONG state_width
= 0;
2346 if (infoPtr
->himlState
&& lpLVItem
->iSubItem
== 0)
2347 state_width
= infoPtr
->iconStateSize
.cx
;
2349 if (infoPtr
->uView
== LV_VIEW_ICON
)
2351 Icon
.left
= Box
.left
+ state_width
;
2352 if (infoPtr
->himlNormal
)
2353 Icon
.left
+= (infoPtr
->nItemWidth
- infoPtr
->iconSize
.cx
- state_width
) / 2;
2354 Icon
.top
= Box
.top
+ ICON_TOP_PADDING
;
2355 Icon
.right
= Icon
.left
;
2356 Icon
.bottom
= Icon
.top
;
2357 if (infoPtr
->himlNormal
)
2359 Icon
.right
+= infoPtr
->iconSize
.cx
;
2360 Icon
.bottom
+= infoPtr
->iconSize
.cy
;
2363 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2365 Icon
.left
= Box
.left
+ state_width
;
2367 if (infoPtr
->uView
== LV_VIEW_DETAILS
&& lpLVItem
->iSubItem
== 0)
2369 /* we need the indent in report mode */
2370 assert(lpLVItem
->mask
& LVIF_INDENT
);
2371 Icon
.left
+= infoPtr
->iconSize
.cx
* lpLVItem
->iIndent
+ REPORT_MARGINX
;
2375 Icon
.right
= Icon
.left
;
2376 if (infoPtr
->himlSmall
&&
2377 (!lpColumnInfo
|| lpLVItem
->iSubItem
== 0 ||
2378 ((infoPtr
->dwLvExStyle
& LVS_EX_SUBITEMIMAGES
) && lpLVItem
->iImage
!= I_IMAGECALLBACK
)))
2379 Icon
.right
+= infoPtr
->iconSize
.cx
;
2380 Icon
.bottom
= Icon
.top
+ infoPtr
->iconSize
.cy
;
2382 if(lprcIcon
) *lprcIcon
= Icon
;
2383 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon
));
2385 /* TODO: is this correct? */
2388 lprcStateIcon
->left
= Icon
.left
- state_width
;
2389 lprcStateIcon
->right
= Icon
.left
;
2390 lprcStateIcon
->top
= Icon
.top
;
2391 lprcStateIcon
->bottom
= lprcStateIcon
->top
+ infoPtr
->iconSize
.cy
;
2392 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon
));
2395 else Icon
.right
= 0;
2397 /************************************************************/
2398 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2399 /************************************************************/
2402 /* calculate how far to the right can the label stretch */
2403 Label
.right
= Box
.right
;
2404 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2406 if (lpLVItem
->iSubItem
== 0)
2408 /* we need a zero based rect here */
2409 Label
= lpColumnInfo
->rcHeader
;
2410 OffsetRect(&Label
, -Label
.left
, 0);
2414 if (lpLVItem
->iSubItem
|| ((infoPtr
->dwStyle
& LVS_OWNERDRAWFIXED
) && infoPtr
->uView
== LV_VIEW_DETAILS
))
2416 labelSize
.cx
= infoPtr
->nItemWidth
;
2417 labelSize
.cy
= infoPtr
->nItemHeight
;
2421 /* we need the text in non owner draw mode */
2422 assert(lpLVItem
->mask
& LVIF_TEXT
);
2423 if (is_text(lpLVItem
->pszText
))
2425 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
2426 HDC hdc
= GetDC(infoPtr
->hwndSelf
);
2427 HFONT hOldFont
= SelectObject(hdc
, hFont
);
2431 /* compute rough rectangle where the label will go */
2432 SetRectEmpty(&rcText
);
2433 rcText
.right
= infoPtr
->nItemWidth
- TRAILING_LABEL_PADDING
;
2434 rcText
.bottom
= infoPtr
->nItemHeight
;
2435 if (infoPtr
->uView
== LV_VIEW_ICON
)
2436 rcText
.bottom
-= ICON_TOP_PADDING
+ infoPtr
->iconSize
.cy
+ ICON_BOTTOM_PADDING
;
2438 /* now figure out the flags */
2439 if (infoPtr
->uView
== LV_VIEW_ICON
)
2440 uFormat
= oversizedBox
? LV_FL_DT_FLAGS
: LV_ML_DT_FLAGS
;
2442 uFormat
= LV_SL_DT_FLAGS
;
2444 DrawTextW (hdc
, lpLVItem
->pszText
, -1, &rcText
, uFormat
| DT_CALCRECT
);
2446 if (rcText
.right
!= rcText
.left
)
2447 labelSize
.cx
= min(rcText
.right
- rcText
.left
+ TRAILING_LABEL_PADDING
, infoPtr
->nItemWidth
);
2449 labelSize
.cy
= rcText
.bottom
- rcText
.top
;
2451 SelectObject(hdc
, hOldFont
);
2452 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
2456 if (infoPtr
->uView
== LV_VIEW_ICON
)
2458 Label
.left
= Box
.left
+ (infoPtr
->nItemWidth
- labelSize
.cx
) / 2;
2459 Label
.top
= Box
.top
+ ICON_TOP_PADDING_HITABLE
+
2460 infoPtr
->iconSize
.cy
+ ICON_BOTTOM_PADDING
;
2461 Label
.right
= Label
.left
+ labelSize
.cx
;
2462 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2463 if (!oversizedBox
&& labelSize
.cy
> infoPtr
->ntmHeight
)
2465 labelSize
.cy
= min(Box
.bottom
- Label
.top
, labelSize
.cy
);
2466 labelSize
.cy
/= infoPtr
->ntmHeight
;
2467 labelSize
.cy
= max(labelSize
.cy
, 1);
2468 labelSize
.cy
*= infoPtr
->ntmHeight
;
2470 Label
.bottom
= Label
.top
+ labelSize
.cy
+ HEIGHT_PADDING
;
2472 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2474 Label
.left
= Icon
.right
;
2475 Label
.top
= Box
.top
;
2476 Label
.right
= lpLVItem
->iSubItem
? lpColumnInfo
->rcHeader
.right
:
2477 lpColumnInfo
->rcHeader
.right
- lpColumnInfo
->rcHeader
.left
;
2478 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2480 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2482 Label
.left
= Icon
.right
;
2483 Label
.top
= Box
.top
;
2484 Label
.right
= min(Label
.left
+ labelSize
.cx
, Label
.right
);
2485 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2488 if (lprcLabel
) *lprcLabel
= Label
;
2489 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label
));
2492 /************************************************************/
2493 /* compute SELECT bounding box */
2494 /************************************************************/
2497 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2499 SelectBox
.left
= Icon
.left
;
2500 SelectBox
.top
= Box
.top
;
2501 SelectBox
.bottom
= Box
.bottom
;
2504 SelectBox
.right
= min(Label
.left
+ labelSize
.cx
, Label
.right
);
2506 SelectBox
.right
= min(Label
.left
+ MAX_EMPTYTEXT_SELECT_WIDTH
, Label
.right
);
2510 UnionRect(&SelectBox
, &Icon
, &Label
);
2512 if (lprcSelectBox
) *lprcSelectBox
= SelectBox
;
2513 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox
));
2516 /* Fix the Box if necessary */
2519 if (oversizedBox
) UnionRect(lprcBox
, &Box
, &Label
);
2520 else *lprcBox
= Box
;
2522 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box
));
2526 * DESCRIPTION: [INTERNAL]
2529 * [I] infoPtr : valid pointer to the listview structure
2530 * [I] nItem : item number
2531 * [O] lprcBox : ptr to Box rectangle
2536 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO
*infoPtr
, INT nItem
, LPRECT lprcBox
)
2538 WCHAR szDispText
[DISP_TEXT_SIZE
] = { '\0' };
2539 POINT Position
, Origin
;
2542 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
2543 LISTVIEW_GetItemOrigin(infoPtr
, nItem
, &Position
);
2545 /* Be smart and try to figure out the minimum we have to do */
2547 if (infoPtr
->uView
== LV_VIEW_ICON
&& infoPtr
->bFocus
&& LISTVIEW_GetItemState(infoPtr
, nItem
, LVIS_FOCUSED
))
2548 lvItem
.mask
|= LVIF_TEXT
;
2549 lvItem
.iItem
= nItem
;
2550 lvItem
.iSubItem
= 0;
2551 lvItem
.pszText
= szDispText
;
2552 lvItem
.cchTextMax
= DISP_TEXT_SIZE
;
2553 if (lvItem
.mask
) LISTVIEW_GetItemW(infoPtr
, &lvItem
);
2554 if (infoPtr
->uView
== LV_VIEW_ICON
)
2556 lvItem
.mask
|= LVIF_STATE
;
2557 lvItem
.stateMask
= LVIS_FOCUSED
;
2558 lvItem
.state
= (lvItem
.mask
& LVIF_TEXT
? LVIS_FOCUSED
: 0);
2560 LISTVIEW_GetItemMetrics(infoPtr
, &lvItem
, lprcBox
, 0, 0, 0, 0);
2562 if (infoPtr
->uView
== LV_VIEW_DETAILS
&& infoPtr
->dwLvExStyle
& LVS_EX_FULLROWSELECT
&&
2563 SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
, 0, 0))
2565 OffsetRect(lprcBox
, Origin
.x
, Position
.y
+ Origin
.y
);
2568 OffsetRect(lprcBox
, Position
.x
+ Origin
.x
, Position
.y
+ Origin
.y
);
2571 /* LISTVIEW_MapIdToIndex helper */
2572 static INT CALLBACK
MapIdSearchCompare(LPVOID p1
, LPVOID p2
, LPARAM lParam
)
2574 ITEM_ID
*id1
= (ITEM_ID
*)p1
;
2575 ITEM_ID
*id2
= (ITEM_ID
*)p2
;
2577 if (id1
->id
== id2
->id
) return 0;
2579 return (id1
->id
< id2
->id
) ? -1 : 1;
2584 * Returns the item index for id specified.
2587 * [I] infoPtr : valid pointer to the listview structure
2588 * [I] iID : item id to get index for
2591 * Item index, or -1 on failure.
2593 static INT
LISTVIEW_MapIdToIndex(const LISTVIEW_INFO
*infoPtr
, UINT iID
)
2598 TRACE("iID=%d\n", iID
);
2600 if (infoPtr
->dwStyle
& LVS_OWNERDATA
) return -1;
2601 if (infoPtr
->nItemCount
== 0) return -1;
2604 index
= DPA_Search(infoPtr
->hdpaItemIds
, &ID
, -1, MapIdSearchCompare
, 0, DPAS_SORTED
);
2608 ITEM_ID
*lpID
= DPA_GetPtr(infoPtr
->hdpaItemIds
, index
);
2609 return DPA_GetPtrIndex(infoPtr
->hdpaItems
, lpID
->item
);
2617 * Returns the item id for index given.
2620 * [I] infoPtr : valid pointer to the listview structure
2621 * [I] iItem : item index to get id for
2626 static DWORD
LISTVIEW_MapIndexToId(const LISTVIEW_INFO
*infoPtr
, INT iItem
)
2631 TRACE("iItem=%d\n", iItem
);
2633 if (infoPtr
->dwStyle
& LVS_OWNERDATA
) return -1;
2634 if (iItem
< 0 || iItem
>= infoPtr
->nItemCount
) return -1;
2636 hdpaSubItems
= DPA_GetPtr(infoPtr
->hdpaItems
, iItem
);
2637 lpItem
= DPA_GetPtr(hdpaSubItems
, 0);
2639 return lpItem
->id
->id
;
2644 * Returns the current icon position, and advances it along the top.
2645 * The returned position is not offset by Origin.
2648 * [I] infoPtr : valid pointer to the listview structure
2649 * [O] lpPos : will get the current icon position
2654 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO
*infoPtr
, LPPOINT lpPos
)
2656 INT nListWidth
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
2658 *lpPos
= infoPtr
->currIconPos
;
2660 infoPtr
->currIconPos
.x
+= infoPtr
->nItemWidth
;
2661 if (infoPtr
->currIconPos
.x
+ infoPtr
->nItemWidth
<= nListWidth
) return;
2663 infoPtr
->currIconPos
.x
= 0;
2664 infoPtr
->currIconPos
.y
+= infoPtr
->nItemHeight
;
2670 * Returns the current icon position, and advances it down the left edge.
2671 * The returned position is not offset by Origin.
2674 * [I] infoPtr : valid pointer to the listview structure
2675 * [O] lpPos : will get the current icon position
2680 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO
*infoPtr
, LPPOINT lpPos
)
2682 INT nListHeight
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
2684 *lpPos
= infoPtr
->currIconPos
;
2686 infoPtr
->currIconPos
.y
+= infoPtr
->nItemHeight
;
2687 if (infoPtr
->currIconPos
.y
+ infoPtr
->nItemHeight
<= nListHeight
) return;
2689 infoPtr
->currIconPos
.x
+= infoPtr
->nItemWidth
;
2690 infoPtr
->currIconPos
.y
= 0;
2696 * Moves an icon to the specified position.
2697 * It takes care of invalidating the item, etc.
2700 * [I] infoPtr : valid pointer to the listview structure
2701 * [I] nItem : the item to move
2702 * [I] lpPos : the new icon position
2703 * [I] isNew : flags the item as being new
2709 static BOOL
LISTVIEW_MoveIconTo(const LISTVIEW_INFO
*infoPtr
, INT nItem
, const POINT
*lppt
, BOOL isNew
)
2715 old
.x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
2716 old
.y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
2718 if (lppt
->x
== old
.x
&& lppt
->y
== old
.y
) return TRUE
;
2719 LISTVIEW_InvalidateItem(infoPtr
, nItem
);
2722 /* Allocating a POINTER for every item is too resource intensive,
2723 * so we'll keep the (x,y) in different arrays */
2724 if (!DPA_SetPtr(infoPtr
->hdpaPosX
, nItem
, (void *)(LONG_PTR
)lppt
->x
)) return FALSE
;
2725 if (!DPA_SetPtr(infoPtr
->hdpaPosY
, nItem
, (void *)(LONG_PTR
)lppt
->y
)) return FALSE
;
2727 LISTVIEW_InvalidateItem(infoPtr
, nItem
);
2734 * Arranges listview items in icon display mode.
2737 * [I] infoPtr : valid pointer to the listview structure
2738 * [I] nAlignCode : alignment code
2744 static BOOL
LISTVIEW_Arrange(LISTVIEW_INFO
*infoPtr
, INT nAlignCode
)
2746 void (*next_pos
)(LISTVIEW_INFO
*, LPPOINT
);
2750 if (infoPtr
->uView
!= LV_VIEW_ICON
&& infoPtr
->uView
!= LV_VIEW_SMALLICON
) return FALSE
;
2752 TRACE("nAlignCode=%d\n", nAlignCode
);
2754 if (nAlignCode
== LVA_DEFAULT
)
2756 if (infoPtr
->dwStyle
& LVS_ALIGNLEFT
) nAlignCode
= LVA_ALIGNLEFT
;
2757 else nAlignCode
= LVA_ALIGNTOP
;
2762 case LVA_ALIGNLEFT
: next_pos
= LISTVIEW_NextIconPosLeft
; break;
2763 case LVA_ALIGNTOP
: next_pos
= LISTVIEW_NextIconPosTop
; break;
2764 case LVA_SNAPTOGRID
: next_pos
= LISTVIEW_NextIconPosTop
; break; /* FIXME */
2765 default: return FALSE
;
2768 infoPtr
->bAutoarrange
= TRUE
;
2769 infoPtr
->currIconPos
.x
= infoPtr
->currIconPos
.y
= 0;
2770 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2772 next_pos(infoPtr
, &pos
);
2773 LISTVIEW_MoveIconTo(infoPtr
, i
, &pos
, FALSE
);
2781 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2782 * For LVS_REPORT always returns empty rectangle.
2785 * [I] infoPtr : valid pointer to the listview structure
2786 * [O] lprcView : bounding rectangle
2792 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO
*infoPtr
, LPRECT lprcView
)
2796 SetRectEmpty(lprcView
);
2798 switch (infoPtr
->uView
)
2801 case LV_VIEW_SMALLICON
:
2802 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2804 x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, i
);
2805 y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, i
);
2806 lprcView
->right
= max(lprcView
->right
, x
);
2807 lprcView
->bottom
= max(lprcView
->bottom
, y
);
2809 if (infoPtr
->nItemCount
> 0)
2811 lprcView
->right
+= infoPtr
->nItemWidth
;
2812 lprcView
->bottom
+= infoPtr
->nItemHeight
;
2817 y
= LISTVIEW_GetCountPerColumn(infoPtr
);
2818 x
= infoPtr
->nItemCount
/ y
;
2819 if (infoPtr
->nItemCount
% y
) x
++;
2820 lprcView
->right
= x
* infoPtr
->nItemWidth
;
2821 lprcView
->bottom
= y
* infoPtr
->nItemHeight
;
2828 * Retrieves the bounding rectangle of all the items.
2831 * [I] infoPtr : valid pointer to the listview structure
2832 * [O] lprcView : bounding rectangle
2838 static BOOL
LISTVIEW_GetViewRect(const LISTVIEW_INFO
*infoPtr
, LPRECT lprcView
)
2842 TRACE("(lprcView=%p)\n", lprcView
);
2844 if (!lprcView
) return FALSE
;
2846 LISTVIEW_GetAreaRect(infoPtr
, lprcView
);
2848 if (infoPtr
->uView
!= LV_VIEW_DETAILS
)
2850 LISTVIEW_GetOrigin(infoPtr
, &ptOrigin
);
2851 OffsetRect(lprcView
, ptOrigin
.x
, ptOrigin
.y
);
2854 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView
));
2861 * Retrieves the subitem pointer associated with the subitem index.
2864 * [I] hdpaSubItems : DPA handle for a specific item
2865 * [I] nSubItem : index of subitem
2868 * SUCCESS : subitem pointer
2871 static SUBITEM_INFO
* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems
, INT nSubItem
)
2873 SUBITEM_INFO
*lpSubItem
;
2876 /* we should binary search here if need be */
2877 for (i
= 1; i
< DPA_GetPtrCount(hdpaSubItems
); i
++)
2879 lpSubItem
= DPA_GetPtr(hdpaSubItems
, i
);
2880 if (lpSubItem
->iSubItem
== nSubItem
)
2890 * Calculates the desired item width.
2893 * [I] infoPtr : valid pointer to the listview structure
2896 * The desired item width.
2898 static INT
LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO
*infoPtr
)
2902 TRACE("uView=%d\n", infoPtr
->uView
);
2904 if (infoPtr
->uView
== LV_VIEW_ICON
)
2905 nItemWidth
= infoPtr
->iconSpacing
.cx
;
2906 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2908 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
) > 0)
2913 index
= SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
,
2914 DPA_GetPtrCount(infoPtr
->hdpaColumns
) - 1, 0);
2916 LISTVIEW_GetHeaderRect(infoPtr
, index
, &rcHeader
);
2917 nItemWidth
= rcHeader
.right
;
2920 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2922 WCHAR szDispText
[DISP_TEXT_SIZE
] = { '\0' };
2926 lvItem
.mask
= LVIF_TEXT
;
2927 lvItem
.iSubItem
= 0;
2929 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2932 lvItem
.pszText
= szDispText
;
2933 lvItem
.cchTextMax
= DISP_TEXT_SIZE
;
2934 if (LISTVIEW_GetItemW(infoPtr
, &lvItem
))
2935 nItemWidth
= max(LISTVIEW_GetStringWidthT(infoPtr
, lvItem
.pszText
, TRUE
),
2939 if (infoPtr
->himlSmall
) nItemWidth
+= infoPtr
->iconSize
.cx
;
2940 if (infoPtr
->himlState
) nItemWidth
+= infoPtr
->iconStateSize
.cx
;
2942 nItemWidth
= max(DEFAULT_COLUMN_WIDTH
, nItemWidth
+ WIDTH_PADDING
);
2950 * Calculates the desired item height.
2953 * [I] infoPtr : valid pointer to the listview structure
2956 * The desired item height.
2958 static INT
LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO
*infoPtr
)
2962 TRACE("uView=%d\n", infoPtr
->uView
);
2964 if (infoPtr
->uView
== LV_VIEW_ICON
)
2965 nItemHeight
= infoPtr
->iconSpacing
.cy
;
2968 nItemHeight
= infoPtr
->ntmHeight
;
2969 if (infoPtr
->himlState
)
2970 nItemHeight
= max(nItemHeight
, infoPtr
->iconStateSize
.cy
);
2971 if (infoPtr
->himlSmall
)
2972 nItemHeight
= max(nItemHeight
, infoPtr
->iconSize
.cy
);
2973 nItemHeight
+= HEIGHT_PADDING
;
2974 if (infoPtr
->nMeasureItemHeight
> 0)
2975 nItemHeight
= infoPtr
->nMeasureItemHeight
;
2978 return max(nItemHeight
, 1);
2983 * Updates the width, and height of an item.
2986 * [I] infoPtr : valid pointer to the listview structure
2991 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO
*infoPtr
)
2993 infoPtr
->nItemWidth
= LISTVIEW_CalculateItemWidth(infoPtr
);
2994 infoPtr
->nItemHeight
= LISTVIEW_CalculateItemHeight(infoPtr
);
3000 * Retrieves and saves important text metrics info for the current
3004 * [I] infoPtr : valid pointer to the listview structure
3007 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO
*infoPtr
)
3009 HDC hdc
= GetDC(infoPtr
->hwndSelf
);
3010 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
3011 HFONT hOldFont
= SelectObject(hdc
, hFont
);
3015 if (GetTextMetricsW(hdc
, &tm
))
3017 infoPtr
->ntmHeight
= tm
.tmHeight
;
3018 infoPtr
->ntmMaxCharWidth
= tm
.tmMaxCharWidth
;
3021 if (GetTextExtentPoint32A(hdc
, "...", 3, &sz
))
3022 infoPtr
->nEllipsisWidth
= sz
.cx
;
3024 SelectObject(hdc
, hOldFont
);
3025 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
3027 TRACE("tmHeight=%d\n", infoPtr
->ntmHeight
);
3032 * A compare function for ranges
3035 * [I] range1 : pointer to range 1;
3036 * [I] range2 : pointer to range 2;
3040 * > 0 : if range 1 > range 2
3041 * < 0 : if range 2 > range 1
3042 * = 0 : if range intersects range 2
3044 static INT CALLBACK
ranges_cmp(LPVOID range1
, LPVOID range2
, LPARAM flags
)
3048 if (((RANGE
*)range1
)->upper
<= ((RANGE
*)range2
)->lower
)
3050 else if (((RANGE
*)range2
)->upper
<= ((RANGE
*)range1
)->lower
)
3055 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1
), debugrange(range2
), cmp
);
3060 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3062 static void ranges_assert(RANGES ranges
, LPCSTR desc
, const char *file
, int line
)
3067 TRACE("*** Checking %s:%d:%s ***\n", file
, line
, desc
);
3069 assert (DPA_GetPtrCount(ranges
->hdpa
) >= 0);
3070 ranges_dump(ranges
);
3071 if (DPA_GetPtrCount(ranges
->hdpa
) > 0)
3073 prev
= DPA_GetPtr(ranges
->hdpa
, 0);
3074 assert (prev
->lower
>= 0 && prev
->lower
< prev
->upper
);
3075 for (i
= 1; i
< DPA_GetPtrCount(ranges
->hdpa
); i
++)
3077 curr
= DPA_GetPtr(ranges
->hdpa
, i
);
3078 assert (prev
->upper
<= curr
->lower
);
3079 assert (curr
->lower
< curr
->upper
);
3083 TRACE("--- Done checking---\n");
3086 static RANGES
ranges_create(int count
)