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-2011 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
28 * This code was audited for completeness against the documented features
29 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
31 * Unless otherwise noted, we believe this code to be complete, as per
32 * the specification mentioned above.
33 * If you discover missing features, or bugs, please note them below.
37 * Default Message Processing
38 * -- WM_CREATE: create the icon and small icon image lists at this point only if
39 * the LVS_SHAREIMAGELISTS style is not specified.
40 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
41 * or small icon and the LVS_AUTOARRANGE style is specified.
46 * -- Hot item handling, mouse hovering
47 * -- Workareas support
52 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
53 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
54 * -- LVA_SNAPTOGRID not implemented
55 * -- LISTVIEW_ApproximateViewRect partially implemented
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
82 * -- LVS_NOSCROLL (see Q137520)
86 * -- LVS_EX_BORDERSELECT
90 * -- LVS_EX_MULTIWORKAREAS
92 * -- LVS_EX_SIMPLESELECT
93 * -- LVS_EX_TWOCLICKACTIVATE
94 * -- LVS_EX_UNDERLINECOLD
95 * -- LVS_EX_UNDERLINEHOT
98 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
105 * -- LVM_ENABLEGROUPVIEW
106 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
107 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
108 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
109 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
110 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
111 * -- LVM_GETINSERTMARKRECT
112 * -- LVM_GETNUMBEROFWORKAREAS
113 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
114 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
115 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
116 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
117 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
118 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
119 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
120 * -- LVM_INSERTGROUPSORTED
121 * -- LVM_INSERTMARKHITTEST
122 * -- LVM_ISGROUPVIEWENABLED
124 * -- LVM_MOVEITEMTOGROUP
126 * -- LVM_SETTILEWIDTH
130 * -- ListView_GetHoverTime, ListView_SetHoverTime
131 * -- ListView_GetISearchString
132 * -- ListView_GetNumberOfWorkAreas
133 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
138 * Known differences in message stream from native control (not known if
139 * these differences cause problems):
140 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
141 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
142 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
143 * processing for "USEDOUBLECLICKTIME".
147 #include "wine/port.h"
162 #include "commctrl.h"
163 #include "comctl32.h"
166 #include "wine/debug.h"
167 #include "wine/unicode.h"
169 WINE_DEFAULT_DEBUG_CHANNEL(listview
);
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader
; /* tracks the header's rectangle */
174 INT fmt
; /* same as LVCOLUMN.fmt */
178 typedef struct tagITEMHDR
182 } ITEMHDR
, *LPITEMHDR
;
184 typedef struct tagSUBITEM_INFO
190 typedef struct tagITEM_ID ITEM_ID
;
192 typedef struct tagITEM_INFO
203 UINT id
; /* item id */
204 HDPA item
; /* link to item data */
207 typedef struct tagRANGE
213 typedef struct tagRANGES
218 typedef struct tagITERATOR
227 typedef struct tagDELAYED_ITEM_EDIT
233 typedef struct tagLISTVIEW_INFO
237 RECT rcList
; /* This rectangle is really the window
238 * client rectangle possibly reduced by the
239 * horizontal scroll bar and/or header - see
240 * LISTVIEW_UpdateSize. This rectangle offset
241 * by the LISTVIEW_GetOrigin value is in
242 * client coordinates */
244 /* notification window */
247 BOOL bDoChangeNotify
; /* send change notification messages? */
254 INT nItemCount
; /* the number of items in the list */
255 HDPA hdpaItems
; /* array ITEM_INFO pointers */
256 HDPA hdpaItemIds
; /* array of ITEM_ID pointers */
257 HDPA hdpaPosX
; /* maintains the (X, Y) coordinates of the */
258 HDPA hdpaPosY
; /* items in LVS_ICON, and LVS_SMALLICON modes */
259 RANGES selectionRanges
;
260 INT nSelectionMark
; /* item to start next multiselection from */
262 BOOL bAutoarrange
; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
265 HDPA hdpaColumns
; /* array of COLUMN_INFO pointers */
266 BOOL colRectsDirty
; /* trigger column rectangles requery from header */
269 BOOL bNoItemMetrics
; /* flags if item metrics are not yet computed */
274 PFNLVCOMPARE pfnCompare
; /* sorting callback pointer */
278 DWORD dwStyle
; /* the cached window GWL_STYLE */
279 DWORD dwLvExStyle
; /* extended listview style */
280 DWORD uView
; /* current view available through LVM_[G,S]ETVIEW */
286 DELAYED_ITEM_EDIT itemEdit
; /* Pointer to this structure will be the timer ID */
289 HIMAGELIST himlNormal
;
290 HIMAGELIST himlSmall
;
291 HIMAGELIST himlState
;
295 POINT currIconPos
; /* this is the position next icon will be placed */
299 INT xTrackLine
; /* The x coefficient of the track line or -1 if none */
301 /* marquee selection */
302 BOOL bMarqueeSelect
; /* marquee selection/highlight underway */
304 RECT marqueeRect
; /* absolute coordinates of marquee selection */
305 RECT marqueeDrawRect
; /* relative coordinates for drawing marquee */
306 POINT marqueeOrigin
; /* absolute coordinates of marquee click origin */
309 BOOL bFocus
; /* control has focus */
311 RECT rcFocus
; /* focus bounds */
318 BOOL bDefaultBkColor
;
323 INT ntmHeight
; /* Some cached metrics of the font used */
324 INT ntmMaxCharWidth
; /* by the listview to draw items */
327 /* mouse operation */
331 POINT ptClickPos
; /* point where the user clicked */
332 INT nLButtonDownItem
; /* tracks item to reset multiselection on WM_LBUTTONUP */
336 /* keyboard operation */
337 DWORD lastKeyPressTimestamp
;
339 INT nSearchParamLength
;
340 WCHAR szSearchParam
[ MAX_PATH
];
343 DWORD cditemmode
; /* Keep the custom draw flags for an item/row */
344 BOOL bIsDrawing
; /* Drawing in progress */
345 INT nMeasureItemHeight
; /* WM_MEASUREITEM result */
346 BOOL bRedraw
; /* WM_SETREDRAW switch */
349 DWORD iVersion
; /* CCM_[G,S]ETVERSION */
355 /* How many we debug buffer to allocate */
356 #define DEBUG_BUFFERS 20
357 /* The size of a single debug buffer */
358 #define DEBUG_BUFFER_SIZE 256
360 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
361 #define SB_INTERNAL -1
363 /* maximum size of a label */
364 #define DISP_TEXT_SIZE 260
366 /* padding for items in list and small icon display modes */
367 #define WIDTH_PADDING 12
369 /* padding for items in list, report and small icon display modes */
370 #define HEIGHT_PADDING 1
372 /* offset of items in report display mode */
373 #define REPORT_MARGINX 2
375 /* padding for icon in large icon display mode
376 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
377 * that HITTEST will see.
378 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
379 * ICON_TOP_PADDING - sum of the two above.
380 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
381 * LABEL_HOR_PADDING - between text and sides of box
382 * LABEL_VERT_PADDING - between bottom of text and end of box
384 * ICON_LR_PADDING - additional width above icon size.
385 * ICON_LR_HALF - half of the above value
387 #define ICON_TOP_PADDING_NOTHITABLE 2
388 #define ICON_TOP_PADDING_HITABLE 2
389 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
390 #define ICON_BOTTOM_PADDING 4
391 #define LABEL_HOR_PADDING 5
392 #define LABEL_VERT_PADDING 7
393 #define ICON_LR_PADDING 16
394 #define ICON_LR_HALF (ICON_LR_PADDING/2)
396 /* default label width for items in list and small icon display modes */
397 #define DEFAULT_LABEL_WIDTH 40
398 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
399 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
401 /* default column width for items in list display mode */
402 #define DEFAULT_COLUMN_WIDTH 128
404 /* Size of "line" scroll for V & H scrolls */
405 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
407 /* Padding between image and label */
408 #define IMAGE_PADDING 2
410 /* Padding behind the label */
411 #define TRAILING_LABEL_PADDING 12
412 #define TRAILING_HEADER_PADDING 11
414 /* Border for the icon caption */
415 #define CAPTION_BORDER 2
417 /* Standard DrawText flags */
418 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
419 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
420 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
422 /* Image index from state */
423 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
425 /* The time in milliseconds to reset the search in the list */
426 #define KEY_DELAY 450
428 /* Dump the LISTVIEW_INFO structure to the debug channel */
429 #define LISTVIEW_DUMP(iP) do { \
430 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
431 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
432 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
433 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
434 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
435 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
436 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
437 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
438 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
439 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
442 static const WCHAR themeClass
[] = {'L','i','s','t','V','i','e','w',0};
445 * forward declarations
447 static BOOL
LISTVIEW_GetItemT(const LISTVIEW_INFO
*, LPLVITEMW
, BOOL
);
448 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO
*, INT
, LPRECT
);
449 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO
*, INT
, LPPOINT
);
450 static BOOL
LISTVIEW_GetItemPosition(const LISTVIEW_INFO
*, INT
, LPPOINT
);
451 static BOOL
LISTVIEW_GetItemRect(const LISTVIEW_INFO
*, INT
, LPRECT
);
452 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO
*, LPPOINT
);
453 static BOOL
LISTVIEW_GetViewRect(const LISTVIEW_INFO
*, LPRECT
);
454 static void LISTVIEW_UpdateSize(LISTVIEW_INFO
*);
455 static LRESULT
LISTVIEW_Command(LISTVIEW_INFO
*, WPARAM
, LPARAM
);
456 static INT
LISTVIEW_GetStringWidthT(const LISTVIEW_INFO
*, LPCWSTR
, BOOL
);
457 static BOOL
LISTVIEW_KeySelection(LISTVIEW_INFO
*, INT
, BOOL
);
458 static UINT
LISTVIEW_GetItemState(const LISTVIEW_INFO
*, INT
, UINT
);
459 static BOOL
LISTVIEW_SetItemState(LISTVIEW_INFO
*, INT
, const LVITEMW
*);
460 static LRESULT
LISTVIEW_VScroll(LISTVIEW_INFO
*, INT
, INT
);
461 static LRESULT
LISTVIEW_HScroll(LISTVIEW_INFO
*, INT
, INT
);
462 static BOOL
LISTVIEW_EnsureVisible(LISTVIEW_INFO
*, INT
, BOOL
);
463 static HIMAGELIST
LISTVIEW_SetImageList(LISTVIEW_INFO
*, INT
, HIMAGELIST
);
464 static INT
LISTVIEW_HitTest(const LISTVIEW_INFO
*, LPLVHITTESTINFO
, BOOL
, BOOL
);
465 static BOOL
LISTVIEW_EndEditLabelT(LISTVIEW_INFO
*, BOOL
, BOOL
);
466 static BOOL
LISTVIEW_Scroll(LISTVIEW_INFO
*, INT
, INT
);
468 /******** Text handling functions *************************************/
470 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
471 * text string. The string may be ANSI or Unicode, in which case
472 * the boolean isW tells us the type of the string.
474 * The name of the function tell what type of strings it expects:
475 * W: Unicode, T: ANSI/Unicode - function of isW
478 static inline BOOL
is_text(LPCWSTR text
)
480 return text
!= NULL
&& text
!= LPSTR_TEXTCALLBACKW
;
483 static inline int textlenT(LPCWSTR text
, BOOL isW
)
485 return !is_text(text
) ? 0 :
486 isW
? lstrlenW(text
) : lstrlenA((LPCSTR
)text
);
489 static inline void textcpynT(LPWSTR dest
, BOOL isDestW
, LPCWSTR src
, BOOL isSrcW
, INT max
)
492 if (isSrcW
) lstrcpynW(dest
, src
, max
);
493 else MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)src
, -1, dest
, max
);
495 if (isSrcW
) WideCharToMultiByte(CP_ACP
, 0, src
, -1, (LPSTR
)dest
, max
, NULL
, NULL
);
496 else lstrcpynA((LPSTR
)dest
, (LPCSTR
)src
, max
);
499 static inline LPWSTR
textdupTtoW(LPCWSTR text
, BOOL isW
)
501 LPWSTR wstr
= (LPWSTR
)text
;
503 if (!isW
&& is_text(text
))
505 INT len
= MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)text
, -1, NULL
, 0);
506 wstr
= Alloc(len
* sizeof(WCHAR
));
507 if (wstr
) MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)text
, -1, wstr
, len
);
509 TRACE(" wstr=%s\n", text
== LPSTR_TEXTCALLBACKW
? "(callback)" : debugstr_w(wstr
));
513 static inline void textfreeT(LPWSTR wstr
, BOOL isW
)
515 if (!isW
&& is_text(wstr
)) Free (wstr
);
519 * dest is a pointer to a Unicode string
520 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
522 static BOOL
textsetptrT(LPWSTR
*dest
, LPCWSTR src
, BOOL isW
)
526 if (src
== LPSTR_TEXTCALLBACKW
)
528 if (is_text(*dest
)) Free(*dest
);
529 *dest
= LPSTR_TEXTCALLBACKW
;
533 LPWSTR pszText
= textdupTtoW(src
, isW
);
534 if (*dest
== LPSTR_TEXTCALLBACKW
) *dest
= NULL
;
535 bResult
= Str_SetPtrW(dest
, pszText
);
536 textfreeT(pszText
, isW
);
542 * compares a Unicode to a Unicode/ANSI text string
544 static inline int textcmpWT(LPCWSTR aw
, LPCWSTR bt
, BOOL isW
)
546 if (!aw
) return bt
? -1 : 0;
548 if (aw
== LPSTR_TEXTCALLBACKW
)
549 return bt
== LPSTR_TEXTCALLBACKW
? 1 : -1;
550 if (bt
!= LPSTR_TEXTCALLBACKW
)
552 LPWSTR bw
= textdupTtoW(bt
, isW
);
553 int r
= bw
? lstrcmpW(aw
, bw
) : 1;
561 static inline int lstrncmpiW(LPCWSTR s1
, LPCWSTR s2
, int n
)
565 n
= min(min(n
, lstrlenW(s1
)), lstrlenW(s2
));
566 res
= CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
, s1
, n
, s2
, n
);
567 return res
? res
- sizeof(WCHAR
) : res
;
570 /******** Debugging functions *****************************************/
572 static inline LPCSTR
debugtext_t(LPCWSTR text
, BOOL isW
)
574 if (text
== LPSTR_TEXTCALLBACKW
) return "(callback)";
575 return isW
? debugstr_w(text
) : debugstr_a((LPCSTR
)text
);
578 static inline LPCSTR
debugtext_tn(LPCWSTR text
, BOOL isW
, INT n
)
580 if (text
== LPSTR_TEXTCALLBACKW
) return "(callback)";
581 n
= min(textlenT(text
, isW
), n
);
582 return isW
? debugstr_wn(text
, n
) : debugstr_an((LPCSTR
)text
, n
);
585 static char* debug_getbuf(void)
587 static int index
= 0;
588 static char buffers
[DEBUG_BUFFERS
][DEBUG_BUFFER_SIZE
];
589 return buffers
[index
++ % DEBUG_BUFFERS
];
592 static inline const char* debugrange(const RANGE
*lprng
)
594 if (!lprng
) return "(null)";
595 return wine_dbg_sprintf("[%d, %d]", lprng
->lower
, lprng
->upper
);
598 static const char* debugscrollinfo(const SCROLLINFO
*pScrollInfo
)
600 char* buf
= debug_getbuf(), *text
= buf
;
601 int len
, size
= DEBUG_BUFFER_SIZE
;
603 if (pScrollInfo
== NULL
) return "(null)";
604 len
= snprintf(buf
, size
, "{cbSize=%d, ", pScrollInfo
->cbSize
);
605 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
606 if (pScrollInfo
->fMask
& SIF_RANGE
)
607 len
= snprintf(buf
, size
, "nMin=%d, nMax=%d, ", pScrollInfo
->nMin
, pScrollInfo
->nMax
);
609 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
610 if (pScrollInfo
->fMask
& SIF_PAGE
)
611 len
= snprintf(buf
, size
, "nPage=%u, ", pScrollInfo
->nPage
);
613 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
614 if (pScrollInfo
->fMask
& SIF_POS
)
615 len
= snprintf(buf
, size
, "nPos=%d, ", pScrollInfo
->nPos
);
617 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
618 if (pScrollInfo
->fMask
& SIF_TRACKPOS
)
619 len
= snprintf(buf
, size
, "nTrackPos=%d, ", pScrollInfo
->nTrackPos
);
621 if (len
== -1) goto end
; buf
+= len
;
624 buf
= text
+ strlen(text
);
626 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
630 static const char* debugnmlistview(const NMLISTVIEW
*plvnm
)
632 if (!plvnm
) return "(null)";
633 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
634 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
635 plvnm
->iItem
, plvnm
->iSubItem
, plvnm
->uNewState
, plvnm
->uOldState
,
636 plvnm
->uChanged
, wine_dbgstr_point(&plvnm
->ptAction
), plvnm
->lParam
);
639 static const char* debuglvitem_t(const LVITEMW
*lpLVItem
, BOOL isW
)
641 char* buf
= debug_getbuf(), *text
= buf
;
642 int len
, size
= DEBUG_BUFFER_SIZE
;
644 if (lpLVItem
== NULL
) return "(null)";
645 len
= snprintf(buf
, size
, "{iItem=%d, iSubItem=%d, ", lpLVItem
->iItem
, lpLVItem
->iSubItem
);
646 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
647 if (lpLVItem
->mask
& LVIF_STATE
)
648 len
= snprintf(buf
, size
, "state=%x, stateMask=%x, ", lpLVItem
->state
, lpLVItem
->stateMask
);
650 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
651 if (lpLVItem
->mask
& LVIF_TEXT
)
652 len
= snprintf(buf
, size
, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem
->pszText
, isW
, 80), lpLVItem
->cchTextMax
);
654 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
655 if (lpLVItem
->mask
& LVIF_IMAGE
)
656 len
= snprintf(buf
, size
, "iImage=%d, ", lpLVItem
->iImage
);
658 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
659 if (lpLVItem
->mask
& LVIF_PARAM
)
660 len
= snprintf(buf
, size
, "lParam=%lx, ", lpLVItem
->lParam
);
662 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
663 if (lpLVItem
->mask
& LVIF_INDENT
)
664 len
= snprintf(buf
, size
, "iIndent=%d, ", lpLVItem
->iIndent
);
666 if (len
== -1) goto end
; buf
+= len
;
669 buf
= text
+ strlen(text
);
671 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
675 static const char* debuglvcolumn_t(const LVCOLUMNW
*lpColumn
, BOOL isW
)
677 char* buf
= debug_getbuf(), *text
= buf
;
678 int len
, size
= DEBUG_BUFFER_SIZE
;
680 if (lpColumn
== NULL
) return "(null)";
681 len
= snprintf(buf
, size
, "{");
682 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
683 if (lpColumn
->mask
& LVCF_SUBITEM
)
684 len
= snprintf(buf
, size
, "iSubItem=%d, ", lpColumn
->iSubItem
);
686 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
687 if (lpColumn
->mask
& LVCF_FMT
)
688 len
= snprintf(buf
, size
, "fmt=%x, ", lpColumn
->fmt
);
690 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
691 if (lpColumn
->mask
& LVCF_WIDTH
)
692 len
= snprintf(buf
, size
, "cx=%d, ", lpColumn
->cx
);
694 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
695 if (lpColumn
->mask
& LVCF_TEXT
)
696 len
= snprintf(buf
, size
, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn
->pszText
, isW
, 80), lpColumn
->cchTextMax
);
698 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
699 if (lpColumn
->mask
& LVCF_IMAGE
)
700 len
= snprintf(buf
, size
, "iImage=%d, ", lpColumn
->iImage
);
702 if (len
== -1) goto end
; buf
+= len
; size
-= len
;
703 if (lpColumn
->mask
& LVCF_ORDER
)
704 len
= snprintf(buf
, size
, "iOrder=%d, ", lpColumn
->iOrder
);
706 if (len
== -1) goto end
; buf
+= len
;
709 buf
= text
+ strlen(text
);
711 if (buf
- text
> 2) { buf
[-2] = '}'; buf
[-1] = 0; }
715 static const char* debuglvhittestinfo(const LVHITTESTINFO
*lpht
)
717 if (!lpht
) return "(null)";
719 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
720 wine_dbgstr_point(&lpht
->pt
), lpht
->flags
, lpht
->iItem
, lpht
->iSubItem
);
723 /* Return the corresponding text for a given scroll value */
724 static inline LPCSTR
debugscrollcode(int nScrollCode
)
728 case SB_LINELEFT
: return "SB_LINELEFT";
729 case SB_LINERIGHT
: return "SB_LINERIGHT";
730 case SB_PAGELEFT
: return "SB_PAGELEFT";
731 case SB_PAGERIGHT
: return "SB_PAGERIGHT";
732 case SB_THUMBPOSITION
: return "SB_THUMBPOSITION";
733 case SB_THUMBTRACK
: return "SB_THUMBTRACK";
734 case SB_ENDSCROLL
: return "SB_ENDSCROLL";
735 case SB_INTERNAL
: return "SB_INTERNAL";
736 default: return "unknown";
741 /******** Notification functions ************************************/
743 static int get_ansi_notification(UINT unicodeNotificationCode
)
745 switch (unicodeNotificationCode
)
747 case LVN_BEGINLABELEDITA
:
748 case LVN_BEGINLABELEDITW
: return LVN_BEGINLABELEDITA
;
749 case LVN_ENDLABELEDITA
:
750 case LVN_ENDLABELEDITW
: return LVN_ENDLABELEDITA
;
751 case LVN_GETDISPINFOA
:
752 case LVN_GETDISPINFOW
: return LVN_GETDISPINFOA
;
753 case LVN_SETDISPINFOA
:
754 case LVN_SETDISPINFOW
: return LVN_SETDISPINFOA
;
755 case LVN_ODFINDITEMA
:
756 case LVN_ODFINDITEMW
: return LVN_ODFINDITEMA
;
757 case LVN_GETINFOTIPA
:
758 case LVN_GETINFOTIPW
: return LVN_GETINFOTIPA
;
759 /* header forwards */
761 case HDN_TRACKW
: return HDN_TRACKA
;
763 case HDN_ENDTRACKW
: return HDN_ENDTRACKA
;
764 case HDN_BEGINDRAG
: return HDN_BEGINDRAG
;
765 case HDN_ENDDRAG
: return HDN_ENDDRAG
;
766 case HDN_ITEMCHANGINGA
:
767 case HDN_ITEMCHANGINGW
: return HDN_ITEMCHANGINGA
;
768 case HDN_ITEMCHANGEDA
:
769 case HDN_ITEMCHANGEDW
: return HDN_ITEMCHANGEDA
;
771 case HDN_ITEMCLICKW
: return HDN_ITEMCLICKA
;
772 case HDN_DIVIDERDBLCLICKA
:
773 case HDN_DIVIDERDBLCLICKW
: return HDN_DIVIDERDBLCLICKA
;
776 FIXME("unknown notification %x\n", unicodeNotificationCode
);
777 return unicodeNotificationCode
;
780 /* forwards header notifications to listview parent */
781 static LRESULT
notify_forward_header(const LISTVIEW_INFO
*infoPtr
, const NMHEADERW
*lpnmh
)
785 HD_TEXTFILTERA textfilter
;
786 LPSTR text
= NULL
, filter
= NULL
;
789 /* on unicode format exit earlier */
790 if (infoPtr
->notifyFormat
== NFR_UNICODE
)
791 return SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, lpnmh
->hdr
.idFrom
,
794 /* header always supplies unicode notifications,
795 all we have to do is to convert strings to ANSI */
796 nmhA
= *(const NMHEADERA
*)lpnmh
;
799 hditema
= *(HDITEMA
*)lpnmh
->pitem
;
800 nmhA
.pitem
= &hditema
;
801 /* convert item text */
802 if (lpnmh
->pitem
->mask
& HDI_TEXT
)
804 hditema
.pszText
= NULL
;
805 Str_SetPtrWtoA(&hditema
.pszText
, lpnmh
->pitem
->pszText
);
806 text
= hditema
.pszText
;
808 /* convert filter text */
809 if ((lpnmh
->pitem
->mask
& HDI_FILTER
) && (lpnmh
->pitem
->type
== HDFT_ISSTRING
) &&
810 lpnmh
->pitem
->pvFilter
)
812 hditema
.pvFilter
= &textfilter
;
813 textfilter
= *(HD_TEXTFILTERA
*)(lpnmh
->pitem
->pvFilter
);
814 textfilter
.pszText
= NULL
;
815 Str_SetPtrWtoA(&textfilter
.pszText
, ((HD_TEXTFILTERW
*)lpnmh
->pitem
->pvFilter
)->pszText
);
816 filter
= textfilter
.pszText
;
819 nmhA
.hdr
.code
= get_ansi_notification(lpnmh
->hdr
.code
);
821 ret
= SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, nmhA
.hdr
.idFrom
,
831 static LRESULT
notify_hdr(const LISTVIEW_INFO
*infoPtr
, INT code
, LPNMHDR pnmh
)
835 TRACE("(code=%d)\n", code
);
837 pnmh
->hwndFrom
= infoPtr
->hwndSelf
;
838 pnmh
->idFrom
= GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
840 result
= SendMessageW(infoPtr
->hwndNotify
, WM_NOTIFY
, pnmh
->idFrom
, (LPARAM
)pnmh
);
842 TRACE(" <= %ld\n", result
);
847 static inline BOOL
notify(const LISTVIEW_INFO
*infoPtr
, INT code
)
850 HWND hwnd
= infoPtr
->hwndSelf
;
851 notify_hdr(infoPtr
, code
, &nmh
);
852 return IsWindow(hwnd
);
855 static inline void notify_itemactivate(const LISTVIEW_INFO
*infoPtr
, const LVHITTESTINFO
*htInfo
)
866 item
.mask
= LVIF_PARAM
|LVIF_STATE
;
867 item
.iItem
= htInfo
->iItem
;
869 item
.stateMask
= (UINT
)-1;
870 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) {
871 nmia
.lParam
= item
.lParam
;
872 nmia
.uOldState
= item
.state
;
873 nmia
.uNewState
= item
.state
| LVIS_ACTIVATING
;
874 nmia
.uChanged
= LVIF_STATE
;
877 nmia
.iItem
= htInfo
->iItem
;
878 nmia
.iSubItem
= htInfo
->iSubItem
;
879 nmia
.ptAction
= htInfo
->pt
;
881 if (GetKeyState(VK_SHIFT
) & 0x8000) nmia
.uKeyFlags
|= LVKF_SHIFT
;
882 if (GetKeyState(VK_CONTROL
) & 0x8000) nmia
.uKeyFlags
|= LVKF_CONTROL
;
883 if (GetKeyState(VK_MENU
) & 0x8000) nmia
.uKeyFlags
|= LVKF_ALT
;
885 notify_hdr(infoPtr
, LVN_ITEMACTIVATE
, (LPNMHDR
)&nmia
);
888 static inline LRESULT
notify_listview(const LISTVIEW_INFO
*infoPtr
, INT code
, LPNMLISTVIEW plvnm
)
890 TRACE("(code=%d, plvnm=%s)\n", code
, debugnmlistview(plvnm
));
891 return notify_hdr(infoPtr
, code
, (LPNMHDR
)plvnm
);
894 static BOOL
notify_click(const LISTVIEW_INFO
*infoPtr
, INT code
, const LVHITTESTINFO
*lvht
)
898 HWND hwnd
= infoPtr
->hwndSelf
;
900 TRACE("code=%d, lvht=%s\n", code
, debuglvhittestinfo(lvht
));
901 ZeroMemory(&nmia
, sizeof(nmia
));
902 nmia
.iItem
= lvht
->iItem
;
903 nmia
.iSubItem
= lvht
->iSubItem
;
904 nmia
.ptAction
= lvht
->pt
;
905 item
.mask
= LVIF_PARAM
;
906 item
.iItem
= lvht
->iItem
;
908 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) nmia
.lParam
= item
.lParam
;
909 notify_hdr(infoPtr
, code
, (LPNMHDR
)&nmia
);
910 return IsWindow(hwnd
);
913 static BOOL
notify_deleteitem(const LISTVIEW_INFO
*infoPtr
, INT nItem
)
917 HWND hwnd
= infoPtr
->hwndSelf
;
919 ZeroMemory(&nmlv
, sizeof (NMLISTVIEW
));
921 item
.mask
= LVIF_PARAM
;
924 if (LISTVIEW_GetItemT(infoPtr
, &item
, TRUE
)) nmlv
.lParam
= item
.lParam
;
925 notify_listview(infoPtr
, LVN_DELETEITEM
, &nmlv
);
926 return IsWindow(hwnd
);
930 Send notification. depends on dispinfoW having same
931 structure as dispinfoA.
932 infoPtr : listview struct
933 code : *Unicode* notification code
934 pdi : dispinfo structure (can be unicode or ansi)
935 isW : TRUE if dispinfo is Unicode
937 static BOOL
notify_dispinfoT(const LISTVIEW_INFO
*infoPtr
, UINT code
, LPNMLVDISPINFOW pdi
, BOOL isW
)
939 INT length
= 0, ret_length
;
940 LPWSTR buffer
= NULL
, ret_text
;
941 BOOL return_ansi
= FALSE
;
942 BOOL return_unicode
= FALSE
;
945 if ((pdi
->item
.mask
& LVIF_TEXT
) && is_text(pdi
->item
.pszText
))
947 return_unicode
= ( isW
&& infoPtr
->notifyFormat
== NFR_ANSI
);
948 return_ansi
= (!isW
&& infoPtr
->notifyFormat
== NFR_UNICODE
);
951 ret_length
= pdi
->item
.cchTextMax
;
952 ret_text
= pdi
->item
.pszText
;
954 if (return_unicode
|| return_ansi
)
956 if (code
!= LVN_GETDISPINFOW
)
958 length
= return_ansi
?
959 MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)pdi
->item
.pszText
, -1, NULL
, 0):
960 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, NULL
, 0, NULL
, NULL
);
964 length
= pdi
->item
.cchTextMax
;
965 *pdi
->item
.pszText
= 0; /* make sure we don't process garbage */
968 buffer
= Alloc( (return_ansi
? sizeof(WCHAR
) : sizeof(CHAR
)) * length
);
969 if (!buffer
) return FALSE
;
972 MultiByteToWideChar(CP_ACP
, 0, (LPCSTR
)pdi
->item
.pszText
, -1,
975 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) buffer
,
978 pdi
->item
.pszText
= buffer
;
979 pdi
->item
.cchTextMax
= length
;
982 if (infoPtr
->notifyFormat
== NFR_ANSI
)
983 code
= get_ansi_notification(code
);
985 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi
->item
, infoPtr
->notifyFormat
!= NFR_ANSI
));
986 ret
= notify_hdr(infoPtr
, code
, &pdi
->hdr
);
987 TRACE(" resulting code=%d\n", pdi
->hdr
.code
);
989 if (return_ansi
|| return_unicode
)
991 if (return_ansi
&& (pdi
->hdr
.code
== LVN_GETDISPINFOA
))
993 strcpy((char*)ret_text
, (char*)pdi
->item
.pszText
);
995 else if (return_unicode
&& (pdi
->hdr
.code
== LVN_GETDISPINFOW
))
997 strcpyW(ret_text
, pdi
->item
.pszText
);
999 else if (return_ansi
) /* note : pointer can be changed by app ! */
1001 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) ret_text
,
1002 ret_length
, NULL
, NULL
);
1005 MultiByteToWideChar(CP_ACP
, 0, (LPSTR
) pdi
->item
.pszText
, -1,
1006 ret_text
, ret_length
);
1008 pdi
->item
.pszText
= ret_text
; /* restores our buffer */
1009 pdi
->item
.cchTextMax
= ret_length
;
1015 /* if dipsinfo holder changed notification code then convert */
1016 if (!isW
&& (pdi
->hdr
.code
== LVN_GETDISPINFOW
) && (pdi
->item
.mask
& LVIF_TEXT
))
1018 length
= WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, NULL
, 0, NULL
, NULL
);
1020 buffer
= Alloc(length
* sizeof(CHAR
));
1021 if (!buffer
) return FALSE
;
1023 WideCharToMultiByte(CP_ACP
, 0, pdi
->item
.pszText
, -1, (LPSTR
) buffer
,
1024 ret_length
, NULL
, NULL
);
1026 strcpy((LPSTR
)pdi
->item
.pszText
, (LPSTR
)buffer
);
1033 static void customdraw_fill(NMLVCUSTOMDRAW
*lpnmlvcd
, const LISTVIEW_INFO
*infoPtr
, HDC hdc
,
1034 const RECT
*rcBounds
, const LVITEMW
*lplvItem
)
1036 ZeroMemory(lpnmlvcd
, sizeof(NMLVCUSTOMDRAW
));
1037 lpnmlvcd
->nmcd
.hdc
= hdc
;
1038 lpnmlvcd
->nmcd
.rc
= *rcBounds
;
1039 lpnmlvcd
->clrTextBk
= infoPtr
->clrTextBk
;
1040 lpnmlvcd
->clrText
= infoPtr
->clrText
;
1041 if (!lplvItem
) return;
1042 lpnmlvcd
->nmcd
.dwItemSpec
= lplvItem
->iItem
+ 1;
1043 lpnmlvcd
->iSubItem
= lplvItem
->iSubItem
;
1044 if (lplvItem
->state
& LVIS_SELECTED
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_SELECTED
;
1045 if (lplvItem
->state
& LVIS_FOCUSED
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_FOCUS
;
1046 if (lplvItem
->iItem
== infoPtr
->nHotItem
) lpnmlvcd
->nmcd
.uItemState
|= CDIS_HOT
;
1047 lpnmlvcd
->nmcd
.lItemlParam
= lplvItem
->lParam
;
1050 static inline DWORD
notify_customdraw (const LISTVIEW_INFO
*infoPtr
, DWORD dwDrawStage
, NMLVCUSTOMDRAW
*lpnmlvcd
)
1052 BOOL isForItem
= (lpnmlvcd
->nmcd
.dwItemSpec
!= 0);
1055 lpnmlvcd
->nmcd
.dwDrawStage
= dwDrawStage
;
1056 if (isForItem
) lpnmlvcd
->nmcd
.dwDrawStage
|= CDDS_ITEM
;
1057 if (lpnmlvcd
->iSubItem
) lpnmlvcd
->nmcd
.dwDrawStage
|= CDDS_SUBITEM
;
1058 if (isForItem
) lpnmlvcd
->nmcd
.dwItemSpec
--;
1059 result
= notify_hdr(infoPtr
, NM_CUSTOMDRAW
, &lpnmlvcd
->nmcd
.hdr
);
1060 if (isForItem
) lpnmlvcd
->nmcd
.dwItemSpec
++;
1064 static void prepaint_setup (const LISTVIEW_INFO
*infoPtr
, HDC hdc
, NMLVCUSTOMDRAW
*lpnmlvcd
, BOOL SubItem
)
1066 if (lpnmlvcd
->clrTextBk
== CLR_DEFAULT
)
1067 lpnmlvcd
->clrTextBk
= comctl32_color
.clrWindow
;
1068 if (lpnmlvcd
->clrText
== CLR_DEFAULT
)
1069 lpnmlvcd
->clrText
= comctl32_color
.clrWindowText
;
1071 /* apparently, for selected items, we have to override the returned values */
1074 if (lpnmlvcd
->nmcd
.uItemState
& CDIS_SELECTED
)
1076 if (infoPtr
->bFocus
)
1078 lpnmlvcd
->clrTextBk
= comctl32_color
.clrHighlight
;
1079 lpnmlvcd
->clrText
= comctl32_color
.clrHighlightText
;
1081 else if (infoPtr
->dwStyle
& LVS_SHOWSELALWAYS
)
1083 lpnmlvcd
->clrTextBk
= comctl32_color
.clr3dFace
;
1084 lpnmlvcd
->clrText
= comctl32_color
.clrBtnText
;
1089 /* Set the text attributes */
1090 if (lpnmlvcd
->clrTextBk
!= CLR_NONE
)
1092 SetBkMode(hdc
, OPAQUE
);
1093 SetBkColor(hdc
,lpnmlvcd
->clrTextBk
);
1096 SetBkMode(hdc
, TRANSPARENT
);
1097 SetTextColor(hdc
, lpnmlvcd
->clrText
);
1100 static inline DWORD
notify_postpaint (const LISTVIEW_INFO
*infoPtr
, NMLVCUSTOMDRAW
*lpnmlvcd
)
1102 return notify_customdraw(infoPtr
, CDDS_POSTPAINT
, lpnmlvcd
);
1105 /* returns TRUE when repaint needed, FALSE otherwise */
1106 static BOOL
notify_measureitem(LISTVIEW_INFO
*infoPtr
)
1108 MEASUREITEMSTRUCT mis
;
1109 mis
.CtlType
= ODT_LISTVIEW
;
1110 mis
.CtlID
= GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
1114 mis
.itemHeight
= infoPtr
->nItemHeight
;
1115 SendMessageW(infoPtr
->hwndNotify
, WM_MEASUREITEM
, mis
.CtlID
, (LPARAM
)&mis
);
1116 if (infoPtr
->nItemHeight
!= max(mis
.itemHeight
, 1))
1118 infoPtr
->nMeasureItemHeight
= infoPtr
->nItemHeight
= max(mis
.itemHeight
, 1);
1124 /******** Item iterator functions **********************************/
1126 static RANGES
ranges_create(int count
);
1127 static void ranges_destroy(RANGES ranges
);
1128 static BOOL
ranges_add(RANGES ranges
, RANGE range
);
1129 static BOOL
ranges_del(RANGES ranges
, RANGE range
);
1130 static void ranges_dump(RANGES ranges
);
1132 static inline BOOL
ranges_additem(RANGES ranges
, INT nItem
)
1134 RANGE range
= { nItem
, nItem
+ 1 };
1136 return ranges_add(ranges
, range
);
1139 static inline BOOL
ranges_delitem(RANGES ranges
, INT nItem
)
1141 RANGE range
= { nItem
, nItem
+ 1 };
1143 return ranges_del(ranges
, range
);
1147 * ITERATOR DOCUMENTATION
1149 * The iterator functions allow for easy, and convenient iteration
1150 * over items of interest in the list. Typically, you create a
1151 * iterator, use it, and destroy it, as such:
1154 * iterator_xxxitems(&i, ...);
1155 * while (iterator_{prev,next}(&i)
1157 * //code which uses i.nItem
1159 * iterator_destroy(&i);
1161 * where xxx is either: framed, or visible.
1162 * Note that it is important that the code destroys the iterator
1163 * after it's done with it, as the creation of the iterator may
1164 * allocate memory, which thus needs to be freed.
1166 * You can iterate both forwards, and backwards through the list,
1167 * by using iterator_next or iterator_prev respectively.
1169 * Lower numbered items are draw on top of higher number items in
1170 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1171 * items may overlap). So, to test items, you should use
1173 * which lists the items top to bottom (in Z-order).
1174 * For drawing items, you should use
1176 * which lists the items bottom to top (in Z-order).
1177 * If you keep iterating over the items after the end-of-items
1178 * marker (-1) is returned, the iterator will start from the
1179 * beginning. Typically, you don't need to test for -1,
1180 * because iterator_{next,prev} will return TRUE if more items
1181 * are to be iterated over, or FALSE otherwise.
1183 * Note: the iterator is defined to be bidirectional. That is,
1184 * any number of prev followed by any number of next, or
1185 * five versa, should leave the iterator at the same item:
1186 * prev * n, next * n = next * n, prev * n
1188 * The iterator has a notion of an out-of-order, special item,
1189 * which sits at the start of the list. This is used in
1190 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1191 * which needs to be first, as it may overlap other items.
1193 * The code is a bit messy because we have:
1194 * - a special item to deal with
1195 * - simple range, or composite range
1197 * If you find bugs, or want to add features, please make sure you
1198 * always check/modify *both* iterator_prev, and iterator_next.
1202 * This function iterates through the items in increasing order,
1203 * but prefixed by the special item, then -1. That is:
1204 * special, 1, 2, 3, ..., n, -1.
1205 * Each item is listed only once.
1207 static inline BOOL
iterator_next(ITERATOR
* i
)
1211 i
->nItem
= i
->nSpecial
;
1212 if (i
->nItem
!= -1) return TRUE
;
1214 if (i
->nItem
== i
->nSpecial
)
1216 if (i
->ranges
) i
->index
= 0;
1222 if (i
->nItem
== i
->nSpecial
) i
->nItem
++;
1223 if (i
->nItem
< i
->range
.upper
) return TRUE
;
1228 if (i
->index
< DPA_GetPtrCount(i
->ranges
->hdpa
))
1229 i
->range
= *(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, i
->index
++);
1232 else if (i
->nItem
>= i
->range
.upper
) goto end
;
1234 i
->nItem
= i
->range
.lower
;
1235 if (i
->nItem
>= 0) goto testitem
;
1242 * This function iterates through the items in decreasing order,
1243 * followed by the special item, then -1. That is:
1244 * n, n-1, ..., 3, 2, 1, special, -1.
1245 * Each item is listed only once.
1247 static inline BOOL
iterator_prev(ITERATOR
* i
)
1254 if (i
->ranges
) i
->index
= DPA_GetPtrCount(i
->ranges
->hdpa
);
1257 if (i
->nItem
== i
->nSpecial
)
1265 if (i
->nItem
== i
->nSpecial
) i
->nItem
--;
1266 if (i
->nItem
>= i
->range
.lower
) return TRUE
;
1272 i
->range
= *(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, --i
->index
);
1275 else if (!start
&& i
->nItem
< i
->range
.lower
) goto end
;
1277 i
->nItem
= i
->range
.upper
;
1278 if (i
->nItem
> 0) goto testitem
;
1280 return (i
->nItem
= i
->nSpecial
) != -1;
1283 static RANGE
iterator_range(const ITERATOR
*i
)
1287 if (!i
->ranges
) return i
->range
;
1289 if (DPA_GetPtrCount(i
->ranges
->hdpa
) > 0)
1291 range
.lower
= (*(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, 0)).lower
;
1292 range
.upper
= (*(RANGE
*)DPA_GetPtr(i
->ranges
->hdpa
, DPA_GetPtrCount(i
->ranges
->hdpa
) - 1)).upper
;
1294 else range
.lower
= range
.upper
= 0;
1300 * Releases resources associated with this ierator.
1302 static inline void iterator_destroy(const ITERATOR
*i
)
1304 ranges_destroy(i
->ranges
);
1308 * Create an empty iterator.
1310 static inline BOOL
iterator_empty(ITERATOR
* i
)
1312 ZeroMemory(i
, sizeof(*i
));
1313 i
->nItem
= i
->nSpecial
= i
->range
.lower
= i
->range
.upper
= -1;
1318 * Create an iterator over a range.
1320 static inline BOOL
iterator_rangeitems(ITERATOR
* i
, RANGE range
)
1328 * Create an iterator over a bunch of ranges.
1329 * Please note that the iterator will take ownership of the ranges,
1330 * and will free them upon destruction.
1332 static inline BOOL
iterator_rangesitems(ITERATOR
* i
, RANGES ranges
)
1340 * Creates an iterator over the items which intersect frame.
1341 * Uses absolute coordinates rather than compensating for the current offset.
1343 static BOOL
iterator_frameditems_absolute(ITERATOR
* i
, const LISTVIEW_INFO
* infoPtr
, const RECT
*frame
)
1345 RECT rcItem
, rcTemp
;
1347 /* in case we fail, we want to return an empty iterator */
1348 if (!iterator_empty(i
)) return FALSE
;
1350 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame
));
1352 if (infoPtr
->uView
== LV_VIEW_ICON
|| infoPtr
->uView
== LV_VIEW_SMALLICON
)
1356 if (infoPtr
->uView
== LV_VIEW_ICON
&& infoPtr
->nFocusedItem
!= -1)
1358 LISTVIEW_GetItemBox(infoPtr
, infoPtr
->nFocusedItem
, &rcItem
);
1359 if (IntersectRect(&rcTemp
, &rcItem
, frame
))
1360 i
->nSpecial
= infoPtr
->nFocusedItem
;
1362 if (!(iterator_rangesitems(i
, ranges_create(50)))) return FALSE
;
1363 /* to do better here, we need to have PosX, and PosY sorted */
1364 TRACE("building icon ranges:\n");
1365 for (nItem
= 0; nItem
< infoPtr
->nItemCount
; nItem
++)
1367 rcItem
.left
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
1368 rcItem
.top
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
1369 rcItem
.right
= rcItem
.left
+ infoPtr
->nItemWidth
;
1370 rcItem
.bottom
= rcItem
.top
+ infoPtr
->nItemHeight
;
1371 if (IntersectRect(&rcTemp
, &rcItem
, frame
))
1372 ranges_additem(i
->ranges
, nItem
);
1376 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
1380 if (frame
->left
>= infoPtr
->nItemWidth
) return TRUE
;
1381 if (frame
->top
>= infoPtr
->nItemHeight
* infoPtr
->nItemCount
) return TRUE
;
1383 range
.lower
= max(frame
->top
/ infoPtr
->nItemHeight
, 0);
1384 range
.upper
= min((frame
->bottom
- 1) / infoPtr
->nItemHeight
, infoPtr
->nItemCount
- 1) + 1;
1385 if (range
.upper
<= range
.lower
) return TRUE
;
1386 if (!iterator_rangeitems(i
, range
)) return FALSE
;
1387 TRACE(" report=%s\n", debugrange(&i
->range
));
1391 INT nPerCol
= max((infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
) / infoPtr
->nItemHeight
, 1);
1392 INT nFirstRow
= max(frame
->top
/ infoPtr
->nItemHeight
, 0);
1393 INT nLastRow
= min((frame
->bottom
- 1) / infoPtr
->nItemHeight
, nPerCol
- 1);
1400 if (infoPtr
->nItemWidth
)
1402 nFirstCol
= max(frame
->left
/ infoPtr
->nItemWidth
, 0);
1403 nLastCol
= min((frame
->right
- 1) / infoPtr
->nItemWidth
, (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
);
1407 nFirstCol
= max(frame
->left
, 0);
1408 nLastCol
= min(frame
->right
- 1, (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
);
1411 lower
= nFirstCol
* nPerCol
+ nFirstRow
;
1413 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1414 nPerCol
, nFirstRow
, nLastRow
, nFirstCol
, nLastCol
, lower
);
1416 if (nLastCol
< nFirstCol
|| nLastRow
< nFirstRow
) return TRUE
;
1418 if (!(iterator_rangesitems(i
, ranges_create(nLastCol
- nFirstCol
+ 1)))) return FALSE
;
1419 TRACE("building list ranges:\n");
1420 for (nCol
= nFirstCol
; nCol
<= nLastCol
; nCol
++)
1422 item_range
.lower
= nCol
* nPerCol
+ nFirstRow
;
1423 if(item_range
.lower
>= infoPtr
->nItemCount
) break;
1424 item_range
.upper
= min(nCol
* nPerCol
+ nLastRow
+ 1, infoPtr
->nItemCount
);
1425 TRACE(" list=%s\n", debugrange(&item_range
));
1426 ranges_add(i
->ranges
, item_range
);
1434 * Creates an iterator over the items which intersect lprc.
1436 static BOOL
iterator_frameditems(ITERATOR
* i
, const LISTVIEW_INFO
* infoPtr
, const RECT
*lprc
)
1441 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc
));
1443 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1444 OffsetRect(&frame
, -Origin
.x
, -Origin
.y
);
1446 return iterator_frameditems_absolute(i
, infoPtr
, &frame
);
1450 * Creates an iterator over the items which intersect the visible region of hdc.
1452 static BOOL
iterator_visibleitems(ITERATOR
*i
, const LISTVIEW_INFO
*infoPtr
, HDC hdc
)
1454 POINT Origin
, Position
;
1455 RECT rcItem
, rcClip
;
1458 rgntype
= GetClipBox(hdc
, &rcClip
);
1459 if (rgntype
== NULLREGION
) return iterator_empty(i
);
1460 if (!iterator_frameditems(i
, infoPtr
, &rcClip
)) return FALSE
;
1461 if (rgntype
== SIMPLEREGION
) return TRUE
;
1463 /* first deal with the special item */
1464 if (i
->nSpecial
!= -1)
1466 LISTVIEW_GetItemBox(infoPtr
, i
->nSpecial
, &rcItem
);
1467 if (!RectVisible(hdc
, &rcItem
)) i
->nSpecial
= -1;
1470 /* if we can't deal with the region, we'll just go with the simple range */
1471 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1472 TRACE("building visible range:\n");
1473 if (!i
->ranges
&& i
->range
.lower
< i
->range
.upper
)
1475 if (!(i
->ranges
= ranges_create(50))) return TRUE
;
1476 if (!ranges_add(i
->ranges
, i
->range
))
1478 ranges_destroy(i
->ranges
);
1484 /* now delete the invisible items from the list */
1485 while(iterator_next(i
))
1487 LISTVIEW_GetItemOrigin(infoPtr
, i
->nItem
, &Position
);
1488 rcItem
.left
= (infoPtr
->uView
== LV_VIEW_DETAILS
) ? Origin
.x
: Position
.x
+ Origin
.x
;
1489 rcItem
.top
= Position
.y
+ Origin
.y
;
1490 rcItem
.right
= rcItem
.left
+ infoPtr
->nItemWidth
;
1491 rcItem
.bottom
= rcItem
.top
+ infoPtr
->nItemHeight
;
1492 if (!RectVisible(hdc
, &rcItem
))
1493 ranges_delitem(i
->ranges
, i
->nItem
);
1495 /* the iterator should restart on the next iterator_next */
1501 /******** Misc helper functions ************************************/
1503 static inline LRESULT
CallWindowProcT(WNDPROC proc
, HWND hwnd
, UINT uMsg
,
1504 WPARAM wParam
, LPARAM lParam
, BOOL isW
)
1506 if (isW
) return CallWindowProcW(proc
, hwnd
, uMsg
, wParam
, lParam
);
1507 else return CallWindowProcA(proc
, hwnd
, uMsg
, wParam
, lParam
);
1510 static inline BOOL
is_autoarrange(const LISTVIEW_INFO
*infoPtr
)
1512 return ((infoPtr
->dwStyle
& LVS_AUTOARRANGE
) || infoPtr
->bAutoarrange
) &&
1513 (infoPtr
->uView
== LV_VIEW_ICON
|| infoPtr
->uView
== LV_VIEW_SMALLICON
);
1516 static void toggle_checkbox_state(LISTVIEW_INFO
*infoPtr
, INT nItem
)
1518 DWORD state
= STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr
, nItem
, LVIS_STATEIMAGEMASK
));
1519 if(state
== 1 || state
== 2)
1523 lvitem
.state
= INDEXTOSTATEIMAGEMASK(state
);
1524 lvitem
.stateMask
= LVIS_STATEIMAGEMASK
;
1525 LISTVIEW_SetItemState(infoPtr
, nItem
, &lvitem
);
1529 /* this should be called after window style got updated,
1530 it used to reset view state to match current window style */
1531 static inline void map_style_view(LISTVIEW_INFO
*infoPtr
)
1533 switch (infoPtr
->dwStyle
& LVS_TYPEMASK
)
1536 infoPtr
->uView
= LV_VIEW_ICON
;
1539 infoPtr
->uView
= LV_VIEW_DETAILS
;
1542 infoPtr
->uView
= LV_VIEW_SMALLICON
;
1545 infoPtr
->uView
= LV_VIEW_LIST
;
1549 /* computes next item id value */
1550 static DWORD
get_next_itemid(const LISTVIEW_INFO
*infoPtr
)
1552 INT count
= DPA_GetPtrCount(infoPtr
->hdpaItemIds
);
1556 ITEM_ID
*lpID
= DPA_GetPtr(infoPtr
->hdpaItemIds
, count
- 1);
1557 return lpID
->id
+ 1;
1562 /******** Internal API functions ************************************/
1564 static inline COLUMN_INFO
* LISTVIEW_GetColumnInfo(const LISTVIEW_INFO
*infoPtr
, INT nSubItem
)
1566 static COLUMN_INFO mainItem
;
1568 if (nSubItem
== 0 && DPA_GetPtrCount(infoPtr
->hdpaColumns
) == 0) return &mainItem
;
1569 assert (nSubItem
>= 0 && nSubItem
< DPA_GetPtrCount(infoPtr
->hdpaColumns
));
1571 /* update cached column rectangles */
1572 if (infoPtr
->colRectsDirty
)
1575 LISTVIEW_INFO
*Ptr
= (LISTVIEW_INFO
*)infoPtr
;
1578 for (i
= 0; i
< DPA_GetPtrCount(infoPtr
->hdpaColumns
); i
++) {
1579 info
= DPA_GetPtr(infoPtr
->hdpaColumns
, i
);
1580 SendMessageW(infoPtr
->hwndHeader
, HDM_GETITEMRECT
, i
, (LPARAM
)&info
->rcHeader
);
1582 Ptr
->colRectsDirty
= FALSE
;
1585 return DPA_GetPtr(infoPtr
->hdpaColumns
, nSubItem
);
1588 static INT
LISTVIEW_CreateHeader(LISTVIEW_INFO
*infoPtr
)
1590 DWORD dFlags
= WS_CHILD
| HDS_HORZ
| HDS_FULLDRAG
| HDS_DRAGDROP
;
1593 if (infoPtr
->hwndHeader
) return 0;
1595 TRACE("Creating header for list %p\n", infoPtr
->hwndSelf
);
1597 /* setup creation flags */
1598 dFlags
|= (LVS_NOSORTHEADER
& infoPtr
->dwStyle
) ? 0 : HDS_BUTTONS
;
1599 dFlags
|= (LVS_NOCOLUMNHEADER
& infoPtr
->dwStyle
) ? HDS_HIDDEN
: 0;
1601 hInst
= (HINSTANCE
)GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_HINSTANCE
);
1604 infoPtr
->hwndHeader
= CreateWindowW(WC_HEADERW
, NULL
, dFlags
,
1605 0, 0, 0, 0, infoPtr
->hwndSelf
, NULL
, hInst
, NULL
);
1606 if (!infoPtr
->hwndHeader
) return -1;
1608 /* set header unicode format */
1609 SendMessageW(infoPtr
->hwndHeader
, HDM_SETUNICODEFORMAT
, TRUE
, 0);
1611 /* set header font */
1612 SendMessageW(infoPtr
->hwndHeader
, WM_SETFONT
, (WPARAM
)infoPtr
->hFont
, TRUE
);
1614 LISTVIEW_UpdateSize(infoPtr
);
1619 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO
*infoPtr
, INT nSubItem
, LPRECT lprc
)
1621 *lprc
= LISTVIEW_GetColumnInfo(infoPtr
, nSubItem
)->rcHeader
;
1624 static inline BOOL
LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO
*infoPtr
)
1626 return (infoPtr
->uView
== LV_VIEW_DETAILS
||
1627 infoPtr
->dwLvExStyle
& LVS_EX_HEADERINALLVIEWS
) &&
1628 !(infoPtr
->dwStyle
& LVS_NOCOLUMNHEADER
);
1631 static inline BOOL
LISTVIEW_GetItemW(const LISTVIEW_INFO
*infoPtr
, LPLVITEMW lpLVItem
)
1633 return LISTVIEW_GetItemT(infoPtr
, lpLVItem
, TRUE
);
1636 /* used to handle collapse main item column case */
1637 static inline BOOL
LISTVIEW_DrawFocusRect(const LISTVIEW_INFO
*infoPtr
, HDC hdc
)
1641 if (infoPtr
->rcFocus
.left
< infoPtr
->rcFocus
.right
)
1643 DWORD dwOldBkColor
, dwOldTextColor
;
1645 dwOldBkColor
= SetBkColor(hdc
, RGB(255, 255, 255));
1646 dwOldTextColor
= SetBkColor(hdc
, RGB(0, 0, 0));
1647 Ret
= DrawFocusRect(hdc
, &infoPtr
->rcFocus
);
1648 SetBkColor(hdc
, dwOldBkColor
);
1649 SetBkColor(hdc
, dwOldTextColor
);
1654 /* Listview invalidation functions: use _only_ these functions to invalidate */
1656 static inline BOOL
is_redrawing(const LISTVIEW_INFO
*infoPtr
)
1658 return infoPtr
->bRedraw
;
1661 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO
*infoPtr
, const RECT
* rect
)
1663 if(!is_redrawing(infoPtr
)) return;
1664 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect
));
1665 InvalidateRect(infoPtr
->hwndSelf
, rect
, TRUE
);
1668 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO
*infoPtr
, INT nItem
)
1672 if(!is_redrawing(infoPtr
)) return;
1673 LISTVIEW_GetItemBox(infoPtr
, nItem
, &rcBox
);
1674 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
1677 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO
*infoPtr
, INT nItem
, INT nSubItem
)
1679 POINT Origin
, Position
;
1682 if(!is_redrawing(infoPtr
)) return;
1683 assert (infoPtr
->uView
== LV_VIEW_DETAILS
);
1684 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
1685 LISTVIEW_GetItemOrigin(infoPtr
, nItem
, &Position
);
1686 LISTVIEW_GetHeaderRect(infoPtr
, nSubItem
, &rcBox
);
1688 rcBox
.bottom
= infoPtr
->nItemHeight
;
1689 OffsetRect(&rcBox
, Origin
.x
+ Position
.x
, Origin
.y
+ Position
.y
);
1690 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
1693 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO
*infoPtr
)
1695 LISTVIEW_InvalidateRect(infoPtr
, NULL
);
1698 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO
*infoPtr
, INT nColumn
)
1702 if(!is_redrawing(infoPtr
)) return;
1703 LISTVIEW_GetHeaderRect(infoPtr
, nColumn
, &rcCol
);
1704 rcCol
.top
= infoPtr
->rcList
.top
;
1705 rcCol
.bottom
= infoPtr
->rcList
.bottom
;
1706 LISTVIEW_InvalidateRect(infoPtr
, &rcCol
);
1711 * Retrieves the number of items that can fit vertically in the client area.
1714 * [I] infoPtr : valid pointer to the listview structure
1717 * Number of items per row.
1719 static inline INT
LISTVIEW_GetCountPerRow(const LISTVIEW_INFO
*infoPtr
)
1721 INT nListWidth
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
1723 return max(nListWidth
/(infoPtr
->nItemWidth
? infoPtr
->nItemWidth
: 1), 1);
1728 * Retrieves the number of items that can fit horizontally in the client
1732 * [I] infoPtr : valid pointer to the listview structure
1735 * Number of items per column.
1737 static inline INT
LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO
*infoPtr
)
1739 INT nListHeight
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
1741 return max(nListHeight
/ infoPtr
->nItemHeight
, 1);
1745 /*************************************************************************
1746 * LISTVIEW_ProcessLetterKeys
1748 * Processes keyboard messages generated by pressing the letter keys
1750 * What this does is perform a case insensitive search from the
1751 * current position with the following quirks:
1752 * - If two chars or more are pressed in quick succession we search
1753 * for the corresponding string (e.g. 'abc').
1754 * - If there is a delay we wipe away the current search string and
1755 * restart with just that char.
1756 * - If the user keeps pressing the same character, whether slowly or
1757 * fast, so that the search string is entirely composed of this
1758 * character ('aaaaa' for instance), then we search for first item
1759 * that starting with that character.
1760 * - If the user types the above character in quick succession, then
1761 * we must also search for the corresponding string ('aaaaa'), and
1762 * go to that string if there is a match.
1765 * [I] hwnd : handle to the window
1766 * [I] charCode : the character code, the actual character
1767 * [I] keyData : key data
1775 * - The current implementation has a list of characters it will
1776 * accept and it ignores everything else. In particular it will
1777 * ignore accentuated characters which seems to match what
1778 * Windows does. But I'm not sure it makes sense to follow
1780 * - We don't sound a beep when the search fails.
1784 * TREEVIEW_ProcessLetterKeys
1786 static INT
LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO
*infoPtr
, WPARAM charCode
, LPARAM keyData
)
1788 WCHAR buffer
[MAX_PATH
];
1789 INT endidx
, startidx
;
1795 /* simple parameter checking */
1796 if (!charCode
|| !keyData
|| infoPtr
->nItemCount
== 0) return 0;
1798 /* only allow the valid WM_CHARs through */
1799 if (!isalnumW(charCode
) &&
1800 charCode
!= '.' && charCode
!= '`' && charCode
!= '!' &&
1801 charCode
!= '@' && charCode
!= '#' && charCode
!= '$' &&
1802 charCode
!= '%' && charCode
!= '^' && charCode
!= '&' &&
1803 charCode
!= '*' && charCode
!= '(' && charCode
!= ')' &&
1804 charCode
!= '-' && charCode
!= '_' && charCode
!= '+' &&
1805 charCode
!= '=' && charCode
!= '\\'&& charCode
!= ']' &&
1806 charCode
!= '}' && charCode
!= '[' && charCode
!= '{' &&
1807 charCode
!= '/' && charCode
!= '?' && charCode
!= '>' &&
1808 charCode
!= '<' && charCode
!= ',' && charCode
!= '~')
1811 /* update the search parameters */
1812 prevTime
= infoPtr
->lastKeyPressTimestamp
;
1813 infoPtr
->lastKeyPressTimestamp
= GetTickCount();
1814 diff
= infoPtr
->lastKeyPressTimestamp
- prevTime
;
1816 if (diff
>= 0 && diff
< KEY_DELAY
)
1818 if (infoPtr
->nSearchParamLength
< MAX_PATH
- 1)
1819 infoPtr
->szSearchParam
[infoPtr
->nSearchParamLength
++] = charCode
;
1821 if (infoPtr
->charCode
!= charCode
)
1822 infoPtr
->charCode
= charCode
= 0;
1826 infoPtr
->charCode
= charCode
;
1827 infoPtr
->szSearchParam
[0] = charCode
;
1828 infoPtr
->nSearchParamLength
= 1;
1831 /* and search from the current position */
1833 endidx
= infoPtr
->nItemCount
;
1835 /* should start from next after focused item, so next item that matches
1836 will be selected, if there isn't any and focused matches it will be selected
1837 on second search stage from beginning of the list */
1838 if (infoPtr
->nFocusedItem
>= 0 && infoPtr
->nItemCount
> 1)
1839 startidx
= infoPtr
->nFocusedItem
+ 1;
1843 /* let application handle this for virtual listview */
1844 if (infoPtr
->dwStyle
& LVS_OWNERDATA
)
1848 memset(&nmlv
.lvfi
, 0, sizeof(nmlv
.lvfi
));
1849 nmlv
.lvfi
.flags
= (LVFI_WRAP
| LVFI_PARTIAL
);
1850 nmlv
.lvfi
.psz
= infoPtr
->szSearchParam
;
1851 nmlv
.iStart
= startidx
;
1853 infoPtr
->szSearchParam
[infoPtr
->nSearchParamLength
] = 0;
1855 nItem
= notify_hdr(infoPtr
, LVN_ODFINDITEMW
, (LPNMHDR
)&nmlv
.hdr
);
1861 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1864 /* start from first item if not found with >= startidx */
1865 if (i
== infoPtr
->nItemCount
&& startidx
> 0)
1871 for (i
= startidx
; i
< endidx
; i
++)
1874 item
.mask
= LVIF_TEXT
;
1877 item
.pszText
= buffer
;
1878 item
.cchTextMax
= MAX_PATH
;
1879 if (!LISTVIEW_GetItemW(infoPtr
, &item
)) return 0;
1881 if (lstrncmpiW(item
.pszText
, infoPtr
->szSearchParam
, infoPtr
->nSearchParamLength
) == 0)
1886 else if (nItem
== -1 && lstrncmpiW(item
.pszText
, infoPtr
->szSearchParam
, 1) == 0)
1888 /* this would work but we must keep looking for a longer match */
1893 if ( nItem
!= -1 || /* found something */
1894 endidx
!= infoPtr
->nItemCount
|| /* second search done */
1895 (startidx
== 0 && endidx
== infoPtr
->nItemCount
) /* full range for first search */ )
1901 LISTVIEW_KeySelection(infoPtr
, nItem
, FALSE
);
1906 /*************************************************************************
1907 * LISTVIEW_UpdateHeaderSize [Internal]
1909 * Function to resize the header control
1912 * [I] hwnd : handle to a window
1913 * [I] nNewScrollPos : scroll pos to set
1918 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO
*infoPtr
, INT nNewScrollPos
)
1923 TRACE("nNewScrollPos=%d\n", nNewScrollPos
);
1925 if (!infoPtr
->hwndHeader
) return;
1927 GetWindowRect(infoPtr
->hwndHeader
, &winRect
);
1928 point
[0].x
= winRect
.left
;
1929 point
[0].y
= winRect
.top
;
1930 point
[1].x
= winRect
.right
;
1931 point
[1].y
= winRect
.bottom
;
1933 MapWindowPoints(HWND_DESKTOP
, infoPtr
->hwndSelf
, point
, 2);
1934 point
[0].x
= -nNewScrollPos
;
1935 point
[1].x
+= nNewScrollPos
;
1937 SetWindowPos(infoPtr
->hwndHeader
,0,
1938 point
[0].x
,point
[0].y
,point
[1].x
,point
[1].y
,
1939 (infoPtr
->dwStyle
& LVS_NOCOLUMNHEADER
) ? SWP_HIDEWINDOW
: SWP_SHOWWINDOW
|
1940 SWP_NOZORDER
| SWP_NOACTIVATE
);
1945 * Update the scrollbars. This functions should be called whenever
1946 * the content, size or view changes.
1949 * [I] infoPtr : valid pointer to the listview structure
1954 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO
*infoPtr
)
1956 SCROLLINFO horzInfo
, vertInfo
;
1959 if ((infoPtr
->dwStyle
& LVS_NOSCROLL
) || !is_redrawing(infoPtr
)) return;
1961 ZeroMemory(&horzInfo
, sizeof(SCROLLINFO
));
1962 horzInfo
.cbSize
= sizeof(SCROLLINFO
);
1963 horzInfo
.nPage
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
1965 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1966 if (infoPtr
->uView
== LV_VIEW_LIST
)
1968 INT nPerCol
= LISTVIEW_GetCountPerColumn(infoPtr
);
1969 horzInfo
.nMax
= (infoPtr
->nItemCount
+ nPerCol
- 1) / nPerCol
;
1971 /* scroll by at least one column per page */
1972 if(horzInfo
.nPage
< infoPtr
->nItemWidth
)
1973 horzInfo
.nPage
= infoPtr
->nItemWidth
;
1975 if (infoPtr
->nItemWidth
)
1976 horzInfo
.nPage
/= infoPtr
->nItemWidth
;
1978 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
1980 horzInfo
.nMax
= infoPtr
->nItemWidth
;
1982 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1986 if (LISTVIEW_GetViewRect(infoPtr
, &rcView
)) horzInfo
.nMax
= rcView
.right
- rcView
.left
;
1989 if (LISTVIEW_IsHeaderEnabled(infoPtr
))
1991 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
))
1996 index
= SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
,
1997 DPA_GetPtrCount(infoPtr
->hdpaColumns
) - 1, 0);
1999 LISTVIEW_GetHeaderRect(infoPtr
, index
, &rcHeader
);
2000 horzInfo
.nMax
= rcHeader
.right
;
2001 TRACE("horzInfo.nMax=%d\n", horzInfo
.nMax
);
2005 horzInfo
.fMask
= SIF_RANGE
| SIF_PAGE
;
2006 horzInfo
.nMax
= max(horzInfo
.nMax
- 1, 0);
2007 dx
= GetScrollPos(infoPtr
->hwndSelf
, SB_HORZ
);
2008 dx
-= SetScrollInfo(infoPtr
->hwndSelf
, SB_HORZ
, &horzInfo
, TRUE
);
2009 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo
));
2011 /* Setting the horizontal scroll can change the listview size
2012 * (and potentially everything else) so we need to recompute
2013 * everything again for the vertical scroll
2016 ZeroMemory(&vertInfo
, sizeof(SCROLLINFO
));
2017 vertInfo
.cbSize
= sizeof(SCROLLINFO
);
2018 vertInfo
.nPage
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
2020 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2022 vertInfo
.nMax
= infoPtr
->nItemCount
;
2024 /* scroll by at least one page */
2025 if(vertInfo
.nPage
< infoPtr
->nItemHeight
)
2026 vertInfo
.nPage
= infoPtr
->nItemHeight
;
2028 if (infoPtr
->nItemHeight
> 0)
2029 vertInfo
.nPage
/= infoPtr
->nItemHeight
;
2031 else if (infoPtr
->uView
!= LV_VIEW_LIST
) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2035 if (LISTVIEW_GetViewRect(infoPtr
, &rcView
)) vertInfo
.nMax
= rcView
.bottom
- rcView
.top
;
2038 vertInfo
.fMask
= SIF_RANGE
| SIF_PAGE
;
2039 vertInfo
.nMax
= max(vertInfo
.nMax
- 1, 0);
2040 dy
= GetScrollPos(infoPtr
->hwndSelf
, SB_VERT
);
2041 dy
-= SetScrollInfo(infoPtr
->hwndSelf
, SB_VERT
, &vertInfo
, TRUE
);
2042 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo
));
2044 /* Change of the range may have changed the scroll pos. If so move the content */
2045 if (dx
!= 0 || dy
!= 0)
2048 listRect
= infoPtr
->rcList
;
2049 ScrollWindowEx(infoPtr
->hwndSelf
, dx
, dy
, &listRect
, &listRect
, 0, 0,
2050 SW_ERASE
| SW_INVALIDATE
);
2053 /* Update the Header Control */
2054 if (infoPtr
->hwndHeader
)
2056 horzInfo
.fMask
= SIF_POS
;
2057 GetScrollInfo(infoPtr
->hwndSelf
, SB_HORZ
, &horzInfo
);
2058 LISTVIEW_UpdateHeaderSize(infoPtr
, horzInfo
.nPos
);
2065 * Shows/hides the focus rectangle.
2068 * [I] infoPtr : valid pointer to the listview structure
2069 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2074 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO
*infoPtr
, BOOL fShow
)
2078 TRACE("fShow=%d, nItem=%d\n", fShow
, infoPtr
->nFocusedItem
);
2080 if (infoPtr
->nFocusedItem
< 0) return;
2082 /* we need some gymnastics in ICON mode to handle large items */
2083 if (infoPtr
->uView
== LV_VIEW_ICON
)
2087 LISTVIEW_GetItemBox(infoPtr
, infoPtr
->nFocusedItem
, &rcBox
);
2088 if ((rcBox
.bottom
- rcBox
.top
) > infoPtr
->nItemHeight
)
2090 LISTVIEW_InvalidateRect(infoPtr
, &rcBox
);
2095 if (!(hdc
= GetDC(infoPtr
->hwndSelf
))) return;
2097 /* for some reason, owner draw should work only in report mode */
2098 if ((infoPtr
->dwStyle
& LVS_OWNERDRAWFIXED
) && (infoPtr
->uView
== LV_VIEW_DETAILS
))
2103 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
2104 HFONT hOldFont
= SelectObject(hdc
, hFont
);
2106 item
.iItem
= infoPtr
->nFocusedItem
;
2108 item
.mask
= LVIF_PARAM
;
2109 if (!LISTVIEW_GetItemW(infoPtr
, &item
)) goto done
;
2111 ZeroMemory(&dis
, sizeof(dis
));
2112 dis
.CtlType
= ODT_LISTVIEW
;
2113 dis
.CtlID
= (UINT
)GetWindowLongPtrW(infoPtr
->hwndSelf
, GWLP_ID
);
2114 dis
.itemID
= item
.iItem
;
2115 dis
.itemAction
= ODA_FOCUS
;
2116 if (fShow
) dis
.itemState
|= ODS_FOCUS
;
2117 dis
.hwndItem
= infoPtr
->hwndSelf
;
2119 LISTVIEW_GetItemBox(infoPtr
, dis
.itemID
, &dis
.rcItem
);
2120 dis
.itemData
= item
.lParam
;
2122 SendMessageW(infoPtr
->hwndNotify
, WM_DRAWITEM
, dis
.CtlID
, (LPARAM
)&dis
);
2124 SelectObject(hdc
, hOldFont
);
2128 LISTVIEW_DrawFocusRect(infoPtr
, hdc
);
2131 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
2135 * Invalidates all visible selected items.
2137 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO
*infoPtr
)
2141 iterator_frameditems(&i
, infoPtr
, &infoPtr
->rcList
);
2142 while(iterator_next(&i
))
2144 if (LISTVIEW_GetItemState(infoPtr
, i
.nItem
, LVIS_SELECTED
))
2145 LISTVIEW_InvalidateItem(infoPtr
, i
.nItem
);
2147 iterator_destroy(&i
);
2152 * DESCRIPTION: [INTERNAL]
2153 * Computes an item's (left,top) corner, relative to rcView.
2154 * That is, the position has NOT been made relative to the Origin.
2155 * This is deliberate, to avoid computing the Origin over, and
2156 * over again, when this function is called in a loop. Instead,
2157 * one can factor the computation of the Origin before the loop,
2158 * and offset the value returned by this function, on every iteration.
2161 * [I] infoPtr : valid pointer to the listview structure
2162 * [I] nItem : item number
2163 * [O] lpptOrig : item top, left corner
2168 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO
*infoPtr
, INT nItem
, LPPOINT lpptPosition
)
2170 assert(nItem
>= 0 && nItem
< infoPtr
->nItemCount
);
2172 if ((infoPtr
->uView
== LV_VIEW_SMALLICON
) || (infoPtr
->uView
== LV_VIEW_ICON
))
2174 lpptPosition
->x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
2175 lpptPosition
->y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
2177 else if (infoPtr
->uView
== LV_VIEW_LIST
)
2179 INT nCountPerColumn
= LISTVIEW_GetCountPerColumn(infoPtr
);
2180 lpptPosition
->x
= nItem
/ nCountPerColumn
* infoPtr
->nItemWidth
;
2181 lpptPosition
->y
= nItem
% nCountPerColumn
* infoPtr
->nItemHeight
;
2183 else /* LV_VIEW_DETAILS */
2185 lpptPosition
->x
= REPORT_MARGINX
;
2186 /* item is always at zero indexed column */
2187 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
) > 0)
2188 lpptPosition
->x
+= LISTVIEW_GetColumnInfo(infoPtr
, 0)->rcHeader
.left
;
2189 lpptPosition
->y
= nItem
* infoPtr
->nItemHeight
;
2194 * DESCRIPTION: [INTERNAL]
2195 * Compute the rectangles of an item. This is to localize all
2196 * the computations in one place. If you are not interested in some
2197 * of these values, simply pass in a NULL -- the function is smart
2198 * enough to compute only what's necessary. The function computes
2199 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2200 * one, the BOX rectangle. This rectangle is very cheap to compute,
2201 * and is guaranteed to contain all the other rectangles. Computing
2202 * the ICON rect is also cheap, but all the others are potentially
2203 * expensive. This gives an easy and effective optimization when
2204 * searching (like point inclusion, or rectangle intersection):
2205 * first test against the BOX, and if TRUE, test against the desired
2207 * If the function does not have all the necessary information
2208 * to computed the requested rectangles, will crash with a
2209 * failed assertion. This is done so we catch all programming
2210 * errors, given that the function is called only from our code.
2212 * We have the following 'special' meanings for a few fields:
2213 * * If LVIS_FOCUSED is set, we assume the item has the focus
2214 * This is important in ICON mode, where it might get a larger
2215 * then usual rectangle
2217 * Please note that subitem support works only in REPORT mode.
2220 * [I] infoPtr : valid pointer to the listview structure
2221 * [I] lpLVItem : item to compute the measures for
2222 * [O] lprcBox : ptr to Box rectangle
2223 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2224 * [0] lprcSelectBox : ptr to select box rectangle
2225 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2226 * [O] lprcIcon : ptr to Icon rectangle
2227 * Same as LVM_GETITEMRECT with LVIR_ICON
2228 * [O] lprcStateIcon: ptr to State Icon rectangle
2229 * [O] lprcLabel : ptr to Label rectangle
2230 * Same as LVM_GETITEMRECT with LVIR_LABEL
2235 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO
*infoPtr
, const LVITEMW
*lpLVItem
,
2236 LPRECT lprcBox
, LPRECT lprcSelectBox
,
2237 LPRECT lprcIcon
, LPRECT lprcStateIcon
, LPRECT lprcLabel
)
2239 BOOL doSelectBox
= FALSE
, doIcon
= FALSE
, doLabel
= FALSE
, oversizedBox
= FALSE
;
2240 RECT Box
, SelectBox
, Icon
, Label
;
2241 COLUMN_INFO
*lpColumnInfo
= NULL
;
2242 SIZE labelSize
= { 0, 0 };
2244 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem
, TRUE
));
2246 /* Be smart and try to figure out the minimum we have to do */
2247 if (lpLVItem
->iSubItem
) assert(infoPtr
->uView
== LV_VIEW_DETAILS
);
2248 if (infoPtr
->uView
== LV_VIEW_ICON
&& (lprcBox
|| lprcLabel
))
2250 assert((lpLVItem
->mask
& LVIF_STATE
) && (lpLVItem
->stateMask
& LVIS_FOCUSED
));
2251 if (lpLVItem
->state
& LVIS_FOCUSED
) oversizedBox
= doLabel
= TRUE
;
2253 if (lprcSelectBox
) doSelectBox
= TRUE
;
2254 if (lprcLabel
) doLabel
= TRUE
;
2255 if (doLabel
|| lprcIcon
|| lprcStateIcon
) doIcon
= TRUE
;
2262 /************************************************************/
2263 /* compute the box rectangle (it should be cheap to do) */
2264 /************************************************************/
2265 if (lpLVItem
->iSubItem
|| infoPtr
->uView
== LV_VIEW_DETAILS
)
2266 lpColumnInfo
= LISTVIEW_GetColumnInfo(infoPtr
, lpLVItem
->iSubItem
);
2268 if (lpLVItem
->iSubItem
)
2270 Box
= lpColumnInfo
->rcHeader
;
2275 Box
.right
= infoPtr
->nItemWidth
;
2278 Box
.bottom
= infoPtr
->nItemHeight
;
2280 /******************************************************************/
2281 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2282 /******************************************************************/
2285 LONG state_width
= 0;
2287 if (infoPtr
->himlState
&& lpLVItem
->iSubItem
== 0)
2288 state_width
= infoPtr
->iconStateSize
.cx
;
2290 if (infoPtr
->uView
== LV_VIEW_ICON
)
2292 Icon
.left
= Box
.left
+ state_width
;
2293 if (infoPtr
->himlNormal
)
2294 Icon
.left
+= (infoPtr
->nItemWidth
- infoPtr
->iconSize
.cx
- state_width
) / 2;
2295 Icon
.top
= Box
.top
+ ICON_TOP_PADDING
;
2296 Icon
.right
= Icon
.left
;
2297 Icon
.bottom
= Icon
.top
;
2298 if (infoPtr
->himlNormal
)
2300 Icon
.right
+= infoPtr
->iconSize
.cx
;
2301 Icon
.bottom
+= infoPtr
->iconSize
.cy
;
2304 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2306 Icon
.left
= Box
.left
+ state_width
;
2308 if (infoPtr
->uView
== LV_VIEW_DETAILS
&& lpLVItem
->iSubItem
== 0)
2310 /* we need the indent in report mode */
2311 assert(lpLVItem
->mask
& LVIF_INDENT
);
2312 Icon
.left
+= infoPtr
->iconSize
.cx
* lpLVItem
->iIndent
+ REPORT_MARGINX
;
2316 Icon
.right
= Icon
.left
;
2317 if (infoPtr
->himlSmall
&&
2318 (!lpColumnInfo
|| lpLVItem
->iSubItem
== 0 || (lpColumnInfo
->fmt
& LVCFMT_IMAGE
) ||
2319 ((infoPtr
->dwLvExStyle
& LVS_EX_SUBITEMIMAGES
) && lpLVItem
->iImage
!= I_IMAGECALLBACK
)))
2320 Icon
.right
+= infoPtr
->iconSize
.cx
;
2321 Icon
.bottom
= Icon
.top
+ infoPtr
->iconSize
.cy
;
2323 if(lprcIcon
) *lprcIcon
= Icon
;
2324 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon
));
2326 /* TODO: is this correct? */
2329 lprcStateIcon
->left
= Icon
.left
- state_width
;
2330 lprcStateIcon
->right
= Icon
.left
;
2331 lprcStateIcon
->top
= Icon
.top
;
2332 lprcStateIcon
->bottom
= lprcStateIcon
->top
+ infoPtr
->iconSize
.cy
;
2333 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon
));
2336 else Icon
.right
= 0;
2338 /************************************************************/
2339 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2340 /************************************************************/
2343 /* calculate how far to the right can the label stretch */
2344 Label
.right
= Box
.right
;
2345 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2347 if (lpLVItem
->iSubItem
== 0)
2349 /* we need a zero based rect here */
2350 Label
= lpColumnInfo
->rcHeader
;
2351 OffsetRect(&Label
, -Label
.left
, 0);
2355 if (lpLVItem
->iSubItem
|| ((infoPtr
->dwStyle
& LVS_OWNERDRAWFIXED
) && infoPtr
->uView
== LV_VIEW_DETAILS
))
2357 labelSize
.cx
= infoPtr
->nItemWidth
;
2358 labelSize
.cy
= infoPtr
->nItemHeight
;
2362 /* we need the text in non owner draw mode */
2363 assert(lpLVItem
->mask
& LVIF_TEXT
);
2364 if (is_text(lpLVItem
->pszText
))
2366 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
2367 HDC hdc
= GetDC(infoPtr
->hwndSelf
);
2368 HFONT hOldFont
= SelectObject(hdc
, hFont
);
2372 /* compute rough rectangle where the label will go */
2373 SetRectEmpty(&rcText
);
2374 rcText
.right
= infoPtr
->nItemWidth
- TRAILING_LABEL_PADDING
;
2375 rcText
.bottom
= infoPtr
->nItemHeight
;
2376 if (infoPtr
->uView
== LV_VIEW_ICON
)
2377 rcText
.bottom
-= ICON_TOP_PADDING
+ infoPtr
->iconSize
.cy
+ ICON_BOTTOM_PADDING
;
2379 /* now figure out the flags */
2380 if (infoPtr
->uView
== LV_VIEW_ICON
)
2381 uFormat
= oversizedBox
? LV_FL_DT_FLAGS
: LV_ML_DT_FLAGS
;
2383 uFormat
= LV_SL_DT_FLAGS
;
2385 DrawTextW (hdc
, lpLVItem
->pszText
, -1, &rcText
, uFormat
| DT_CALCRECT
);
2387 if (rcText
.right
!= rcText
.left
)
2388 labelSize
.cx
= min(rcText
.right
- rcText
.left
+ TRAILING_LABEL_PADDING
, infoPtr
->nItemWidth
);
2390 labelSize
.cy
= rcText
.bottom
- rcText
.top
;
2392 SelectObject(hdc
, hOldFont
);
2393 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
2397 if (infoPtr
->uView
== LV_VIEW_ICON
)
2399 Label
.left
= Box
.left
+ (infoPtr
->nItemWidth
- labelSize
.cx
) / 2;
2400 Label
.top
= Box
.top
+ ICON_TOP_PADDING_HITABLE
+
2401 infoPtr
->iconSize
.cy
+ ICON_BOTTOM_PADDING
;
2402 Label
.right
= Label
.left
+ labelSize
.cx
;
2403 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2404 if (!oversizedBox
&& labelSize
.cy
> infoPtr
->ntmHeight
)
2406 labelSize
.cy
= min(Box
.bottom
- Label
.top
, labelSize
.cy
);
2407 labelSize
.cy
/= infoPtr
->ntmHeight
;
2408 labelSize
.cy
= max(labelSize
.cy
, 1);
2409 labelSize
.cy
*= infoPtr
->ntmHeight
;
2411 Label
.bottom
= Label
.top
+ labelSize
.cy
+ HEIGHT_PADDING
;
2413 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2415 Label
.left
= Icon
.right
;
2416 Label
.top
= Box
.top
;
2417 Label
.right
= lpLVItem
->iSubItem
? lpColumnInfo
->rcHeader
.right
:
2418 lpColumnInfo
->rcHeader
.right
- lpColumnInfo
->rcHeader
.left
;
2419 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2421 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2423 Label
.left
= Icon
.right
;
2424 Label
.top
= Box
.top
;
2425 Label
.right
= min(Label
.left
+ labelSize
.cx
, Label
.right
);
2426 Label
.bottom
= Label
.top
+ infoPtr
->nItemHeight
;
2429 if (lprcLabel
) *lprcLabel
= Label
;
2430 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label
));
2433 /************************************************************/
2434 /* compute SELECT bounding box */
2435 /************************************************************/
2438 if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2440 SelectBox
.left
= Icon
.left
;
2441 SelectBox
.top
= Box
.top
;
2442 SelectBox
.bottom
= Box
.bottom
;
2445 SelectBox
.right
= min(Label
.left
+ labelSize
.cx
, Label
.right
);
2447 SelectBox
.right
= min(Label
.left
+ MAX_EMPTYTEXT_SELECT_WIDTH
, Label
.right
);
2451 UnionRect(&SelectBox
, &Icon
, &Label
);
2453 if (lprcSelectBox
) *lprcSelectBox
= SelectBox
;
2454 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox
));
2457 /* Fix the Box if necessary */
2460 if (oversizedBox
) UnionRect(lprcBox
, &Box
, &Label
);
2461 else *lprcBox
= Box
;
2463 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box
));
2467 * DESCRIPTION: [INTERNAL]
2470 * [I] infoPtr : valid pointer to the listview structure
2471 * [I] nItem : item number
2472 * [O] lprcBox : ptr to Box rectangle
2477 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO
*infoPtr
, INT nItem
, LPRECT lprcBox
)
2479 WCHAR szDispText
[DISP_TEXT_SIZE
] = { '\0' };
2480 POINT Position
, Origin
;
2483 LISTVIEW_GetOrigin(infoPtr
, &Origin
);
2484 LISTVIEW_GetItemOrigin(infoPtr
, nItem
, &Position
);
2486 /* Be smart and try to figure out the minimum we have to do */
2488 if (infoPtr
->uView
== LV_VIEW_ICON
&& infoPtr
->bFocus
&& LISTVIEW_GetItemState(infoPtr
, nItem
, LVIS_FOCUSED
))
2489 lvItem
.mask
|= LVIF_TEXT
;
2490 lvItem
.iItem
= nItem
;
2491 lvItem
.iSubItem
= 0;
2492 lvItem
.pszText
= szDispText
;
2493 lvItem
.cchTextMax
= DISP_TEXT_SIZE
;
2494 if (lvItem
.mask
) LISTVIEW_GetItemW(infoPtr
, &lvItem
);
2495 if (infoPtr
->uView
== LV_VIEW_ICON
)
2497 lvItem
.mask
|= LVIF_STATE
;
2498 lvItem
.stateMask
= LVIS_FOCUSED
;
2499 lvItem
.state
= (lvItem
.mask
& LVIF_TEXT
? LVIS_FOCUSED
: 0);
2501 LISTVIEW_GetItemMetrics(infoPtr
, &lvItem
, lprcBox
, 0, 0, 0, 0);
2503 if (infoPtr
->uView
== LV_VIEW_DETAILS
&& infoPtr
->dwLvExStyle
& LVS_EX_FULLROWSELECT
&&
2504 SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
, 0, 0))
2506 OffsetRect(lprcBox
, Origin
.x
, Position
.y
+ Origin
.y
);
2509 OffsetRect(lprcBox
, Position
.x
+ Origin
.x
, Position
.y
+ Origin
.y
);
2512 /* LISTVIEW_MapIdToIndex helper */
2513 static INT CALLBACK
MapIdSearchCompare(LPVOID p1
, LPVOID p2
, LPARAM lParam
)
2515 ITEM_ID
*id1
= (ITEM_ID
*)p1
;
2516 ITEM_ID
*id2
= (ITEM_ID
*)p2
;
2518 if (id1
->id
== id2
->id
) return 0;
2520 return (id1
->id
< id2
->id
) ? -1 : 1;
2525 * Returns the item index for id specified.
2528 * [I] infoPtr : valid pointer to the listview structure
2529 * [I] iID : item id to get index for
2532 * Item index, or -1 on failure.
2534 static INT
LISTVIEW_MapIdToIndex(const LISTVIEW_INFO
*infoPtr
, UINT iID
)
2539 TRACE("iID=%d\n", iID
);
2541 if (infoPtr
->dwStyle
& LVS_OWNERDATA
) return -1;
2542 if (infoPtr
->nItemCount
== 0) return -1;
2545 index
= DPA_Search(infoPtr
->hdpaItemIds
, &ID
, -1, &MapIdSearchCompare
, 0, DPAS_SORTED
);
2549 ITEM_ID
*lpID
= DPA_GetPtr(infoPtr
->hdpaItemIds
, index
);
2550 return DPA_GetPtrIndex(infoPtr
->hdpaItems
, lpID
->item
);
2558 * Returns the item id for index given.
2561 * [I] infoPtr : valid pointer to the listview structure
2562 * [I] iItem : item index to get id for
2567 static DWORD
LISTVIEW_MapIndexToId(const LISTVIEW_INFO
*infoPtr
, INT iItem
)
2572 TRACE("iItem=%d\n", iItem
);
2574 if (infoPtr
->dwStyle
& LVS_OWNERDATA
) return -1;
2575 if (iItem
< 0 || iItem
>= infoPtr
->nItemCount
) return -1;
2577 hdpaSubItems
= DPA_GetPtr(infoPtr
->hdpaItems
, iItem
);
2578 lpItem
= DPA_GetPtr(hdpaSubItems
, 0);
2580 return lpItem
->id
->id
;
2585 * Returns the current icon position, and advances it along the top.
2586 * The returned position is not offset by Origin.
2589 * [I] infoPtr : valid pointer to the listview structure
2590 * [O] lpPos : will get the current icon position
2595 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO
*infoPtr
, LPPOINT lpPos
)
2597 INT nListWidth
= infoPtr
->rcList
.right
- infoPtr
->rcList
.left
;
2599 *lpPos
= infoPtr
->currIconPos
;
2601 infoPtr
->currIconPos
.x
+= infoPtr
->nItemWidth
;
2602 if (infoPtr
->currIconPos
.x
+ infoPtr
->nItemWidth
<= nListWidth
) return;
2604 infoPtr
->currIconPos
.x
= 0;
2605 infoPtr
->currIconPos
.y
+= infoPtr
->nItemHeight
;
2611 * Returns the current icon position, and advances it down the left edge.
2612 * The returned position is not offset by Origin.
2615 * [I] infoPtr : valid pointer to the listview structure
2616 * [O] lpPos : will get the current icon position
2621 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO
*infoPtr
, LPPOINT lpPos
)
2623 INT nListHeight
= infoPtr
->rcList
.bottom
- infoPtr
->rcList
.top
;
2625 *lpPos
= infoPtr
->currIconPos
;
2627 infoPtr
->currIconPos
.y
+= infoPtr
->nItemHeight
;
2628 if (infoPtr
->currIconPos
.y
+ infoPtr
->nItemHeight
<= nListHeight
) return;
2630 infoPtr
->currIconPos
.x
+= infoPtr
->nItemWidth
;
2631 infoPtr
->currIconPos
.y
= 0;
2637 * Moves an icon to the specified position.
2638 * It takes care of invalidating the item, etc.
2641 * [I] infoPtr : valid pointer to the listview structure
2642 * [I] nItem : the item to move
2643 * [I] lpPos : the new icon position
2644 * [I] isNew : flags the item as being new
2650 static BOOL
LISTVIEW_MoveIconTo(const LISTVIEW_INFO
*infoPtr
, INT nItem
, const POINT
*lppt
, BOOL isNew
)
2656 old
.x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, nItem
);
2657 old
.y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, nItem
);
2659 if (lppt
->x
== old
.x
&& lppt
->y
== old
.y
) return TRUE
;
2660 LISTVIEW_InvalidateItem(infoPtr
, nItem
);
2663 /* Allocating a POINTER for every item is too resource intensive,
2664 * so we'll keep the (x,y) in different arrays */
2665 if (!DPA_SetPtr(infoPtr
->hdpaPosX
, nItem
, (void *)(LONG_PTR
)lppt
->x
)) return FALSE
;
2666 if (!DPA_SetPtr(infoPtr
->hdpaPosY
, nItem
, (void *)(LONG_PTR
)lppt
->y
)) return FALSE
;
2668 LISTVIEW_InvalidateItem(infoPtr
, nItem
);
2675 * Arranges listview items in icon display mode.
2678 * [I] infoPtr : valid pointer to the listview structure
2679 * [I] nAlignCode : alignment code
2685 static BOOL
LISTVIEW_Arrange(LISTVIEW_INFO
*infoPtr
, INT nAlignCode
)
2687 void (*next_pos
)(LISTVIEW_INFO
*, LPPOINT
);
2691 if (infoPtr
->uView
!= LV_VIEW_ICON
&& infoPtr
->uView
!= LV_VIEW_SMALLICON
) return FALSE
;
2693 TRACE("nAlignCode=%d\n", nAlignCode
);
2695 if (nAlignCode
== LVA_DEFAULT
)
2697 if (infoPtr
->dwStyle
& LVS_ALIGNLEFT
) nAlignCode
= LVA_ALIGNLEFT
;
2698 else nAlignCode
= LVA_ALIGNTOP
;
2703 case LVA_ALIGNLEFT
: next_pos
= LISTVIEW_NextIconPosLeft
; break;
2704 case LVA_ALIGNTOP
: next_pos
= LISTVIEW_NextIconPosTop
; break;
2705 case LVA_SNAPTOGRID
: next_pos
= LISTVIEW_NextIconPosTop
; break; /* FIXME */
2706 default: return FALSE
;
2709 infoPtr
->bAutoarrange
= TRUE
;
2710 infoPtr
->currIconPos
.x
= infoPtr
->currIconPos
.y
= 0;
2711 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2713 next_pos(infoPtr
, &pos
);
2714 LISTVIEW_MoveIconTo(infoPtr
, i
, &pos
, FALSE
);
2722 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2723 * For LVS_REPORT always returns empty rectangle.
2726 * [I] infoPtr : valid pointer to the listview structure
2727 * [O] lprcView : bounding rectangle
2733 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO
*infoPtr
, LPRECT lprcView
)
2737 SetRectEmpty(lprcView
);
2739 switch (infoPtr
->uView
)
2742 case LV_VIEW_SMALLICON
:
2743 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2745 x
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosX
, i
);
2746 y
= (LONG_PTR
)DPA_GetPtr(infoPtr
->hdpaPosY
, i
);
2747 lprcView
->right
= max(lprcView
->right
, x
);
2748 lprcView
->bottom
= max(lprcView
->bottom
, y
);
2750 if (infoPtr
->nItemCount
> 0)
2752 lprcView
->right
+= infoPtr
->nItemWidth
;
2753 lprcView
->bottom
+= infoPtr
->nItemHeight
;
2758 y
= LISTVIEW_GetCountPerColumn(infoPtr
);
2759 x
= infoPtr
->nItemCount
/ y
;
2760 if (infoPtr
->nItemCount
% y
) x
++;
2761 lprcView
->right
= x
* infoPtr
->nItemWidth
;
2762 lprcView
->bottom
= y
* infoPtr
->nItemHeight
;
2769 * Retrieves the bounding rectangle of all the items.
2772 * [I] infoPtr : valid pointer to the listview structure
2773 * [O] lprcView : bounding rectangle
2779 static BOOL
LISTVIEW_GetViewRect(const LISTVIEW_INFO
*infoPtr
, LPRECT lprcView
)
2783 TRACE("(lprcView=%p)\n", lprcView
);
2785 if (!lprcView
) return FALSE
;
2787 LISTVIEW_GetAreaRect(infoPtr
, lprcView
);
2789 if (infoPtr
->uView
!= LV_VIEW_DETAILS
)
2791 LISTVIEW_GetOrigin(infoPtr
, &ptOrigin
);
2792 OffsetRect(lprcView
, ptOrigin
.x
, ptOrigin
.y
);
2795 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView
));
2802 * Retrieves the subitem pointer associated with the subitem index.
2805 * [I] hdpaSubItems : DPA handle for a specific item
2806 * [I] nSubItem : index of subitem
2809 * SUCCESS : subitem pointer
2812 static SUBITEM_INFO
* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems
, INT nSubItem
)
2814 SUBITEM_INFO
*lpSubItem
;
2817 /* we should binary search here if need be */
2818 for (i
= 1; i
< DPA_GetPtrCount(hdpaSubItems
); i
++)
2820 lpSubItem
= DPA_GetPtr(hdpaSubItems
, i
);
2821 if (lpSubItem
->iSubItem
== nSubItem
)
2831 * Calculates the desired item width.
2834 * [I] infoPtr : valid pointer to the listview structure
2837 * The desired item width.
2839 static INT
LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO
*infoPtr
)
2843 TRACE("uView=%d\n", infoPtr
->uView
);
2845 if (infoPtr
->uView
== LV_VIEW_ICON
)
2846 nItemWidth
= infoPtr
->iconSpacing
.cx
;
2847 else if (infoPtr
->uView
== LV_VIEW_DETAILS
)
2849 if (DPA_GetPtrCount(infoPtr
->hdpaColumns
) > 0)
2854 index
= SendMessageW(infoPtr
->hwndHeader
, HDM_ORDERTOINDEX
,
2855 DPA_GetPtrCount(infoPtr
->hdpaColumns
) - 1, 0);
2857 LISTVIEW_GetHeaderRect(infoPtr
, index
, &rcHeader
);
2858 nItemWidth
= rcHeader
.right
;
2861 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2863 WCHAR szDispText
[DISP_TEXT_SIZE
] = { '\0' };
2867 lvItem
.mask
= LVIF_TEXT
;
2868 lvItem
.iSubItem
= 0;
2870 for (i
= 0; i
< infoPtr
->nItemCount
; i
++)
2873 lvItem
.pszText
= szDispText
;
2874 lvItem
.cchTextMax
= DISP_TEXT_SIZE
;
2875 if (LISTVIEW_GetItemW(infoPtr
, &lvItem
))
2876 nItemWidth
= max(LISTVIEW_GetStringWidthT(infoPtr
, lvItem
.pszText
, TRUE
),
2880 if (infoPtr
->himlSmall
) nItemWidth
+= infoPtr
->iconSize
.cx
;
2881 if (infoPtr
->himlState
) nItemWidth
+= infoPtr
->iconStateSize
.cx
;
2883 nItemWidth
= max(DEFAULT_COLUMN_WIDTH
, nItemWidth
+ WIDTH_PADDING
);
2891 * Calculates the desired item height.
2894 * [I] infoPtr : valid pointer to the listview structure
2897 * The desired item height.
2899 static INT
LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO
*infoPtr
)
2903 TRACE("uView=%d\n", infoPtr
->uView
);
2905 if (infoPtr
->uView
== LV_VIEW_ICON
)
2906 nItemHeight
= infoPtr
->iconSpacing
.cy
;
2909 nItemHeight
= infoPtr
->ntmHeight
;
2910 if (infoPtr
->uView
== LV_VIEW_DETAILS
&& infoPtr
->dwLvExStyle
& LVS_EX_GRIDLINES
)
2912 if (infoPtr
->himlState
)
2913 nItemHeight
= max(nItemHeight
, infoPtr
->iconStateSize
.cy
);
2914 if (infoPtr
->himlSmall
)
2915 nItemHeight
= max(nItemHeight
, infoPtr
->iconSize
.cy
);
2916 if (infoPtr
->himlState
|| infoPtr
->himlSmall
)
2917 nItemHeight
+= HEIGHT_PADDING
;
2918 if (infoPtr
->nMeasureItemHeight
> 0)
2919 nItemHeight
= infoPtr
->nMeasureItemHeight
;
2922 return max(nItemHeight
, 1);
2927 * Updates the width, and height of an item.
2930 * [I] infoPtr : valid pointer to the listview structure
2935 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO
*infoPtr
)
2937 infoPtr
->nItemWidth
= LISTVIEW_CalculateItemWidth(infoPtr
);
2938 infoPtr
->nItemHeight
= LISTVIEW_CalculateItemHeight(infoPtr
);
2944 * Retrieves and saves important text metrics info for the current
2948 * [I] infoPtr : valid pointer to the listview structure
2951 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO
*infoPtr
)
2953 HDC hdc
= GetDC(infoPtr
->hwndSelf
);
2954 HFONT hFont
= infoPtr
->hFont
? infoPtr
->hFont
: infoPtr
->hDefaultFont
;
2955 HFONT hOldFont
= SelectObject(hdc
, hFont
);
2959 if (GetTextMetricsW(hdc
, &tm
))
2961 infoPtr
->ntmHeight
= tm
.tmHeight
;
2962 infoPtr
->ntmMaxCharWidth
= tm
.tmMaxCharWidth
;
2965 if (GetTextExtentPoint32A(hdc
, "...", 3, &sz
))
2966 infoPtr
->nEllipsisWidth
= sz
.cx
;
2968 SelectObject(hdc
, hOldFont
);
2969 ReleaseDC(infoPtr
->hwndSelf
, hdc
);
2971 TRACE("tmHeight=%d\n", infoPtr
->ntmHeight
);
2976 * A compare function for ranges
2979 * [I] range1 : pointer to range 1;
2980 * [I] range2 : pointer to range 2;
2984 * > 0 : if range 1 > range 2
2985 * < 0 : if range 2 > range 1
2986 * = 0 : if range intersects range 2
2988 static INT CALLBACK
ranges_cmp(LPVOID range1
, LPVOID range2
, LPARAM flags
)
2992 if (((RANGE
*)range1
)->upper
<= ((RANGE
*)range2
)->lower
)
2994 else if (((RANGE
*)range2
)->upper
<= ((RANGE
*)range1
)->lower
)
2999 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1
), debugrange(range2
), cmp
);
3004 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
3006 static void ranges_assert(RANGES ranges
, LPCSTR desc
, const char *func
, int line
)
3011 TRACE("*** Checking %s:%d:%s ***\n", func
, line
, desc
);
3013 assert (DPA_GetPtrCount(ranges
->hdpa
) >= 0);
3014 ranges_dump(ranges
);
3015 if (DPA_GetPtrCount(ranges
->hdpa
) > 0)
3017 prev
= DPA_GetPtr(ranges
->hdpa
, 0);
3018 assert (prev
->lower
>= 0 && prev
->lower
< prev
->upper
);
3019 for (i
= 1; i
< DPA_GetPtrCount(ranges
->hdpa
); i
++)
3021 curr
= DPA_GetPtr(ranges
->hdpa
, i
);
3022 assert (prev
->upper
<= curr
->lower
);
3023 assert (curr
->lower
< curr
->upper
);
3027 TRACE("--- Done checking---\n");
3030 static RANGES
ranges_create(int count
)
3032 RANGES ranges
= Alloc(sizeof(struct tagRANGES
));
3033 if (!ranges
) return NULL
;
3034 ranges
->hdpa
= DPA_Create(count
);
3035 if (ranges
->hdpa
) return ranges
;
3040 static void ranges_clear(RANGES ranges
)
3044 for(i
= 0; i
< DPA_GetPtrCount(ranges
->hdpa
); i
++)
3045 Free(DPA_GetPtr(ranges
->hdpa
, i
));
3046 DPA_DeleteAllPtrs(ranges
->hdpa
);
3050 static void ranges_destroy(RANGES ranges
)
3052 if (!ranges
) return;
3053 ranges_clear(ranges
);
3054 DPA_Destroy(ranges
->hdpa
);
3058 static RANGES
ranges_clone(RANGES ranges
)
3063 if (!(clone
= ranges_create(DPA_GetPtrCount(ranges
->hdpa
)))) goto fail
;
3065 for (i
= 0; i
< DPA_GetPtrCount(ranges
->hdpa
); i
++)
3067 RANGE
*newrng
= Alloc(sizeof(RANGE
));
3068 if (!newrng
) goto fail
;
3069 *newrng
= *((RANGE
*)DPA_GetPtr(ranges
->hdpa
, i
));
3070 DPA_SetPtr(clone
->hdpa
, i
, newrng
);
3075 TRACE ("clone failed\n");
3076 ranges_destroy(clone
);
3080 static RANGES
ranges_diff(RANGES ranges
, RANGES sub
)
3084 for (i
= 0; i
< DPA_GetPtrCount(sub
->hdpa
); i
++)
3085 ranges_del(ranges
, *((RANGE
*)DPA_GetPtr(sub
->hdpa
, i
)));
3090 static void ranges_dump(RANGES ranges
)
3094 for (i
= 0; i
< DPA_GetPtrCount(ranges
->hdpa
); i
++)
3095 TRACE(" %s\n", debugrange