comctl32: Implement LVN_ODFINDITEM.
[wine/hacks.git] / dlls / comctl32 / listview.c
blobaf84938eb884330576cdddbf46ad3513a6a945ca
1 /*
2 * Listview control
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
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
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_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
63 * Speedups
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
73 * Flags
74 * -- LVIF_COLUMNS
75 * -- LVIF_GROUPID
76 * -- LVIF_NORECOMPUTE
78 * States
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVIS_CUT
81 * -- LVIS_DROPHILITED
82 * -- LVIS_OVERLAYMASK
84 * Styles
85 * -- LVS_NOLABELWRAP
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
88 * -- LVS_ALIGNTOP
89 * -- LVS_TYPESTYLEMASK
91 * Extended Styles
92 * -- LVS_EX_BORDERSELECT
93 * -- LVS_EX_FLATSB
94 * -- LVS_EX_HEADERDRAGDROP
95 * -- LVS_EX_INFOTIP
96 * -- LVS_EX_LABELTIP
97 * -- LVS_EX_MULTIWORKAREAS
98 * -- LVS_EX_REGIONAL
99 * -- LVS_EX_SIMPLESELECT
100 * -- LVS_EX_TWOCLICKACTIVATE
101 * -- LVS_EX_UNDERLINECOLD
102 * -- LVS_EX_UNDERLINEHOT
104 * Notifications:
105 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
106 * -- LVN_GETINFOTIP
107 * -- LVN_HOTTRACK
108 * -- LVN_MARQUEEBEGIN
109 * -- LVN_SETDISPINFO
110 * -- NM_HOVER
111 * -- LVN_BEGINRDRAG
113 * Messages:
114 * -- LVM_CANCELEDITLABEL
115 * -- LVM_ENABLEGROUPVIEW
116 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
117 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
118 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
119 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
120 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
121 * -- LVM_GETINSERTMARKRECT
122 * -- LVM_GETNUMBEROFWORKAREAS
123 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
124 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
125 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
126 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
127 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
128 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
129 * -- LVM_GETVIEW, LVM_SETVIEW
130 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
131 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
132 * -- LVM_INSERTGROUPSORTED
133 * -- LVM_INSERTMARKHITTEST
134 * -- LVM_ISGROUPVIEWENABLED
135 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
136 * -- LVM_MOVEGROUP
137 * -- LVM_MOVEITEMTOGROUP
138 * -- LVM_SETINFOTIP
139 * -- LVM_SETTILEWIDTH
140 * -- LVM_SORTGROUPS
141 * -- LVM_SORTITEMSEX
143 * Macros:
144 * -- ListView_GetCheckSate, ListView_SetCheckState
145 * -- ListView_GetHoverTime, ListView_SetHoverTime
146 * -- ListView_GetISearchString
147 * -- ListView_GetNumberOfWorkAreas
148 * -- ListView_GetOrigin
149 * -- ListView_GetTextBkColor
150 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
151 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
152 * -- ListView_SortItemsEx
154 * Functions:
155 * -- LVGroupComparE
157 * Known differences in message stream from native control (not known if
158 * these differences cause problems):
159 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
160 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
161 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
162 * processing for "USEDOUBLECLICKTIME".
165 #include "config.h"
166 #include "wine/port.h"
168 #include <assert.h>
169 #include <ctype.h>
170 #include <string.h>
171 #include <stdlib.h>
172 #include <stdarg.h>
173 #include <stdio.h>
175 #include "windef.h"
176 #include "winbase.h"
177 #include "winnt.h"
178 #include "wingdi.h"
179 #include "winuser.h"
180 #include "winnls.h"
181 #include "commctrl.h"
182 #include "comctl32.h"
183 #include "uxtheme.h"
185 #include "wine/debug.h"
186 #include "wine/unicode.h"
188 WINE_DEFAULT_DEBUG_CHANNEL(listview);
190 /* make sure you set this to 0 for production use! */
191 #define DEBUG_RANGES 1
193 typedef struct tagCOLUMN_INFO
195 RECT rcHeader; /* tracks the header's rectangle */
196 int fmt; /* same as LVCOLUMN.fmt */
197 } COLUMN_INFO;
199 typedef struct tagITEMHDR
201 LPWSTR pszText;
202 INT iImage;
203 } ITEMHDR, *LPITEMHDR;
205 typedef struct tagSUBITEM_INFO
207 ITEMHDR hdr;
208 INT iSubItem;
209 } SUBITEM_INFO;
211 typedef struct tagITEM_INFO
213 ITEMHDR hdr;
214 UINT state;
215 LPARAM lParam;
216 INT iIndent;
217 } ITEM_INFO;
219 typedef struct tagRANGE
221 INT lower;
222 INT upper;
223 } RANGE;
225 typedef struct tagRANGES
227 HDPA hdpa;
228 } *RANGES;
230 typedef struct tagITERATOR
232 INT nItem;
233 INT nSpecial;
234 RANGE range;
235 RANGES ranges;
236 INT index;
237 } ITERATOR;
239 typedef struct tagDELAYED_ITEM_EDIT
241 BOOL fEnabled;
242 INT iItem;
243 } DELAYED_ITEM_EDIT;
245 typedef struct tagLISTVIEW_INFO
247 HWND hwndSelf;
248 HBRUSH hBkBrush;
249 COLORREF clrBk;
250 COLORREF clrText;
251 COLORREF clrTextBk;
252 HIMAGELIST himlNormal;
253 HIMAGELIST himlSmall;
254 HIMAGELIST himlState;
255 BOOL bLButtonDown;
256 BOOL bRButtonDown;
257 BOOL bDragging;
258 POINT ptClickPos; /* point where the user clicked */
259 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
260 INT nItemHeight;
261 INT nItemWidth;
262 RANGES selectionRanges;
263 INT nSelectionMark;
264 INT nHotItem;
265 SHORT notifyFormat;
266 HWND hwndNotify;
267 RECT rcList; /* This rectangle is really the window
268 * client rectangle possibly reduced by the
269 * horizontal scroll bar and/or header - see
270 * LISTVIEW_UpdateSize. This rectangle offset
271 * by the LISTVIEW_GetOrigin value is in
272 * client coordinates */
273 SIZE iconSize;
274 SIZE iconSpacing;
275 SIZE iconStateSize;
276 UINT uCallbackMask;
277 HWND hwndHeader;
278 HCURSOR hHotCursor;
279 HFONT hDefaultFont;
280 HFONT hFont;
281 INT ntmHeight; /* Some cached metrics of the font used */
282 INT ntmMaxCharWidth; /* by the listview to draw items */
283 INT nEllipsisWidth;
284 BOOL bRedraw; /* Turns on/off repaints & invalidations */
285 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
286 BOOL bFocus;
287 BOOL bDoChangeNotify; /* send change notification messages? */
288 INT nFocusedItem;
289 RECT rcFocus;
290 DWORD dwStyle; /* the cached window GWL_STYLE */
291 DWORD dwLvExStyle; /* extended listview style */
292 INT nItemCount; /* the number of items in the list */
293 HDPA hdpaItems; /* array ITEM_INFO pointers */
294 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
295 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
296 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
297 POINT currIconPos; /* this is the position next icon will be placed */
298 PFNLVCOMPARE pfnCompare;
299 LPARAM lParamSort;
300 HWND hwndEdit;
301 WNDPROC EditWndProc;
302 INT nEditLabelItem;
303 DWORD dwHoverTime;
304 HWND hwndToolTip;
306 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
308 DWORD lastKeyPressTimestamp;
309 WPARAM charCode;
310 INT nSearchParamLength;
311 WCHAR szSearchParam[ MAX_PATH ];
312 BOOL bIsDrawing;
313 INT nMeasureItemHeight;
314 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
315 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
316 } LISTVIEW_INFO;
319 * constants
321 /* How many we debug buffer to allocate */
322 #define DEBUG_BUFFERS 20
323 /* The size of a single debug bbuffer */
324 #define DEBUG_BUFFER_SIZE 256
326 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
327 #define SB_INTERNAL -1
329 /* maximum size of a label */
330 #define DISP_TEXT_SIZE 512
332 /* padding for items in list and small icon display modes */
333 #define WIDTH_PADDING 12
335 /* padding for items in list, report and small icon display modes */
336 #define HEIGHT_PADDING 1
338 /* offset of items in report display mode */
339 #define REPORT_MARGINX 2
341 /* padding for icon in large icon display mode
342 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
343 * that HITTEST will see.
344 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
345 * ICON_TOP_PADDING - sum of the two above.
346 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
347 * LABEL_HOR_PADDING - between text and sides of box
348 * LABEL_VERT_PADDING - between bottom of text and end of box
350 * ICON_LR_PADDING - additional width above icon size.
351 * ICON_LR_HALF - half of the above value
353 #define ICON_TOP_PADDING_NOTHITABLE 2
354 #define ICON_TOP_PADDING_HITABLE 2
355 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
356 #define ICON_BOTTOM_PADDING 4
357 #define LABEL_HOR_PADDING 5
358 #define LABEL_VERT_PADDING 7
359 #define ICON_LR_PADDING 16
360 #define ICON_LR_HALF (ICON_LR_PADDING/2)
362 /* default label width for items in list and small icon display modes */
363 #define DEFAULT_LABEL_WIDTH 40
365 /* default column width for items in list display mode */
366 #define DEFAULT_COLUMN_WIDTH 128
368 /* Size of "line" scroll for V & H scrolls */
369 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
371 /* Padding between image and label */
372 #define IMAGE_PADDING 2
374 /* Padding behind the label */
375 #define TRAILING_LABEL_PADDING 12
376 #define TRAILING_HEADER_PADDING 11
378 /* Border for the icon caption */
379 #define CAPTION_BORDER 2
381 /* Standard DrawText flags */
382 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
383 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
384 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 /* Image index from state */
387 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
389 /* The time in milliseconds to reset the search in the list */
390 #define KEY_DELAY 450
392 /* Dump the LISTVIEW_INFO structure to the debug channel */
393 #define LISTVIEW_DUMP(iP) do { \
394 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
395 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
396 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
397 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
398 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
399 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
400 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
401 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
402 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
403 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
404 } while(0)
406 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
409 * forward declarations
411 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
412 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
413 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
414 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
415 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
416 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
417 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
418 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
419 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
420 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
421 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
422 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
423 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
424 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
425 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
426 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
427 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
428 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
429 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
430 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
431 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
432 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
433 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
434 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
435 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
436 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
437 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
439 /******** Text handling functions *************************************/
441 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
442 * text string. The string may be ANSI or Unicode, in which case
443 * the boolean isW tells us the type of the string.
445 * The name of the function tell what type of strings it expects:
446 * W: Unicode, T: ANSI/Unicode - function of isW
449 static inline BOOL is_textW(LPCWSTR text)
451 return text != NULL && text != LPSTR_TEXTCALLBACKW;
454 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
456 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
457 return is_textW(text);
460 static inline int textlenT(LPCWSTR text, BOOL isW)
462 return !is_textT(text, isW) ? 0 :
463 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
466 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
468 if (isDestW)
469 if (isSrcW) lstrcpynW(dest, src, max);
470 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
471 else
472 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
473 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
476 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
478 LPWSTR wstr = (LPWSTR)text;
480 if (!isW && is_textT(text, isW))
482 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
483 wstr = Alloc(len * sizeof(WCHAR));
484 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
486 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
487 return wstr;
490 static inline void textfreeT(LPWSTR wstr, BOOL isW)
492 if (!isW && is_textT(wstr, isW)) Free (wstr);
496 * dest is a pointer to a Unicode string
497 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
499 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
501 BOOL bResult = TRUE;
503 if (src == LPSTR_TEXTCALLBACKW)
505 if (is_textW(*dest)) Free(*dest);
506 *dest = LPSTR_TEXTCALLBACKW;
508 else
510 LPWSTR pszText = textdupTtoW(src, isW);
511 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
512 bResult = Str_SetPtrW(dest, pszText);
513 textfreeT(pszText, isW);
515 return bResult;
519 * compares a Unicode to a Unicode/ANSI text string
521 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
523 if (!aw) return bt ? -1 : 0;
524 if (!bt) return aw ? 1 : 0;
525 if (aw == LPSTR_TEXTCALLBACKW)
526 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
527 if (bt != LPSTR_TEXTCALLBACKW)
529 LPWSTR bw = textdupTtoW(bt, isW);
530 int r = bw ? lstrcmpW(aw, bw) : 1;
531 textfreeT(bw, isW);
532 return r;
535 return 1;
538 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
540 int res;
542 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
543 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
544 return res ? res - sizeof(WCHAR) : res;
547 /******** Debugging functions *****************************************/
549 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
551 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
552 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
555 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
557 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
558 n = min(textlenT(text, isW), n);
559 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
562 static char* debug_getbuf(void)
564 static int index = 0;
565 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
566 return buffers[index++ % DEBUG_BUFFERS];
569 static inline const char* debugrange(const RANGE *lprng)
571 if (!lprng) return "(null)";
572 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
575 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
577 char* buf = debug_getbuf(), *text = buf;
578 int len, size = DEBUG_BUFFER_SIZE;
580 if (pScrollInfo == NULL) return "(null)";
581 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
582 if (len == -1) goto end; buf += len; size -= len;
583 if (pScrollInfo->fMask & SIF_RANGE)
584 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
585 else len = 0;
586 if (len == -1) goto end; buf += len; size -= len;
587 if (pScrollInfo->fMask & SIF_PAGE)
588 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
589 else len = 0;
590 if (len == -1) goto end; buf += len; size -= len;
591 if (pScrollInfo->fMask & SIF_POS)
592 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
593 else len = 0;
594 if (len == -1) goto end; buf += len; size -= len;
595 if (pScrollInfo->fMask & SIF_TRACKPOS)
596 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
597 else len = 0;
598 if (len == -1) goto end; buf += len; size -= len;
599 goto undo;
600 end:
601 buf = text + strlen(text);
602 undo:
603 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
604 return text;
607 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
609 if (!plvnm) return "(null)";
610 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
611 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
612 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
613 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
616 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
618 char* buf = debug_getbuf(), *text = buf;
619 int len, size = DEBUG_BUFFER_SIZE;
621 if (lpLVItem == NULL) return "(null)";
622 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
623 if (len == -1) goto end; buf += len; size -= len;
624 if (lpLVItem->mask & LVIF_STATE)
625 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
626 else len = 0;
627 if (len == -1) goto end; buf += len; size -= len;
628 if (lpLVItem->mask & LVIF_TEXT)
629 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
630 else len = 0;
631 if (len == -1) goto end; buf += len; size -= len;
632 if (lpLVItem->mask & LVIF_IMAGE)
633 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
634 else len = 0;
635 if (len == -1) goto end; buf += len; size -= len;
636 if (lpLVItem->mask & LVIF_PARAM)
637 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
638 else len = 0;
639 if (len == -1) goto end; buf += len; size -= len;
640 if (lpLVItem->mask & LVIF_INDENT)
641 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
642 else len = 0;
643 if (len == -1) goto end; buf += len; size -= len;
644 goto undo;
645 end:
646 buf = text + strlen(text);
647 undo:
648 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
649 return text;
652 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
654 char* buf = debug_getbuf(), *text = buf;
655 int len, size = DEBUG_BUFFER_SIZE;
657 if (lpColumn == NULL) return "(null)";
658 len = snprintf(buf, size, "{");
659 if (len == -1) goto end; buf += len; size -= len;
660 if (lpColumn->mask & LVCF_SUBITEM)
661 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
662 else len = 0;
663 if (len == -1) goto end; buf += len; size -= len;
664 if (lpColumn->mask & LVCF_FMT)
665 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
666 else len = 0;
667 if (len == -1) goto end; buf += len; size -= len;
668 if (lpColumn->mask & LVCF_WIDTH)
669 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
670 else len = 0;
671 if (len == -1) goto end; buf += len; size -= len;
672 if (lpColumn->mask & LVCF_TEXT)
673 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
674 else len = 0;
675 if (len == -1) goto end; buf += len; size -= len;
676 if (lpColumn->mask & LVCF_IMAGE)
677 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
678 else len = 0;
679 if (len == -1) goto end; buf += len; size -= len;
680 if (lpColumn->mask & LVCF_ORDER)
681 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
682 else len = 0;
683 if (len == -1) goto end; buf += len; size -= len;
684 goto undo;
685 end:
686 buf = text + strlen(text);
687 undo:
688 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
689 return text;
692 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
694 if (!lpht) return "(null)";
696 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
697 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
700 /* Return the corresponding text for a given scroll value */
701 static inline LPCSTR debugscrollcode(int nScrollCode)
703 switch(nScrollCode)
705 case SB_LINELEFT: return "SB_LINELEFT";
706 case SB_LINERIGHT: return "SB_LINERIGHT";
707 case SB_PAGELEFT: return "SB_PAGELEFT";
708 case SB_PAGERIGHT: return "SB_PAGERIGHT";
709 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
710 case SB_THUMBTRACK: return "SB_THUMBTRACK";
711 case SB_ENDSCROLL: return "SB_ENDSCROLL";
712 case SB_INTERNAL: return "SB_INTERNAL";
713 default: return "unknown";
718 /******** Notification functions i************************************/
720 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
722 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
723 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
726 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
728 LRESULT result;
730 TRACE("(code=%d)\n", code);
732 pnmh->hwndFrom = infoPtr->hwndSelf;
733 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
734 pnmh->code = code;
735 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
737 TRACE(" <= %ld\n", result);
739 return result;
742 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
744 NMHDR nmh;
745 HWND hwnd = infoPtr->hwndSelf;
746 notify_hdr(infoPtr, code, &nmh);
747 return IsWindow(hwnd);
750 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
752 NMITEMACTIVATE nmia;
753 LVITEMW item;
755 if (htInfo) {
756 nmia.uNewState = 0;
757 nmia.uOldState = 0;
758 nmia.uChanged = 0;
759 nmia.uKeyFlags = 0;
761 item.mask = LVIF_PARAM|LVIF_STATE;
762 item.iItem = htInfo->iItem;
763 item.iSubItem = 0;
764 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
765 nmia.lParam = item.lParam;
766 nmia.uOldState = item.state;
767 nmia.uNewState = item.state | LVIS_ACTIVATING;
768 nmia.uChanged = LVIF_STATE;
771 nmia.iItem = htInfo->iItem;
772 nmia.iSubItem = htInfo->iSubItem;
773 nmia.ptAction = htInfo->pt;
775 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
776 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
777 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
779 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
782 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
784 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
785 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
788 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
790 NMLISTVIEW nmlv;
791 LVITEMW item;
792 HWND hwnd = infoPtr->hwndSelf;
794 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
795 ZeroMemory(&nmlv, sizeof(nmlv));
796 nmlv.iItem = lvht->iItem;
797 nmlv.iSubItem = lvht->iSubItem;
798 nmlv.ptAction = lvht->pt;
799 item.mask = LVIF_PARAM;
800 item.iItem = lvht->iItem;
801 item.iSubItem = 0;
802 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
803 notify_listview(infoPtr, code, &nmlv);
804 return IsWindow(hwnd);
807 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
809 NMLISTVIEW nmlv;
810 LVITEMW item;
811 HWND hwnd = infoPtr->hwndSelf;
813 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
814 nmlv.iItem = nItem;
815 item.mask = LVIF_PARAM;
816 item.iItem = nItem;
817 item.iSubItem = 0;
818 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
819 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
820 return IsWindow(hwnd);
823 static int get_ansi_notification(UINT unicodeNotificationCode)
825 switch (unicodeNotificationCode)
827 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
828 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
829 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
830 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
831 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
832 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
834 ERR("unknown notification %x\n", unicodeNotificationCode);
835 assert(FALSE);
836 return 0;
840 Send notification. depends on dispinfoW having same
841 structure as dispinfoA.
842 infoPtr : listview struct
843 notificationCode : *Unicode* notification code
844 pdi : dispinfo structure (can be unicode or ansi)
845 isW : TRUE if dispinfo is Unicode
847 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
849 BOOL bResult = FALSE;
850 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
851 INT cchTempBufMax = 0, savCchTextMax = 0;
852 UINT realNotifCode;
853 LPWSTR pszTempBuf = NULL, savPszText = NULL;
855 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
857 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
858 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
861 if (convertToAnsi || convertToUnicode)
863 if (notificationCode != LVN_GETDISPINFOW)
865 cchTempBufMax = convertToUnicode ?
866 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
867 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
869 else
871 cchTempBufMax = pdi->item.cchTextMax;
872 *pdi->item.pszText = 0; /* make sure we don't process garbage */
875 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
876 if (!pszTempBuf) return FALSE;
878 if (convertToUnicode)
879 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
880 pszTempBuf, cchTempBufMax);
881 else
882 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
883 cchTempBufMax, NULL, NULL);
885 savCchTextMax = pdi->item.cchTextMax;
886 savPszText = pdi->item.pszText;
887 pdi->item.pszText = pszTempBuf;
888 pdi->item.cchTextMax = cchTempBufMax;
891 if (infoPtr->notifyFormat == NFR_ANSI)
892 realNotifCode = get_ansi_notification(notificationCode);
893 else
894 realNotifCode = notificationCode;
895 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
896 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
898 if (convertToUnicode || convertToAnsi)
900 if (convertToUnicode) /* note : pointer can be changed by app ! */
901 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
902 savCchTextMax, NULL, NULL);
903 else
904 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
905 savPszText, savCchTextMax);
906 pdi->item.pszText = savPszText; /* restores our buffer */
907 pdi->item.cchTextMax = savCchTextMax;
908 Free (pszTempBuf);
910 return bResult;
913 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
914 const RECT *rcBounds, const LVITEMW *lplvItem)
916 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
917 lpnmlvcd->nmcd.hdc = hdc;
918 lpnmlvcd->nmcd.rc = *rcBounds;
919 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
920 lpnmlvcd->clrText = infoPtr->clrText;
921 if (!lplvItem) return;
922 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
923 lpnmlvcd->iSubItem = lplvItem->iSubItem;
924 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
925 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
926 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
927 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
930 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
932 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
933 DWORD result;
935 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
936 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
937 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
938 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
939 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
940 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
941 return result;
944 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
946 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
947 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
948 if (lpnmlvcd->clrText == CLR_DEFAULT)
949 lpnmlvcd->clrText = comctl32_color.clrWindowText;
951 /* apparently, for selected items, we have to override the returned values */
952 if (!SubItem)
954 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
956 if (infoPtr->bFocus)
958 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
959 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
961 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
963 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
964 lpnmlvcd->clrText = comctl32_color.clrBtnText;
969 /* Set the text attributes */
970 if (lpnmlvcd->clrTextBk != CLR_NONE)
972 SetBkMode(hdc, OPAQUE);
973 SetBkColor(hdc,lpnmlvcd->clrTextBk);
975 else
976 SetBkMode(hdc, TRANSPARENT);
977 SetTextColor(hdc, lpnmlvcd->clrText);
980 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
982 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
985 /******** Item iterator functions **********************************/
987 static RANGES ranges_create(int count);
988 static void ranges_destroy(RANGES ranges);
989 static BOOL ranges_add(RANGES ranges, RANGE range);
990 static BOOL ranges_del(RANGES ranges, RANGE range);
991 static void ranges_dump(RANGES ranges);
993 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
995 RANGE range = { nItem, nItem + 1 };
997 return ranges_add(ranges, range);
1000 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1002 RANGE range = { nItem, nItem + 1 };
1004 return ranges_del(ranges, range);
1007 /***
1008 * ITERATOR DOCUMENTATION
1010 * The iterator functions allow for easy, and convenient iteration
1011 * over items of interest in the list. Typically, you create a
1012 * iterator, use it, and destroy it, as such:
1013 * ITERATOR i;
1015 * iterator_xxxitems(&i, ...);
1016 * while (iterator_{prev,next}(&i)
1018 * //code which uses i.nItem
1020 * iterator_destroy(&i);
1022 * where xxx is either: framed, or visible.
1023 * Note that it is important that the code destroys the iterator
1024 * after it's done with it, as the creation of the iterator may
1025 * allocate memory, which thus needs to be freed.
1027 * You can iterate both forwards, and backwards through the list,
1028 * by using iterator_next or iterator_prev respectively.
1030 * Lower numbered items are draw on top of higher number items in
1031 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1032 * items may overlap). So, to test items, you should use
1033 * iterator_next
1034 * which lists the items top to bottom (in Z-order).
1035 * For drawing items, you should use
1036 * iterator_prev
1037 * which lists the items bottom to top (in Z-order).
1038 * If you keep iterating over the items after the end-of-items
1039 * marker (-1) is returned, the iterator will start from the
1040 * beginning. Typically, you don't need to test for -1,
1041 * because iterator_{next,prev} will return TRUE if more items
1042 * are to be iterated over, or FALSE otherwise.
1044 * Note: the iterator is defined to be bidirectional. That is,
1045 * any number of prev followed by any number of next, or
1046 * five versa, should leave the iterator at the same item:
1047 * prev * n, next * n = next * n, prev * n
1049 * The iterator has a notion of an out-of-order, special item,
1050 * which sits at the start of the list. This is used in
1051 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1052 * which needs to be first, as it may overlap other items.
1054 * The code is a bit messy because we have:
1055 * - a special item to deal with
1056 * - simple range, or composite range
1057 * - empty range.
1058 * If you find bugs, or want to add features, please make sure you
1059 * always check/modify *both* iterator_prev, and iterator_next.
1062 /****
1063 * This function iterates through the items in increasing order,
1064 * but prefixed by the special item, then -1. That is:
1065 * special, 1, 2, 3, ..., n, -1.
1066 * Each item is listed only once.
1068 static inline BOOL iterator_next(ITERATOR* i)
1070 if (i->nItem == -1)
1072 i->nItem = i->nSpecial;
1073 if (i->nItem != -1) return TRUE;
1075 if (i->nItem == i->nSpecial)
1077 if (i->ranges) i->index = 0;
1078 goto pickarange;
1081 i->nItem++;
1082 testitem:
1083 if (i->nItem == i->nSpecial) i->nItem++;
1084 if (i->nItem < i->range.upper) return TRUE;
1086 pickarange:
1087 if (i->ranges)
1089 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1090 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1091 else goto end;
1093 else if (i->nItem >= i->range.upper) goto end;
1095 i->nItem = i->range.lower;
1096 if (i->nItem >= 0) goto testitem;
1097 end:
1098 i->nItem = -1;
1099 return FALSE;
1102 /****
1103 * This function iterates through the items in decreasing order,
1104 * followed by the special item, then -1. That is:
1105 * n, n-1, ..., 3, 2, 1, special, -1.
1106 * Each item is listed only once.
1108 static inline BOOL iterator_prev(ITERATOR* i)
1110 BOOL start = FALSE;
1112 if (i->nItem == -1)
1114 start = TRUE;
1115 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1116 goto pickarange;
1118 if (i->nItem == i->nSpecial)
1120 i->nItem = -1;
1121 return FALSE;
1124 testitem:
1125 i->nItem--;
1126 if (i->nItem == i->nSpecial) i->nItem--;
1127 if (i->nItem >= i->range.lower) return TRUE;
1129 pickarange:
1130 if (i->ranges)
1132 if (i->index > 0)
1133 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1134 else goto end;
1136 else if (!start && i->nItem < i->range.lower) goto end;
1138 i->nItem = i->range.upper;
1139 if (i->nItem > 0) goto testitem;
1140 end:
1141 return (i->nItem = i->nSpecial) != -1;
1144 static RANGE iterator_range(const ITERATOR *i)
1146 RANGE range;
1148 if (!i->ranges) return i->range;
1150 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1152 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1153 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1155 else range.lower = range.upper = 0;
1157 return range;
1160 /***
1161 * Releases resources associated with this ierator.
1163 static inline void iterator_destroy(const ITERATOR *i)
1165 ranges_destroy(i->ranges);
1168 /***
1169 * Create an empty iterator.
1171 static inline BOOL iterator_empty(ITERATOR* i)
1173 ZeroMemory(i, sizeof(*i));
1174 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1175 return TRUE;
1178 /***
1179 * Create an iterator over a range.
1181 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1183 iterator_empty(i);
1184 i->range = range;
1185 return TRUE;
1188 /***
1189 * Create an iterator over a bunch of ranges.
1190 * Please note that the iterator will take ownership of the ranges,
1191 * and will free them upon destruction.
1193 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1195 iterator_empty(i);
1196 i->ranges = ranges;
1197 return TRUE;
1200 /***
1201 * Creates an iterator over the items which intersect lprc.
1203 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1205 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1206 RECT frame = *lprc, rcItem, rcTemp;
1207 POINT Origin;
1209 /* in case we fail, we want to return an empty iterator */
1210 if (!iterator_empty(i)) return FALSE;
1212 LISTVIEW_GetOrigin(infoPtr, &Origin);
1214 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1215 OffsetRect(&frame, -Origin.x, -Origin.y);
1217 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1219 INT nItem;
1221 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1223 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1224 if (IntersectRect(&rcTemp, &rcItem, lprc))
1225 i->nSpecial = infoPtr->nFocusedItem;
1227 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1228 /* to do better here, we need to have PosX, and PosY sorted */
1229 TRACE("building icon ranges:\n");
1230 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1232 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1233 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1234 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1235 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1236 if (IntersectRect(&rcTemp, &rcItem, &frame))
1237 ranges_additem(i->ranges, nItem);
1239 return TRUE;
1241 else if (uView == LVS_REPORT)
1243 RANGE range;
1245 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1246 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1248 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1249 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1250 if (range.upper <= range.lower) return TRUE;
1251 if (!iterator_rangeitems(i, range)) return FALSE;
1252 TRACE(" report=%s\n", debugrange(&i->range));
1254 else
1256 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1257 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1258 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1259 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1260 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1261 INT lower = nFirstCol * nPerCol + nFirstRow;
1262 RANGE item_range;
1263 INT nCol;
1265 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1266 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1268 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1270 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1271 TRACE("building list ranges:\n");
1272 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1274 item_range.lower = nCol * nPerCol + nFirstRow;
1275 if(item_range.lower >= infoPtr->nItemCount) break;
1276 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1277 TRACE(" list=%s\n", debugrange(&item_range));
1278 ranges_add(i->ranges, item_range);
1282 return TRUE;
1285 /***
1286 * Creates an iterator over the items which intersect the visible region of hdc.
1288 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1290 POINT Origin, Position;
1291 RECT rcItem, rcClip;
1292 INT rgntype;
1294 rgntype = GetClipBox(hdc, &rcClip);
1295 if (rgntype == NULLREGION) return iterator_empty(i);
1296 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1297 if (rgntype == SIMPLEREGION) return TRUE;
1299 /* first deal with the special item */
1300 if (i->nSpecial != -1)
1302 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1303 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1306 /* if we can't deal with the region, we'll just go with the simple range */
1307 LISTVIEW_GetOrigin(infoPtr, &Origin);
1308 TRACE("building visible range:\n");
1309 if (!i->ranges && i->range.lower < i->range.upper)
1311 if (!(i->ranges = ranges_create(50))) return TRUE;
1312 if (!ranges_add(i->ranges, i->range))
1314 ranges_destroy(i->ranges);
1315 i->ranges = 0;
1316 return TRUE;
1320 /* now delete the invisible items from the list */
1321 while(iterator_next(i))
1323 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1324 rcItem.left = Position.x + Origin.x;
1325 rcItem.top = Position.y + Origin.y;
1326 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1327 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1328 if (!RectVisible(hdc, &rcItem))
1329 ranges_delitem(i->ranges, i->nItem);
1331 /* the iterator should restart on the next iterator_next */
1332 TRACE("done\n");
1334 return TRUE;
1337 /******** Misc helper functions ************************************/
1339 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1340 WPARAM wParam, LPARAM lParam, BOOL isW)
1342 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1343 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1346 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1348 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1350 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1351 (uView == LVS_ICON || uView == LVS_SMALLICON);
1354 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1356 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1357 if(state == 1 || state == 2)
1359 LVITEMW lvitem;
1360 state ^= 3;
1361 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1362 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1363 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1367 /******** Internal API functions ************************************/
1369 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1371 static COLUMN_INFO mainItem;
1373 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1374 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1375 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1378 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1380 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1383 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1385 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1388 /* Listview invalidation functions: use _only_ these functions to invalidate */
1390 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1392 return infoPtr->bRedraw;
1395 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1397 if(!is_redrawing(infoPtr)) return;
1398 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1399 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1402 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1404 RECT rcBox;
1406 if(!is_redrawing(infoPtr)) return;
1407 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1408 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1411 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1413 POINT Origin, Position;
1414 RECT rcBox;
1416 if(!is_redrawing(infoPtr)) return;
1417 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1418 LISTVIEW_GetOrigin(infoPtr, &Origin);
1419 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1420 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1421 rcBox.top = 0;
1422 rcBox.bottom = infoPtr->nItemHeight;
1423 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1424 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1427 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1429 LISTVIEW_InvalidateRect(infoPtr, NULL);
1432 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1434 RECT rcCol;
1436 if(!is_redrawing(infoPtr)) return;
1437 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1438 rcCol.top = infoPtr->rcList.top;
1439 rcCol.bottom = infoPtr->rcList.bottom;
1440 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1443 /***
1444 * DESCRIPTION:
1445 * Retrieves the number of items that can fit vertically in the client area.
1447 * PARAMETER(S):
1448 * [I] infoPtr : valid pointer to the listview structure
1450 * RETURN:
1451 * Number of items per row.
1453 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1455 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1457 return max(nListWidth/infoPtr->nItemWidth, 1);
1460 /***
1461 * DESCRIPTION:
1462 * Retrieves the number of items that can fit horizontally in the client
1463 * area.
1465 * PARAMETER(S):
1466 * [I] infoPtr : valid pointer to the listview structure
1468 * RETURN:
1469 * Number of items per column.
1471 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1473 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1475 return max(nListHeight / infoPtr->nItemHeight, 1);
1479 /*************************************************************************
1480 * LISTVIEW_ProcessLetterKeys
1482 * Processes keyboard messages generated by pressing the letter keys
1483 * on the keyboard.
1484 * What this does is perform a case insensitive search from the
1485 * current position with the following quirks:
1486 * - If two chars or more are pressed in quick succession we search
1487 * for the corresponding string (e.g. 'abc').
1488 * - If there is a delay we wipe away the current search string and
1489 * restart with just that char.
1490 * - If the user keeps pressing the same character, whether slowly or
1491 * fast, so that the search string is entirely composed of this
1492 * character ('aaaaa' for instance), then we search for first item
1493 * that starting with that character.
1494 * - If the user types the above character in quick succession, then
1495 * we must also search for the corresponding string ('aaaaa'), and
1496 * go to that string if there is a match.
1498 * PARAMETERS
1499 * [I] hwnd : handle to the window
1500 * [I] charCode : the character code, the actual character
1501 * [I] keyData : key data
1503 * RETURNS
1505 * Zero.
1507 * BUGS
1509 * - The current implementation has a list of characters it will
1510 * accept and it ignores everything else. In particular it will
1511 * ignore accentuated characters which seems to match what
1512 * Windows does. But I'm not sure it makes sense to follow
1513 * Windows there.
1514 * - We don't sound a beep when the search fails.
1516 * SEE ALSO
1518 * TREEVIEW_ProcessLetterKeys
1520 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1522 INT nItem;
1523 INT endidx,idx;
1524 LVITEMW item;
1525 WCHAR buffer[MAX_PATH];
1526 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1528 /* simple parameter checking */
1529 if (!charCode || !keyData) return 0;
1531 /* only allow the valid WM_CHARs through */
1532 if (!isalnumW(charCode) &&
1533 charCode != '.' && charCode != '`' && charCode != '!' &&
1534 charCode != '@' && charCode != '#' && charCode != '$' &&
1535 charCode != '%' && charCode != '^' && charCode != '&' &&
1536 charCode != '*' && charCode != '(' && charCode != ')' &&
1537 charCode != '-' && charCode != '_' && charCode != '+' &&
1538 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1539 charCode != '}' && charCode != '[' && charCode != '{' &&
1540 charCode != '/' && charCode != '?' && charCode != '>' &&
1541 charCode != '<' && charCode != ',' && charCode != '~')
1542 return 0;
1544 /* if there's one item or less, there is no where to go */
1545 if (infoPtr->nItemCount <= 1) return 0;
1547 /* update the search parameters */
1548 infoPtr->lastKeyPressTimestamp = GetTickCount();
1549 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1550 if (infoPtr->nSearchParamLength < MAX_PATH)
1551 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1552 if (infoPtr->charCode != charCode)
1553 infoPtr->charCode = charCode = 0;
1554 } else {
1555 infoPtr->charCode=charCode;
1556 infoPtr->szSearchParam[0]=charCode;
1557 infoPtr->nSearchParamLength=1;
1558 /* Redundant with the 1 char string */
1559 charCode=0;
1562 /* and search from the current position */
1563 nItem=-1;
1564 if (infoPtr->nFocusedItem >= 0) {
1565 endidx=infoPtr->nFocusedItem;
1566 idx=endidx;
1567 /* if looking for single character match,
1568 * then we must always move forward
1570 if (infoPtr->nSearchParamLength == 1)
1571 idx++;
1572 } else {
1573 endidx=infoPtr->nItemCount;
1574 idx=0;
1577 /* Let application handle this for virtual listview */
1578 if (infoPtr->dwStyle & LVS_OWNERDATA)
1580 NMLVFINDITEMW nmlv;
1581 LVFINDINFOW lvfi;
1583 ZeroMemory(&lvfi, sizeof(lvfi));
1584 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1585 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1586 lvfi.psz = infoPtr->szSearchParam;
1587 nmlv.iStart = idx;
1588 nmlv.lvfi = lvfi;
1590 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1592 if (nItem != -1)
1593 LISTVIEW_KeySelection(infoPtr, nItem);
1595 return 0;
1598 do {
1599 if (idx == infoPtr->nItemCount) {
1600 if (endidx == infoPtr->nItemCount || endidx == 0)
1601 break;
1602 idx=0;
1605 /* get item */
1606 item.mask = LVIF_TEXT;
1607 item.iItem = idx;
1608 item.iSubItem = 0;
1609 item.pszText = buffer;
1610 item.cchTextMax = MAX_PATH;
1611 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1613 /* check for a match */
1614 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1615 nItem=idx;
1616 break;
1617 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1618 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1619 /* This would work but we must keep looking for a longer match */
1620 nItem=idx;
1622 idx++;
1623 } while (idx != endidx);
1625 if (nItem != -1)
1626 LISTVIEW_KeySelection(infoPtr, nItem);
1628 return 0;
1631 /*************************************************************************
1632 * LISTVIEW_UpdateHeaderSize [Internal]
1634 * Function to resize the header control
1636 * PARAMS
1637 * [I] hwnd : handle to a window
1638 * [I] nNewScrollPos : scroll pos to set
1640 * RETURNS
1641 * None.
1643 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1645 RECT winRect;
1646 POINT point[2];
1648 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1650 GetWindowRect(infoPtr->hwndHeader, &winRect);
1651 point[0].x = winRect.left;
1652 point[0].y = winRect.top;
1653 point[1].x = winRect.right;
1654 point[1].y = winRect.bottom;
1656 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1657 point[0].x = -nNewScrollPos;
1658 point[1].x += nNewScrollPos;
1660 SetWindowPos(infoPtr->hwndHeader,0,
1661 point[0].x,point[0].y,point[1].x,point[1].y,
1662 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1663 SWP_NOZORDER | SWP_NOACTIVATE);
1666 /***
1667 * DESCRIPTION:
1668 * Update the scrollbars. This functions should be called whenever
1669 * the content, size or view changes.
1671 * PARAMETER(S):
1672 * [I] infoPtr : valid pointer to the listview structure
1674 * RETURN:
1675 * None
1677 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1679 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1680 SCROLLINFO horzInfo, vertInfo;
1681 INT dx, dy;
1683 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1685 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1686 horzInfo.cbSize = sizeof(SCROLLINFO);
1687 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1689 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1690 if (uView == LVS_LIST)
1692 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1693 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1695 /* scroll by at least one column per page */
1696 if(horzInfo.nPage < infoPtr->nItemWidth)
1697 horzInfo.nPage = infoPtr->nItemWidth;
1699 horzInfo.nPage /= infoPtr->nItemWidth;
1701 else if (uView == LVS_REPORT)
1703 horzInfo.nMax = infoPtr->nItemWidth;
1705 else /* LVS_ICON, or LVS_SMALLICON */
1707 RECT rcView;
1709 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1712 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1713 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1714 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1715 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1716 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1718 /* Setting the horizontal scroll can change the listview size
1719 * (and potentially everything else) so we need to recompute
1720 * everything again for the vertical scroll
1723 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1724 vertInfo.cbSize = sizeof(SCROLLINFO);
1725 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1727 if (uView == LVS_REPORT)
1729 vertInfo.nMax = infoPtr->nItemCount;
1731 /* scroll by at least one page */
1732 if(vertInfo.nPage < infoPtr->nItemHeight)
1733 vertInfo.nPage = infoPtr->nItemHeight;
1735 if (infoPtr->nItemHeight > 0)
1736 vertInfo.nPage /= infoPtr->nItemHeight;
1738 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1740 RECT rcView;
1742 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1745 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1746 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1747 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1748 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1749 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1751 /* Change of the range may have changed the scroll pos. If so move the content */
1752 if (dx != 0 || dy != 0)
1754 RECT listRect;
1755 listRect = infoPtr->rcList;
1756 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1757 SW_ERASE | SW_INVALIDATE);
1760 /* Update the Header Control */
1761 if (uView == LVS_REPORT)
1763 horzInfo.fMask = SIF_POS;
1764 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1765 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1770 /***
1771 * DESCRIPTION:
1772 * Shows/hides the focus rectangle.
1774 * PARAMETER(S):
1775 * [I] infoPtr : valid pointer to the listview structure
1776 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1778 * RETURN:
1779 * None
1781 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1783 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1784 HDC hdc;
1786 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1788 if (infoPtr->nFocusedItem < 0) return;
1790 /* we need some gymnastics in ICON mode to handle large items */
1791 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1793 RECT rcBox;
1795 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1796 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1798 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1799 return;
1803 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1805 /* for some reason, owner draw should work only in report mode */
1806 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1808 DRAWITEMSTRUCT dis;
1809 LVITEMW item;
1811 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1812 HFONT hOldFont = SelectObject(hdc, hFont);
1814 item.iItem = infoPtr->nFocusedItem;
1815 item.iSubItem = 0;
1816 item.mask = LVIF_PARAM;
1817 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1819 ZeroMemory(&dis, sizeof(dis));
1820 dis.CtlType = ODT_LISTVIEW;
1821 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1822 dis.itemID = item.iItem;
1823 dis.itemAction = ODA_FOCUS;
1824 if (fShow) dis.itemState |= ODS_FOCUS;
1825 dis.hwndItem = infoPtr->hwndSelf;
1826 dis.hDC = hdc;
1827 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1828 dis.itemData = item.lParam;
1830 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1832 SelectObject(hdc, hOldFont);
1834 else
1836 DrawFocusRect(hdc, &infoPtr->rcFocus);
1838 done:
1839 ReleaseDC(infoPtr->hwndSelf, hdc);
1842 /***
1843 * Invalidates all visible selected items.
1845 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1847 ITERATOR i;
1849 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1850 while(iterator_next(&i))
1852 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1853 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1855 iterator_destroy(&i);
1859 /***
1860 * DESCRIPTION: [INTERNAL]
1861 * Computes an item's (left,top) corner, relative to rcView.
1862 * That is, the position has NOT been made relative to the Origin.
1863 * This is deliberate, to avoid computing the Origin over, and
1864 * over again, when this function is called in a loop. Instead,
1865 * one can factor the computation of the Origin before the loop,
1866 * and offset the value returned by this function, on every iteration.
1868 * PARAMETER(S):
1869 * [I] infoPtr : valid pointer to the listview structure
1870 * [I] nItem : item number
1871 * [O] lpptOrig : item top, left corner
1873 * RETURN:
1874 * None.
1876 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1878 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1880 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1882 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1884 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1885 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1887 else if (uView == LVS_LIST)
1889 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1890 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1891 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1893 else /* LVS_REPORT */
1895 lpptPosition->x = 0;
1896 lpptPosition->y = nItem * infoPtr->nItemHeight;
1900 /***
1901 * DESCRIPTION: [INTERNAL]
1902 * Compute the rectangles of an item. This is to localize all
1903 * the computations in one place. If you are not interested in some
1904 * of these values, simply pass in a NULL -- the function is smart
1905 * enough to compute only what's necessary. The function computes
1906 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1907 * one, the BOX rectangle. This rectangle is very cheap to compute,
1908 * and is guaranteed to contain all the other rectangles. Computing
1909 * the ICON rect is also cheap, but all the others are potentially
1910 * expensive. This gives an easy and effective optimization when
1911 * searching (like point inclusion, or rectangle intersection):
1912 * first test against the BOX, and if TRUE, test against the desired
1913 * rectangle.
1914 * If the function does not have all the necessary information
1915 * to computed the requested rectangles, will crash with a
1916 * failed assertion. This is done so we catch all programming
1917 * errors, given that the function is called only from our code.
1919 * We have the following 'special' meanings for a few fields:
1920 * * If LVIS_FOCUSED is set, we assume the item has the focus
1921 * This is important in ICON mode, where it might get a larger
1922 * then usual rectangle
1924 * Please note that subitem support works only in REPORT mode.
1926 * PARAMETER(S):
1927 * [I] infoPtr : valid pointer to the listview structure
1928 * [I] lpLVItem : item to compute the measures for
1929 * [O] lprcBox : ptr to Box rectangle
1930 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1931 * [0] lprcSelectBox : ptr to select box rectangle
1932 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1933 * [O] lprcIcon : ptr to Icon rectangle
1934 * Same as LVM_GETITEMRECT with LVIR_ICON
1935 * [O] lprcStateIcon: ptr to State Icon rectangle
1936 * [O] lprcLabel : ptr to Label rectangle
1937 * Same as LVM_GETITEMRECT with LVIR_LABEL
1939 * RETURN:
1940 * None.
1942 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1943 LPRECT lprcBox, LPRECT lprcSelectBox,
1944 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1946 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1947 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1948 RECT Box, SelectBox, Icon, Label;
1949 COLUMN_INFO *lpColumnInfo = NULL;
1950 SIZE labelSize = { 0, 0 };
1952 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1954 /* Be smart and try to figure out the minimum we have to do */
1955 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1956 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1958 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1959 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1961 if (lprcSelectBox) doSelectBox = TRUE;
1962 if (lprcLabel) doLabel = TRUE;
1963 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1964 if (doSelectBox)
1966 doIcon = TRUE;
1967 doLabel = TRUE;
1970 /************************************************************/
1971 /* compute the box rectangle (it should be cheap to do) */
1972 /************************************************************/
1973 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1974 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1976 if (lpLVItem->iSubItem)
1978 Box = lpColumnInfo->rcHeader;
1980 else
1982 Box.left = 0;
1983 Box.right = infoPtr->nItemWidth;
1985 Box.top = 0;
1986 Box.bottom = infoPtr->nItemHeight;
1988 /******************************************************************/
1989 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1990 /******************************************************************/
1991 if (doIcon)
1993 LONG state_width = 0;
1995 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1996 state_width = infoPtr->iconStateSize.cx;
1998 if (uView == LVS_ICON)
2000 Icon.left = Box.left + state_width;
2001 if (infoPtr->himlNormal)
2002 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2003 Icon.top = Box.top + ICON_TOP_PADDING;
2004 Icon.right = Icon.left;
2005 Icon.bottom = Icon.top;
2006 if (infoPtr->himlNormal)
2008 Icon.right += infoPtr->iconSize.cx;
2009 Icon.bottom += infoPtr->iconSize.cy;
2012 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2014 Icon.left = Box.left + state_width;
2016 if (uView == LVS_REPORT)
2017 Icon.left += REPORT_MARGINX;
2019 Icon.top = Box.top;
2020 Icon.right = Icon.left;
2021 if (infoPtr->himlSmall &&
2022 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2023 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2024 Icon.right += infoPtr->iconSize.cx;
2025 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2027 if(lprcIcon) *lprcIcon = Icon;
2028 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2030 /* TODO: is this correct? */
2031 if (lprcStateIcon)
2033 lprcStateIcon->left = Icon.left - state_width;
2034 lprcStateIcon->right = Icon.left;
2035 lprcStateIcon->top = Icon.top;
2036 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2037 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2040 else Icon.right = 0;
2042 /************************************************************/
2043 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2044 /************************************************************/
2045 if (doLabel)
2047 /* calculate how far to the right can the label stretch */
2048 Label.right = Box.right;
2049 if (uView == LVS_REPORT)
2051 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2054 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2056 labelSize.cx = infoPtr->nItemWidth;
2057 labelSize.cy = infoPtr->nItemHeight;
2058 goto calc_label;
2061 /* we need the text in non owner draw mode */
2062 assert(lpLVItem->mask & LVIF_TEXT);
2063 if (is_textT(lpLVItem->pszText, TRUE))
2065 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2066 HDC hdc = GetDC(infoPtr->hwndSelf);
2067 HFONT hOldFont = SelectObject(hdc, hFont);
2068 UINT uFormat;
2069 RECT rcText;
2071 /* compute rough rectangle where the label will go */
2072 SetRectEmpty(&rcText);
2073 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2074 rcText.bottom = infoPtr->nItemHeight;
2075 if (uView == LVS_ICON)
2076 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2078 /* now figure out the flags */
2079 if (uView == LVS_ICON)
2080 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2081 else
2082 uFormat = LV_SL_DT_FLAGS;
2084 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2086 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2087 labelSize.cy = rcText.bottom - rcText.top;
2089 SelectObject(hdc, hOldFont);
2090 ReleaseDC(infoPtr->hwndSelf, hdc);
2093 calc_label:
2094 if (uView == LVS_ICON)
2096 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2097 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2098 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2099 Label.right = Label.left + labelSize.cx;
2100 Label.bottom = Label.top + infoPtr->nItemHeight;
2101 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2103 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2104 labelSize.cy /= infoPtr->ntmHeight;
2105 labelSize.cy = max(labelSize.cy, 1);
2106 labelSize.cy *= infoPtr->ntmHeight;
2108 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2110 else if (uView == LVS_REPORT)
2112 Label.left = Icon.right;
2113 Label.top = Box.top;
2114 Label.right = lpColumnInfo->rcHeader.right;
2115 Label.bottom = Label.top + infoPtr->nItemHeight;
2117 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2119 Label.left = Icon.right;
2120 Label.top = Box.top;
2121 Label.right = min(Label.left + labelSize.cx, Label.right);
2122 Label.bottom = Label.top + infoPtr->nItemHeight;
2125 if (lprcLabel) *lprcLabel = Label;
2126 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2129 /************************************************************/
2130 /* compute STATEICON bounding box */
2131 /************************************************************/
2132 if (doSelectBox)
2134 if (uView == LVS_REPORT)
2136 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2137 SelectBox.top = Box.top;
2138 SelectBox.bottom = Box.bottom;
2139 if (lpLVItem->iSubItem == 0)
2141 /* we need the indent in report mode */
2142 assert(lpLVItem->mask & LVIF_INDENT);
2143 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2145 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2147 else
2149 UnionRect(&SelectBox, &Icon, &Label);
2151 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2152 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2155 /* Fix the Box if necessary */
2156 if (lprcBox)
2158 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2159 else *lprcBox = Box;
2161 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2164 /***
2165 * DESCRIPTION: [INTERNAL]
2167 * PARAMETER(S):
2168 * [I] infoPtr : valid pointer to the listview structure
2169 * [I] nItem : item number
2170 * [O] lprcBox : ptr to Box rectangle
2172 * RETURN:
2173 * None.
2175 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2177 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2178 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2179 POINT Position, Origin;
2180 LVITEMW lvItem;
2182 LISTVIEW_GetOrigin(infoPtr, &Origin);
2183 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2185 /* Be smart and try to figure out the minimum we have to do */
2186 lvItem.mask = 0;
2187 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2188 lvItem.mask |= LVIF_TEXT;
2189 lvItem.iItem = nItem;
2190 lvItem.iSubItem = 0;
2191 lvItem.pszText = szDispText;
2192 lvItem.cchTextMax = DISP_TEXT_SIZE;
2193 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2194 if (uView == LVS_ICON)
2196 lvItem.mask |= LVIF_STATE;
2197 lvItem.stateMask = LVIS_FOCUSED;
2198 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2200 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2202 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2206 /***
2207 * DESCRIPTION:
2208 * Returns the current icon position, and advances it along the top.
2209 * The returned position is not offset by Origin.
2211 * PARAMETER(S):
2212 * [I] infoPtr : valid pointer to the listview structure
2213 * [O] lpPos : will get the current icon position
2215 * RETURN:
2216 * None
2218 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2220 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2222 *lpPos = infoPtr->currIconPos;
2224 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2225 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2227 infoPtr->currIconPos.x = 0;
2228 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2232 /***
2233 * DESCRIPTION:
2234 * Returns the current icon position, and advances it down the left edge.
2235 * The returned position is not offset by Origin.
2237 * PARAMETER(S):
2238 * [I] infoPtr : valid pointer to the listview structure
2239 * [O] lpPos : will get the current icon position
2241 * RETURN:
2242 * None
2244 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2246 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2248 *lpPos = infoPtr->currIconPos;
2250 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2251 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2253 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2254 infoPtr->currIconPos.y = 0;
2258 /***
2259 * DESCRIPTION:
2260 * Moves an icon to the specified position.
2261 * It takes care of invalidating the item, etc.
2263 * PARAMETER(S):
2264 * [I] infoPtr : valid pointer to the listview structure
2265 * [I] nItem : the item to move
2266 * [I] lpPos : the new icon position
2267 * [I] isNew : flags the item as being new
2269 * RETURN:
2270 * Success: TRUE
2271 * Failure: FALSE
2273 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2275 POINT old;
2277 if (!isNew)
2279 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2280 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2282 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2283 LISTVIEW_InvalidateItem(infoPtr, nItem);
2286 /* Allocating a POINTER for every item is too resource intensive,
2287 * so we'll keep the (x,y) in different arrays */
2288 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2289 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2291 LISTVIEW_InvalidateItem(infoPtr, nItem);
2293 return TRUE;
2296 /***
2297 * DESCRIPTION:
2298 * Arranges listview items in icon display mode.
2300 * PARAMETER(S):
2301 * [I] infoPtr : valid pointer to the listview structure
2302 * [I] nAlignCode : alignment code
2304 * RETURN:
2305 * SUCCESS : TRUE
2306 * FAILURE : FALSE
2308 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2310 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2311 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2312 POINT pos;
2313 INT i;
2315 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2317 TRACE("nAlignCode=%d\n", nAlignCode);
2319 if (nAlignCode == LVA_DEFAULT)
2321 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2322 else nAlignCode = LVA_ALIGNTOP;
2325 switch (nAlignCode)
2327 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2328 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2329 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2330 default: return FALSE;
2333 infoPtr->bAutoarrange = TRUE;
2334 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2335 for (i = 0; i < infoPtr->nItemCount; i++)
2337 next_pos(infoPtr, &pos);
2338 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2341 return TRUE;
2344 /***
2345 * DESCRIPTION:
2346 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2348 * PARAMETER(S):
2349 * [I] infoPtr : valid pointer to the listview structure
2350 * [O] lprcView : bounding rectangle
2352 * RETURN:
2353 * SUCCESS : TRUE
2354 * FAILURE : FALSE
2356 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2358 INT i, x, y;
2360 SetRectEmpty(lprcView);
2362 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2364 case LVS_ICON:
2365 case LVS_SMALLICON:
2366 for (i = 0; i < infoPtr->nItemCount; i++)
2368 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2369 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2370 lprcView->right = max(lprcView->right, x);
2371 lprcView->bottom = max(lprcView->bottom, y);
2373 if (infoPtr->nItemCount > 0)
2375 lprcView->right += infoPtr->nItemWidth;
2376 lprcView->bottom += infoPtr->nItemHeight;
2378 break;
2380 case LVS_LIST:
2381 y = LISTVIEW_GetCountPerColumn(infoPtr);
2382 x = infoPtr->nItemCount / y;
2383 if (infoPtr->nItemCount % y) x++;
2384 lprcView->right = x * infoPtr->nItemWidth;
2385 lprcView->bottom = y * infoPtr->nItemHeight;
2386 break;
2388 case LVS_REPORT:
2389 lprcView->right = infoPtr->nItemWidth;
2390 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2391 break;
2395 /***
2396 * DESCRIPTION:
2397 * Retrieves the bounding rectangle of all the items.
2399 * PARAMETER(S):
2400 * [I] infoPtr : valid pointer to the listview structure
2401 * [O] lprcView : bounding rectangle
2403 * RETURN:
2404 * SUCCESS : TRUE
2405 * FAILURE : FALSE
2407 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2409 POINT ptOrigin;
2411 TRACE("(lprcView=%p)\n", lprcView);
2413 if (!lprcView) return FALSE;
2415 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2416 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2417 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2419 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2421 return TRUE;
2424 /***
2425 * DESCRIPTION:
2426 * Retrieves the subitem pointer associated with the subitem index.
2428 * PARAMETER(S):
2429 * [I] hdpaSubItems : DPA handle for a specific item
2430 * [I] nSubItem : index of subitem
2432 * RETURN:
2433 * SUCCESS : subitem pointer
2434 * FAILURE : NULL
2436 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2438 SUBITEM_INFO *lpSubItem;
2439 INT i;
2441 /* we should binary search here if need be */
2442 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2444 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2445 if (lpSubItem->iSubItem == nSubItem)
2446 return lpSubItem;
2449 return NULL;
2453 /***
2454 * DESCRIPTION:
2455 * Calculates the desired item width.
2457 * PARAMETER(S):
2458 * [I] infoPtr : valid pointer to the listview structure
2460 * RETURN:
2461 * The desired item width.
2463 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2465 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2466 INT nItemWidth = 0;
2468 TRACE("uView=%d\n", uView);
2470 if (uView == LVS_ICON)
2471 nItemWidth = infoPtr->iconSpacing.cx;
2472 else if (uView == LVS_REPORT)
2474 RECT rcHeader;
2476 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2478 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2479 nItemWidth = rcHeader.right;
2482 else /* LVS_SMALLICON, or LVS_LIST */
2484 INT i;
2486 for (i = 0; i < infoPtr->nItemCount; i++)
2487 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2489 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2490 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2492 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2495 return max(nItemWidth, 1);
2498 /***
2499 * DESCRIPTION:
2500 * Calculates the desired item height.
2502 * PARAMETER(S):
2503 * [I] infoPtr : valid pointer to the listview structure
2505 * RETURN:
2506 * The desired item height.
2508 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2510 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2511 INT nItemHeight;
2513 TRACE("uView=%d\n", uView);
2515 if (uView == LVS_ICON)
2516 nItemHeight = infoPtr->iconSpacing.cy;
2517 else
2519 nItemHeight = infoPtr->ntmHeight;
2520 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2521 nItemHeight++;
2522 if (infoPtr->himlState)
2523 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2524 if (infoPtr->himlSmall)
2525 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2526 if (infoPtr->himlState || infoPtr->himlSmall)
2527 nItemHeight += HEIGHT_PADDING;
2528 if (infoPtr->nMeasureItemHeight > 0)
2529 nItemHeight = infoPtr->nMeasureItemHeight;
2532 return max(nItemHeight, 1);
2535 /***
2536 * DESCRIPTION:
2537 * Updates the width, and height of an item.
2539 * PARAMETER(S):
2540 * [I] infoPtr : valid pointer to the listview structure
2542 * RETURN:
2543 * None.
2545 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2547 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2548 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2552 /***
2553 * DESCRIPTION:
2554 * Retrieves and saves important text metrics info for the current
2555 * Listview font.
2557 * PARAMETER(S):
2558 * [I] infoPtr : valid pointer to the listview structure
2561 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2563 HDC hdc = GetDC(infoPtr->hwndSelf);
2564 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2565 HFONT hOldFont = SelectObject(hdc, hFont);
2566 TEXTMETRICW tm;
2567 SIZE sz;
2569 if (GetTextMetricsW(hdc, &tm))
2571 infoPtr->ntmHeight = tm.tmHeight;
2572 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2575 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2576 infoPtr->nEllipsisWidth = sz.cx;
2578 SelectObject(hdc, hOldFont);
2579 ReleaseDC(infoPtr->hwndSelf, hdc);
2581 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2584 /***
2585 * DESCRIPTION:
2586 * A compare function for ranges
2588 * PARAMETER(S)
2589 * [I] range1 : pointer to range 1;
2590 * [I] range2 : pointer to range 2;
2591 * [I] flags : flags
2593 * RETURNS:
2594 * > 0 : if range 1 > range 2
2595 * < 0 : if range 2 > range 1
2596 * = 0 : if range intersects range 2
2598 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2600 INT cmp;
2602 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2603 cmp = -1;
2604 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2605 cmp = 1;
2606 else
2607 cmp = 0;
2609 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2611 return cmp;
2614 #if DEBUG_RANGES
2615 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2616 #else
2617 #define ranges_check(ranges, desc) do { } while(0)
2618 #endif
2620 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2622 INT i;
2623 RANGE *prev, *curr;
2625 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2626 assert (ranges);
2627 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2628 ranges_dump(ranges);
2629 prev = DPA_GetPtr(ranges->hdpa, 0);
2630 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2631 assert (prev->lower >= 0 && prev->lower < prev->upper);
2632 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2634 curr = DPA_GetPtr(ranges->hdpa, i);
2635 assert (prev->upper <= curr->lower);
2636 assert (curr->lower < curr->upper);
2637 prev = curr;
2639 TRACE("--- Done checking---\n");
2642 static RANGES ranges_create(int count)
2644 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2645 if (!ranges) return NULL;
2646 ranges->hdpa = DPA_Create(count);
2647 if (ranges->hdpa) return ranges;
2648 Free(ranges);
2649 return NULL;
2652 static void ranges_clear(RANGES ranges)
2654 INT i;
2656 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2657 Free(DPA_GetPtr(ranges->hdpa, i));
2658 DPA_DeleteAllPtrs(ranges->hdpa);
2662 static void ranges_destroy(RANGES ranges)
2664 if (!ranges) return;
2665 ranges_clear(ranges);
2666 DPA_Destroy(ranges->hdpa);
2667 Free(ranges);
2670 static RANGES ranges_clone(RANGES ranges)
2672 RANGES clone;
2673 INT i;
2675 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2677 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2679 RANGE *newrng = Alloc(sizeof(RANGE));
2680 if (!newrng) goto fail;
2681 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2682 DPA_SetPtr(clone->hdpa, i, newrng);
2684 return clone;
2686 fail:
2687 TRACE ("clone failed\n");
2688 ranges_destroy(clone);
2689 return NULL;
2692 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2694 INT i;
2696 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2697 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2699 return ranges;
2702 static void ranges_dump(RANGES ranges)
2704 INT i;
2706 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2707 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2710 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2712 RANGE srchrng = { nItem, nItem + 1 };
2714 TRACE("(nItem=%d)\n", nItem);
2715 ranges_check(ranges, "before contain");
2716 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2719 static INT ranges_itemcount(RANGES ranges)
2721 INT i, count = 0;
2723 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2725 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2726 count += sel->upper - sel->lower;
2729 return count;
2732 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2734 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2735 INT index;
2737 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2738 if (index == -1) return TRUE;
2740 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2742 chkrng = DPA_GetPtr(ranges->hdpa, index);
2743 if (chkrng->lower >= nItem)
2744 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2745 if (chkrng->upper > nItem)
2746 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2748 return TRUE;
2751 static BOOL ranges_add(RANGES ranges, RANGE range)
2753 RANGE srchrgn;
2754 INT index;
2756 TRACE("(%s)\n", debugrange(&range));
2757 ranges_check(ranges, "before add");
2759 /* try find overlapping regions first */
2760 srchrgn.lower = range.lower - 1;
2761 srchrgn.upper = range.upper + 1;
2762 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2764 if (index == -1)
2766 RANGE *newrgn;
2768 TRACE("Adding new range\n");
2770 /* create the brand new range to insert */
2771 newrgn = Alloc(sizeof(RANGE));
2772 if(!newrgn) goto fail;
2773 *newrgn = range;
2775 /* figure out where to insert it */
2776 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2777 TRACE("index=%d\n", index);
2778 if (index == -1) index = 0;
2780 /* and get it over with */
2781 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2783 Free(newrgn);
2784 goto fail;
2787 else
2789 RANGE *chkrgn, *mrgrgn;
2790 INT fromindex, mergeindex;
2792 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2793 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2795 chkrgn->lower = min(range.lower, chkrgn->lower);
2796 chkrgn->upper = max(range.upper, chkrgn->upper);
2798 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2800 /* merge now common ranges */
2801 fromindex = 0;
2802 srchrgn.lower = chkrgn->lower - 1;
2803 srchrgn.upper = chkrgn->upper + 1;
2807 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2808 if (mergeindex == -1) break;
2809 if (mergeindex == index)
2811 fromindex = index + 1;
2812 continue;
2815 TRACE("Merge with index %i\n", mergeindex);
2817 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2818 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2819 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2820 Free(mrgrgn);
2821 DPA_DeletePtr(ranges->hdpa, mergeindex);
2822 if (mergeindex < index) index --;
2823 } while(1);
2826 ranges_check(ranges, "after add");
2827 return TRUE;
2829 fail:
2830 ranges_check(ranges, "failed add");
2831 return FALSE;
2834 static BOOL ranges_del(RANGES ranges, RANGE range)
2836 RANGE *chkrgn;
2837 INT index;
2839 TRACE("(%s)\n", debugrange(&range));
2840 ranges_check(ranges, "before del");
2842 /* we don't use DPAS_SORTED here, since we need *
2843 * to find the first overlapping range */
2844 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2845 while(index != -1)
2847 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2849 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2851 /* case 1: Same range */
2852 if ( (chkrgn->upper == range.upper) &&
2853 (chkrgn->lower == range.lower) )
2855 DPA_DeletePtr(ranges->hdpa, index);
2856 break;
2858 /* case 2: engulf */
2859 else if ( (chkrgn->upper <= range.upper) &&
2860 (chkrgn->lower >= range.lower) )
2862 DPA_DeletePtr(ranges->hdpa, index);
2864 /* case 3: overlap upper */
2865 else if ( (chkrgn->upper <= range.upper) &&
2866 (chkrgn->lower < range.lower) )
2868 chkrgn->upper = range.lower;
2870 /* case 4: overlap lower */
2871 else if ( (chkrgn->upper > range.upper) &&
2872 (chkrgn->lower >= range.lower) )
2874 chkrgn->lower = range.upper;
2875 break;
2877 /* case 5: fully internal */
2878 else
2880 RANGE tmprgn = *chkrgn, *newrgn;
2882 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2883 newrgn->lower = chkrgn->lower;
2884 newrgn->upper = range.lower;
2885 chkrgn->lower = range.upper;
2886 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2888 Free(newrgn);
2889 goto fail;
2891 chkrgn = &tmprgn;
2892 break;
2895 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2898 ranges_check(ranges, "after del");
2899 return TRUE;
2901 fail:
2902 ranges_check(ranges, "failed del");
2903 return FALSE;
2906 /***
2907 * DESCRIPTION:
2908 * Removes all selection ranges
2910 * Parameters(s):
2911 * [I] infoPtr : valid pointer to the listview structure
2912 * [I] toSkip : item range to skip removing the selection
2914 * RETURNS:
2915 * SUCCESS : TRUE
2916 * FAILURE : TRUE
2918 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2920 LVITEMW lvItem;
2921 ITERATOR i;
2922 RANGES clone;
2924 TRACE("()\n");
2926 lvItem.state = 0;
2927 lvItem.stateMask = LVIS_SELECTED;
2929 /* need to clone the DPA because callbacks can change it */
2930 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2931 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2932 while(iterator_next(&i))
2933 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2934 /* note that the iterator destructor will free the cloned range */
2935 iterator_destroy(&i);
2937 return TRUE;
2940 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2942 RANGES toSkip;
2944 if (!(toSkip = ranges_create(1))) return FALSE;
2945 if (nItem != -1) ranges_additem(toSkip, nItem);
2946 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2947 ranges_destroy(toSkip);
2948 return TRUE;
2951 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2953 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2956 /***
2957 * DESCRIPTION:
2958 * Retrieves the number of items that are marked as selected.
2960 * PARAMETER(S):
2961 * [I] infoPtr : valid pointer to the listview structure
2963 * RETURN:
2964 * Number of items selected.
2966 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2968 INT nSelectedCount = 0;
2970 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2972 INT i;
2973 for (i = 0; i < infoPtr->nItemCount; i++)
2975 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2976 nSelectedCount++;
2979 else
2980 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2982 TRACE("nSelectedCount=%d\n", nSelectedCount);
2983 return nSelectedCount;
2986 /***
2987 * DESCRIPTION:
2988 * Manages the item focus.
2990 * PARAMETER(S):
2991 * [I] infoPtr : valid pointer to the listview structure
2992 * [I] nItem : item index
2994 * RETURN:
2995 * TRUE : focused item changed
2996 * FALSE : focused item has NOT changed
2998 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3000 INT oldFocus = infoPtr->nFocusedItem;
3001 LVITEMW lvItem;
3003 if (nItem == infoPtr->nFocusedItem) return FALSE;
3005 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3006 lvItem.stateMask = LVIS_FOCUSED;
3007 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3009 return oldFocus != infoPtr->nFocusedItem;
3012 /* Helper function for LISTVIEW_ShiftIndices *only* */
3013 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3015 if (nShiftItem < nItem) return nShiftItem;
3017 if (nShiftItem > nItem) return nShiftItem + direction;
3019 if (direction > 0) return nShiftItem + direction;
3021 return min(nShiftItem, infoPtr->nItemCount - 1);
3025 * DESCRIPTION:
3026 * Updates the various indices after an item has been inserted or deleted.
3028 * PARAMETER(S):
3029 * [I] infoPtr : valid pointer to the listview structure
3030 * [I] nItem : item index
3031 * [I] direction : Direction of shift, +1 or -1.
3033 * RETURN:
3034 * None
3036 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3038 INT nNewFocus;
3039 BOOL bOldChange;
3041 /* temporarily disable change notification while shifting items */
3042 bOldChange = infoPtr->bDoChangeNotify;
3043 infoPtr->bDoChangeNotify = FALSE;
3045 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3047 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3049 assert(abs(direction) == 1);
3051 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3053 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3054 if (nNewFocus != infoPtr->nFocusedItem)
3055 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3057 /* But we are not supposed to modify nHotItem! */
3059 infoPtr->bDoChangeNotify = bOldChange;
3064 * DESCRIPTION:
3065 * Adds a block of selections.
3067 * PARAMETER(S):
3068 * [I] infoPtr : valid pointer to the listview structure
3069 * [I] nItem : item index
3071 * RETURN:
3072 * Whether the window is still valid.
3074 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3076 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3077 INT nLast = max(infoPtr->nSelectionMark, nItem);
3078 HWND hwndSelf = infoPtr->hwndSelf;
3079 NMLVODSTATECHANGE nmlv;
3080 LVITEMW item;
3081 BOOL bOldChange;
3082 INT i;
3084 /* Temporarily disable change notification
3085 * If the control is LVS_OWNERDATA, we need to send
3086 * only one LVN_ODSTATECHANGED notification.
3087 * See MSDN documentation for LVN_ITEMCHANGED.
3089 bOldChange = infoPtr->bDoChangeNotify;
3090 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3092 if (nFirst == -1) nFirst = nItem;
3094 item.state = LVIS_SELECTED;
3095 item.stateMask = LVIS_SELECTED;
3097 for (i = nFirst; i <= nLast; i++)
3098 LISTVIEW_SetItemState(infoPtr,i,&item);
3100 ZeroMemory(&nmlv, sizeof(nmlv));
3101 nmlv.iFrom = nFirst;
3102 nmlv.iTo = nLast;
3103 nmlv.uNewState = 0;
3104 nmlv.uOldState = item.state;
3106 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3107 if (!IsWindow(hwndSelf))
3108 return FALSE;
3109 infoPtr->bDoChangeNotify = bOldChange;
3110 return TRUE;
3114 /***
3115 * DESCRIPTION:
3116 * Sets a single group selection.
3118 * PARAMETER(S):
3119 * [I] infoPtr : valid pointer to the listview structure
3120 * [I] nItem : item index
3122 * RETURN:
3123 * None
3125 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3127 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3128 RANGES selection;
3129 LVITEMW item;
3130 ITERATOR i;
3131 BOOL bOldChange;
3133 if (!(selection = ranges_create(100))) return;
3135 item.state = LVIS_SELECTED;
3136 item.stateMask = LVIS_SELECTED;
3138 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3140 if (infoPtr->nSelectionMark == -1)
3142 infoPtr->nSelectionMark = nItem;
3143 ranges_additem(selection, nItem);
3145 else
3147 RANGE sel;
3149 sel.lower = min(infoPtr->nSelectionMark, nItem);
3150 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3151 ranges_add(selection, sel);
3154 else
3156 RECT rcItem, rcSel, rcSelMark;
3157 POINT ptItem;
3159 rcItem.left = LVIR_BOUNDS;
3160 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3161 rcSelMark.left = LVIR_BOUNDS;
3162 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3163 UnionRect(&rcSel, &rcItem, &rcSelMark);
3164 iterator_frameditems(&i, infoPtr, &rcSel);
3165 while(iterator_next(&i))
3167 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3168 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3170 iterator_destroy(&i);
3173 bOldChange = infoPtr->bDoChangeNotify;
3174 infoPtr->bDoChangeNotify = FALSE;
3176 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3179 iterator_rangesitems(&i, selection);
3180 while(iterator_next(&i))
3181 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3182 /* this will also destroy the selection */
3183 iterator_destroy(&i);
3185 infoPtr->bDoChangeNotify = bOldChange;
3187 LISTVIEW_SetItemFocus(infoPtr, nItem);
3190 /***
3191 * DESCRIPTION:
3192 * Sets a single selection.
3194 * PARAMETER(S):
3195 * [I] infoPtr : valid pointer to the listview structure
3196 * [I] nItem : item index
3198 * RETURN:
3199 * None
3201 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3203 LVITEMW lvItem;
3205 TRACE("nItem=%d\n", nItem);
3207 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3209 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3210 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3211 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3213 infoPtr->nSelectionMark = nItem;
3216 /***
3217 * DESCRIPTION:
3218 * Set selection(s) with keyboard.
3220 * PARAMETER(S):
3221 * [I] infoPtr : valid pointer to the listview structure
3222 * [I] nItem : item index
3224 * RETURN:
3225 * SUCCESS : TRUE (needs to be repainted)
3226 * FAILURE : FALSE (nothing has changed)
3228 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3230 /* FIXME: pass in the state */
3231 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3232 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3233 BOOL bResult = FALSE;
3235 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3236 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3238 if (infoPtr->dwStyle & LVS_SINGLESEL)
3240 bResult = TRUE;
3241 LISTVIEW_SetSelection(infoPtr, nItem);
3243 else
3245 if (wShift)
3247 bResult = TRUE;
3248 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3250 else if (wCtrl)
3252 LVITEMW lvItem;
3253 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3254 lvItem.stateMask = LVIS_SELECTED;
3255 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3257 if (lvItem.state & LVIS_SELECTED)
3258 infoPtr->nSelectionMark = nItem;
3260 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3262 else
3264 bResult = TRUE;
3265 LISTVIEW_SetSelection(infoPtr, nItem);
3268 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3271 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3272 return bResult;
3275 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3277 LVHITTESTINFO lvHitTestInfo;
3279 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3280 lvHitTestInfo.pt.x = pt.x;
3281 lvHitTestInfo.pt.y = pt.y;
3283 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3285 lpLVItem->mask = LVIF_PARAM;
3286 lpLVItem->iItem = lvHitTestInfo.iItem;
3287 lpLVItem->iSubItem = 0;
3289 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3292 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3294 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3295 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3296 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3299 /***
3300 * DESCRIPTION:
3301 * Called when the mouse is being actively tracked and has hovered for a specified
3302 * amount of time
3304 * PARAMETER(S):
3305 * [I] infoPtr : valid pointer to the listview structure
3306 * [I] fwKeys : key indicator
3307 * [I] x,y : mouse position
3309 * RETURN:
3310 * 0 if the message was processed, non-zero if there was an error
3312 * INFO:
3313 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3314 * over the item for a certain period of time.
3317 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3319 if (LISTVIEW_isHotTracking(infoPtr))
3321 LVITEMW item;
3322 POINT pt;
3324 pt.x = x;
3325 pt.y = y;
3327 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3328 LISTVIEW_SetSelection(infoPtr, item.iItem);
3331 return 0;
3334 /***
3335 * DESCRIPTION:
3336 * Called whenever WM_MOUSEMOVE is received.
3338 * PARAMETER(S):
3339 * [I] infoPtr : valid pointer to the listview structure
3340 * [I] fwKeys : key indicator
3341 * [I] x,y : mouse position
3343 * RETURN:
3344 * 0 if the message is processed, non-zero if there was an error
3346 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3348 TRACKMOUSEEVENT trackinfo;
3350 if (!(fwKeys & MK_LBUTTON))
3351 infoPtr->bLButtonDown = FALSE;
3353 if (infoPtr->bLButtonDown)
3355 POINT tmp;
3356 RECT rect;
3357 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3358 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3360 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3361 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3362 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3363 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3365 tmp.x = x;
3366 tmp.y = y;
3368 if (!PtInRect(&rect, tmp))
3370 LVHITTESTINFO lvHitTestInfo;
3371 NMLISTVIEW nmlv;
3373 lvHitTestInfo.pt = infoPtr->ptClickPos;
3374 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3376 ZeroMemory(&nmlv, sizeof(nmlv));
3377 nmlv.iItem = lvHitTestInfo.iItem;
3378 nmlv.ptAction = infoPtr->ptClickPos;
3380 if (!infoPtr->bDragging)
3382 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3383 infoPtr->bDragging = TRUE;
3386 return 0;
3389 else
3390 infoPtr->bLButtonDown = FALSE;
3392 /* see if we are supposed to be tracking mouse hovering */
3393 if (LISTVIEW_isHotTracking(infoPtr)) {
3394 /* fill in the trackinfo struct */
3395 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3396 trackinfo.dwFlags = TME_QUERY;
3397 trackinfo.hwndTrack = infoPtr->hwndSelf;
3398 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3400 /* see if we are already tracking this hwnd */
3401 _TrackMouseEvent(&trackinfo);
3403 if(!(trackinfo.dwFlags & TME_HOVER)) {
3404 trackinfo.dwFlags = TME_HOVER;
3406 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3407 _TrackMouseEvent(&trackinfo);
3411 return 0;
3415 /***
3416 * Tests whether the item is assignable to a list with style lStyle
3418 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3420 if ( (lpLVItem->mask & LVIF_TEXT) &&
3421 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3422 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3424 return TRUE;
3428 /***
3429 * DESCRIPTION:
3430 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3432 * PARAMETER(S):
3433 * [I] infoPtr : valid pointer to the listview structure
3434 * [I] lpLVItem : valid pointer to new item attributes
3435 * [I] isNew : the item being set is being inserted
3436 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3437 * [O] bChanged : will be set to TRUE if the item really changed
3439 * RETURN:
3440 * SUCCESS : TRUE
3441 * FAILURE : FALSE
3443 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3445 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3446 ITEM_INFO *lpItem;
3447 NMLISTVIEW nmlv;
3448 UINT uChanged = 0;
3449 LVITEMW item;
3451 TRACE("()\n");
3453 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3455 if (lpLVItem->mask == 0) return TRUE;
3457 if (infoPtr->dwStyle & LVS_OWNERDATA)
3459 /* a virtual listview only stores selection and focus */
3460 if (lpLVItem->mask & ~LVIF_STATE)
3461 return FALSE;
3462 lpItem = NULL;
3464 else
3466 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3467 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3468 assert (lpItem);
3471 /* we need to get the lParam and state of the item */
3472 item.iItem = lpLVItem->iItem;
3473 item.iSubItem = lpLVItem->iSubItem;
3474 item.mask = LVIF_STATE | LVIF_PARAM;
3475 item.stateMask = ~0;
3476 item.state = 0;
3477 item.lParam = 0;
3478 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3480 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3481 /* determine what fields will change */
3482 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3483 uChanged |= LVIF_STATE;
3485 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3486 uChanged |= LVIF_IMAGE;
3488 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3489 uChanged |= LVIF_PARAM;
3491 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3492 uChanged |= LVIF_INDENT;
3494 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3495 uChanged |= LVIF_TEXT;
3497 TRACE("uChanged=0x%x\n", uChanged);
3498 if (!uChanged) return TRUE;
3499 *bChanged = TRUE;
3501 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3502 nmlv.iItem = lpLVItem->iItem;
3503 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3504 nmlv.uOldState = item.state;
3505 nmlv.uChanged = uChanged;
3506 nmlv.lParam = item.lParam;
3508 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3509 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3510 /* are enabled */
3511 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3513 HWND hwndSelf = infoPtr->hwndSelf;
3515 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3516 return FALSE;
3517 if (!IsWindow(hwndSelf))
3518 return FALSE;
3521 /* copy information */
3522 if (lpLVItem->mask & LVIF_TEXT)
3523 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3525 if (lpLVItem->mask & LVIF_IMAGE)
3526 lpItem->hdr.iImage = lpLVItem->iImage;
3528 if (lpLVItem->mask & LVIF_PARAM)
3529 lpItem->lParam = lpLVItem->lParam;
3531 if (lpLVItem->mask & LVIF_INDENT)
3532 lpItem->iIndent = lpLVItem->iIndent;
3534 if (uChanged & LVIF_STATE)
3536 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3538 lpItem->state &= ~lpLVItem->stateMask;
3539 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3541 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3543 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3544 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3546 else if (lpLVItem->stateMask & LVIS_SELECTED)
3547 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3549 /* if we are asked to change focus, and we manage it, do it */
3550 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3552 if (lpLVItem->state & LVIS_FOCUSED)
3554 LISTVIEW_SetItemFocus(infoPtr, -1);
3555 infoPtr->nFocusedItem = lpLVItem->iItem;
3556 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3558 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3559 infoPtr->nFocusedItem = -1;
3563 /* if we're inserting the item, we're done */
3564 if (isNew) return TRUE;
3566 /* send LVN_ITEMCHANGED notification */
3567 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3568 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3570 return TRUE;
3573 /***
3574 * DESCRIPTION:
3575 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3577 * PARAMETER(S):
3578 * [I] infoPtr : valid pointer to the listview structure
3579 * [I] lpLVItem : valid pointer to new subitem attributes
3580 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3581 * [O] bChanged : will be set to TRUE if the item really changed
3583 * RETURN:
3584 * SUCCESS : TRUE
3585 * FAILURE : FALSE
3587 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3589 HDPA hdpaSubItems;
3590 SUBITEM_INFO *lpSubItem;
3592 /* we do not support subitems for virtual listviews */
3593 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3595 /* set subitem only if column is present */
3596 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3598 /* First do some sanity checks */
3599 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3600 particularly useful. We currently do not actually do anything with
3601 the flag on subitems.
3603 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3604 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3606 /* get the subitem structure, and create it if not there */
3607 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3608 assert (hdpaSubItems);
3610 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3611 if (!lpSubItem)
3613 SUBITEM_INFO *tmpSubItem;
3614 INT i;
3616 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3617 if (!lpSubItem) return FALSE;
3618 /* we could binary search here, if need be...*/
3619 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3621 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3622 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3624 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3626 Free(lpSubItem);
3627 return FALSE;
3629 lpSubItem->iSubItem = lpLVItem->iSubItem;
3630 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3631 *bChanged = TRUE;
3634 if (lpLVItem->mask & LVIF_IMAGE)
3635 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3637 lpSubItem->hdr.iImage = lpLVItem->iImage;
3638 *bChanged = TRUE;
3641 if (lpLVItem->mask & LVIF_TEXT)
3642 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3644 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3645 *bChanged = TRUE;
3648 return TRUE;
3651 /***
3652 * DESCRIPTION:
3653 * Sets item attributes.
3655 * PARAMETER(S):
3656 * [I] infoPtr : valid pointer to the listview structure
3657 * [I] lpLVItem : new item attributes
3658 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3660 * RETURN:
3661 * SUCCESS : TRUE
3662 * FAILURE : FALSE
3664 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3666 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3667 HWND hwndSelf = infoPtr->hwndSelf;
3668 LPWSTR pszText = NULL;
3669 BOOL bResult, bChanged = FALSE;
3671 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3673 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3674 return FALSE;
3676 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3677 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3679 pszText = lpLVItem->pszText;
3680 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3683 /* actually set the fields */
3684 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3686 if (lpLVItem->iSubItem)
3687 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3688 else
3689 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3690 if (!IsWindow(hwndSelf))
3691 return FALSE;
3693 /* redraw item, if necessary */
3694 if (bChanged && !infoPtr->bIsDrawing)
3696 /* this little optimization eliminates some nasty flicker */
3697 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3698 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3699 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3700 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3701 else
3702 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3704 /* restore text */
3705 if (pszText)
3707 textfreeT(lpLVItem->pszText, isW);
3708 lpLVItem->pszText = pszText;
3711 return bResult;
3714 /***
3715 * DESCRIPTION:
3716 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3718 * PARAMETER(S):
3719 * [I] infoPtr : valid pointer to the listview structure
3721 * RETURN:
3722 * item index
3724 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3726 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3727 INT nItem = 0;
3728 SCROLLINFO scrollInfo;
3730 scrollInfo.cbSize = sizeof(SCROLLINFO);
3731 scrollInfo.fMask = SIF_POS;
3733 if (uView == LVS_LIST)
3735 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3736 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3738 else if (uView == LVS_REPORT)
3740 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3741 nItem = scrollInfo.nPos;
3743 else
3745 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3746 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3749 TRACE("nItem=%d\n", nItem);
3751 return nItem;
3755 /***
3756 * DESCRIPTION:
3757 * Erases the background of the given rectangle
3759 * PARAMETER(S):
3760 * [I] infoPtr : valid pointer to the listview structure
3761 * [I] hdc : device context handle
3762 * [I] lprcBox : clipping rectangle
3764 * RETURN:
3765 * Success: TRUE
3766 * Failure: FALSE
3768 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3770 if (!infoPtr->hBkBrush) return FALSE;
3772 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3774 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3777 /***
3778 * DESCRIPTION:
3779 * Draws an item.
3781 * PARAMETER(S):
3782 * [I] infoPtr : valid pointer to the listview structure
3783 * [I] hdc : device context handle
3784 * [I] nItem : item index
3785 * [I] nSubItem : subitem index
3786 * [I] pos : item position in client coordinates
3787 * [I] cdmode : custom draw mode
3789 * RETURN:
3790 * Success: TRUE
3791 * Failure: FALSE
3793 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3795 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3796 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3797 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3798 DWORD cdsubitemmode = CDRF_DODEFAULT;
3799 LPRECT lprcFocus;
3800 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3801 NMLVCUSTOMDRAW nmlvcd;
3802 HIMAGELIST himl;
3803 LVITEMW lvItem;
3804 HFONT hOldFont;
3806 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3808 /* get information needed for drawing the item */
3809 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3810 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3811 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3812 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3813 lvItem.iItem = nItem;
3814 lvItem.iSubItem = nSubItem;
3815 lvItem.state = 0;
3816 lvItem.lParam = 0;
3817 lvItem.cchTextMax = DISP_TEXT_SIZE;
3818 lvItem.pszText = szDispText;
3819 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3820 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3821 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3822 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3823 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3825 /* now check if we need to update the focus rectangle */
3826 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3828 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3829 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3830 OffsetRect(&rcBox, pos.x, pos.y);
3831 OffsetRect(&rcSelect, pos.x, pos.y);
3832 OffsetRect(&rcIcon, pos.x, pos.y);
3833 OffsetRect(&rcStateIcon, pos.x, pos.y);
3834 OffsetRect(&rcLabel, pos.x, pos.y);
3835 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3836 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3837 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3839 /* fill in the custom draw structure */
3840 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3842 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3843 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3844 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3845 if (cdmode & CDRF_NOTIFYITEMDRAW)
3846 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3847 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3848 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3849 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3850 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3852 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3853 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3855 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3856 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3857 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3858 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3860 /* in full row select, subitems, will just use main item's colors */
3861 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3862 nmlvcd.clrTextBk = CLR_NONE;
3864 /* state icons */
3865 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3867 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3868 if (uStateImage)
3870 TRACE("uStateImage=%d\n", uStateImage);
3871 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3872 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3876 /* small icons */
3877 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3878 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3880 TRACE("iImage=%d\n", lvItem.iImage);
3881 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3882 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3883 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3886 /* Don't bother painting item being edited */
3887 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3889 /* FIXME: temporary hack */
3890 rcSelect.left = rcLabel.left;
3892 /* draw the selection background, if we're drawing the main item */
3893 if (nSubItem == 0)
3895 /* in icon mode, the label rect is really what we want to draw the
3896 * background for */
3897 if (uView == LVS_ICON)
3898 rcSelect = rcLabel;
3900 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3901 rcSelect.right = rcBox.right;
3903 if (nmlvcd.clrTextBk != CLR_NONE)
3904 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3905 if(lprcFocus) *lprcFocus = rcSelect;
3908 /* figure out the text drawing flags */
3909 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3910 if (uView == LVS_ICON)
3911 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3912 else if (nSubItem)
3914 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3916 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3917 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3918 default: uFormat |= DT_LEFT;
3921 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3923 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3924 else rcLabel.left += LABEL_HOR_PADDING;
3926 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3928 /* for GRIDLINES reduce the bottom so the text formats correctly */
3929 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3930 rcLabel.bottom--;
3932 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3934 postpaint:
3935 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3936 notify_postpaint(infoPtr, &nmlvcd);
3937 if (cdsubitemmode & CDRF_NEWFONT)
3938 SelectObject(hdc, hOldFont);
3939 return TRUE;
3942 /***
3943 * DESCRIPTION:
3944 * Draws listview items when in owner draw mode.
3946 * PARAMETER(S):
3947 * [I] infoPtr : valid pointer to the listview structure
3948 * [I] hdc : device context handle
3950 * RETURN:
3951 * None
3953 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3955 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3956 DWORD cditemmode = CDRF_DODEFAULT;
3957 NMLVCUSTOMDRAW nmlvcd;
3958 POINT Origin, Position;
3959 DRAWITEMSTRUCT dis;
3960 LVITEMW item;
3962 TRACE("()\n");
3964 ZeroMemory(&dis, sizeof(dis));
3966 /* Get scroll info once before loop */
3967 LISTVIEW_GetOrigin(infoPtr, &Origin);
3969 /* iterate through the invalidated rows */
3970 while(iterator_next(i))
3972 item.iItem = i->nItem;
3973 item.iSubItem = 0;
3974 item.mask = LVIF_PARAM | LVIF_STATE;
3975 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3976 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3978 dis.CtlType = ODT_LISTVIEW;
3979 dis.CtlID = uID;
3980 dis.itemID = item.iItem;
3981 dis.itemAction = ODA_DRAWENTIRE;
3982 dis.itemState = 0;
3983 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3984 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3985 dis.hwndItem = infoPtr->hwndSelf;
3986 dis.hDC = hdc;
3987 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3988 dis.rcItem.left = Position.x + Origin.x;
3989 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3990 dis.rcItem.top = Position.y + Origin.y;
3991 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3992 dis.itemData = item.lParam;
3994 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3997 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3998 * structure for the rest. of the paint cycle
4000 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4001 if (cdmode & CDRF_NOTIFYITEMDRAW)
4002 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4004 if (!(cditemmode & CDRF_SKIPDEFAULT))
4006 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4007 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4010 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4011 notify_postpaint(infoPtr, &nmlvcd);
4015 /***
4016 * DESCRIPTION:
4017 * Draws listview items when in report display mode.
4019 * PARAMETER(S):
4020 * [I] infoPtr : valid pointer to the listview structure
4021 * [I] hdc : device context handle
4022 * [I] cdmode : custom draw mode
4024 * RETURN:
4025 * None
4027 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4029 INT rgntype;
4030 RECT rcClip, rcItem;
4031 POINT Origin, Position;
4032 RANGE colRange;
4033 ITERATOR j;
4035 TRACE("()\n");
4037 /* figure out what to draw */
4038 rgntype = GetClipBox(hdc, &rcClip);
4039 if (rgntype == NULLREGION) return;
4041 /* Get scroll info once before loop */
4042 LISTVIEW_GetOrigin(infoPtr, &Origin);
4044 /* narrow down the columns we need to paint */
4045 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4047 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4048 if (rcItem.right + Origin.x >= rcClip.left) break;
4050 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4052 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4053 if (rcItem.left + Origin.x < rcClip.right) break;
4055 iterator_rangeitems(&j, colRange);
4057 /* in full row select, we _have_ to draw the main item */
4058 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4059 j.nSpecial = 0;
4061 /* iterate through the invalidated rows */
4062 while(iterator_next(i))
4064 /* iterate through the invalidated columns */
4065 while(iterator_next(&j))
4067 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4068 Position.x += Origin.x;
4069 Position.y += Origin.y;
4071 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4073 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4074 rcItem.top = 0;
4075 rcItem.bottom = infoPtr->nItemHeight;
4076 OffsetRect(&rcItem, Position.x, Position.y);
4077 if (!RectVisible(hdc, &rcItem)) continue;
4080 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4083 iterator_destroy(&j);
4086 /***
4087 * DESCRIPTION:
4088 * Draws the gridlines if necessary when in report display mode.
4090 * PARAMETER(S):
4091 * [I] infoPtr : valid pointer to the listview structure
4092 * [I] hdc : device context handle
4094 * RETURN:
4095 * None
4097 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4099 INT rgntype;
4100 INT y, itemheight;
4101 HPEN hPen, hOldPen;
4102 RECT rcClip, rcItem;
4103 POINT Origin;
4104 RANGE colRange;
4105 ITERATOR j;
4107 TRACE("()\n");
4109 /* figure out what to draw */
4110 rgntype = GetClipBox(hdc, &rcClip);
4111 if (rgntype == NULLREGION) return;
4113 /* Get scroll info once before loop */
4114 LISTVIEW_GetOrigin(infoPtr, &Origin);
4116 /* narrow down the columns we need to paint */
4117 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4119 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4120 if (rcItem.right + Origin.x >= rcClip.left) break;
4122 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4124 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4125 if (rcItem.left + Origin.x < rcClip.right) break;
4127 iterator_rangeitems(&j, colRange);
4129 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4131 hOldPen = SelectObject ( hdc, hPen );
4133 /* draw the vertical lines for the columns */
4134 iterator_rangeitems(&j, colRange);
4135 while(iterator_next(&j))
4137 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4138 if (rcItem.left == 0) continue; /* skip first column */
4139 rcItem.left += Origin.x;
4140 rcItem.right += Origin.x;
4141 rcItem.top = infoPtr->rcList.top;
4142 rcItem.bottom = infoPtr->rcList.bottom;
4143 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4144 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4145 LineTo (hdc, rcItem.left, rcItem.bottom);
4147 iterator_destroy(&j);
4149 /* draw the horizontial lines for the rows */
4150 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4151 rcItem.left = infoPtr->rcList.left + Origin.x;
4152 rcItem.right = infoPtr->rcList.right + Origin.x;
4153 rcItem.bottom = rcItem.top = Origin.y - 1;
4154 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4155 LineTo(hdc, rcItem.right, rcItem.top);
4156 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4158 rcItem.bottom = rcItem.top = y;
4159 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4160 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4161 LineTo (hdc, rcItem.right, rcItem.top);
4164 SelectObject( hdc, hOldPen );
4165 DeleteObject( hPen );
4169 /***
4170 * DESCRIPTION:
4171 * Draws listview items when in list display mode.
4173 * PARAMETER(S):
4174 * [I] infoPtr : valid pointer to the listview structure
4175 * [I] hdc : device context handle
4176 * [I] cdmode : custom draw mode
4178 * RETURN:
4179 * None
4181 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4183 POINT Origin, Position;
4185 /* Get scroll info once before loop */
4186 LISTVIEW_GetOrigin(infoPtr, &Origin);
4188 while(iterator_prev(i))
4190 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4191 Position.x += Origin.x;
4192 Position.y += Origin.y;
4194 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4199 /***
4200 * DESCRIPTION:
4201 * Draws listview items.
4203 * PARAMETER(S):
4204 * [I] infoPtr : valid pointer to the listview structure
4205 * [I] hdc : device context handle
4206 * [I] prcErase : rect to be erased before refresh (may be NULL)
4208 * RETURN:
4209 * NoneX
4211 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4213 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4214 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4215 NMLVCUSTOMDRAW nmlvcd;
4216 HFONT hOldFont = 0;
4217 DWORD cdmode;
4218 INT oldBkMode = 0;
4219 RECT rcClient;
4220 ITERATOR i;
4221 HDC hdcOrig = hdc;
4222 HBITMAP hbmp = NULL;
4224 LISTVIEW_DUMP(infoPtr);
4226 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4227 TRACE("double buffering\n");
4229 hdc = CreateCompatibleDC(hdcOrig);
4230 if (!hdc) {
4231 ERR("Failed to create DC for backbuffer\n");
4232 return;
4234 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4235 infoPtr->rcList.bottom);
4236 if (!hbmp) {
4237 ERR("Failed to create bitmap for backbuffer\n");
4238 DeleteDC(hdc);
4239 return;
4242 SelectObject(hdc, hbmp);
4243 SelectObject(hdc, infoPtr->hFont);
4244 } else {
4245 /* Save dc values we're gonna trash while drawing
4246 * FIXME: Should be done in LISTVIEW_DrawItem() */
4247 hOldFont = SelectObject(hdc, infoPtr->hFont);
4248 oldBkMode = GetBkMode(hdc);
4249 oldBkColor = GetBkColor(hdc);
4250 oldTextColor = GetTextColor(hdc);
4253 infoPtr->bIsDrawing = TRUE;
4255 if (prcErase) {
4256 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4257 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4258 /* If no erasing was done (usually because RedrawWindow was called
4259 * with RDW_INVALIDATE only) we need to copy the old contents into
4260 * the backbuffer before continuing. */
4261 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4262 infoPtr->rcList.right - infoPtr->rcList.left,
4263 infoPtr->rcList.bottom - infoPtr->rcList.top,
4264 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4267 /* FIXME: Shouldn't need to do this */
4268 oldClrTextBk = infoPtr->clrTextBk;
4269 oldClrText = infoPtr->clrText;
4271 infoPtr->cditemmode = CDRF_DODEFAULT;
4273 GetClientRect(infoPtr->hwndSelf, &rcClient);
4274 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4275 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4276 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4277 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4279 /* Use these colors to draw the items */
4280 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4281 infoPtr->clrText = nmlvcd.clrText;
4283 /* nothing to draw */
4284 if(infoPtr->nItemCount == 0) goto enddraw;
4286 /* figure out what we need to draw */
4287 iterator_visibleitems(&i, infoPtr, hdc);
4289 /* send cache hint notification */
4290 if (infoPtr->dwStyle & LVS_OWNERDATA)
4292 RANGE range = iterator_range(&i);
4293 NMLVCACHEHINT nmlv;
4295 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4296 nmlv.iFrom = range.lower;
4297 nmlv.iTo = range.upper - 1;
4298 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4301 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4302 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4303 else
4305 if (uView == LVS_REPORT)
4306 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4307 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4308 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4310 /* if we have a focus rect, draw it */
4311 if (infoPtr->bFocus)
4312 DrawFocusRect(hdc, &infoPtr->rcFocus);
4314 iterator_destroy(&i);
4316 enddraw:
4317 /* For LVS_EX_GRIDLINES go and draw lines */
4318 /* This includes the case where there were *no* items */
4319 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4320 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4321 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4323 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4324 notify_postpaint(infoPtr, &nmlvcd);
4326 infoPtr->clrTextBk = oldClrTextBk;
4327 infoPtr->clrText = oldClrText;
4329 if(hbmp) {
4330 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4331 infoPtr->rcList.right - infoPtr->rcList.left,
4332 infoPtr->rcList.bottom - infoPtr->rcList.top,
4333 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4335 DeleteObject(hbmp);
4336 DeleteDC(hdc);
4337 } else {
4338 SelectObject(hdc, hOldFont);
4339 SetBkMode(hdc, oldBkMode);
4340 SetBkColor(hdc, oldBkColor);
4341 SetTextColor(hdc, oldTextColor);
4344 infoPtr->bIsDrawing = FALSE;
4348 /***
4349 * DESCRIPTION:
4350 * Calculates the approximate width and height of a given number of items.
4352 * PARAMETER(S):
4353 * [I] infoPtr : valid pointer to the listview structure
4354 * [I] nItemCount : number of items
4355 * [I] wWidth : width
4356 * [I] wHeight : height
4358 * RETURN:
4359 * Returns a DWORD. The width in the low word and the height in high word.
4361 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4362 WORD wWidth, WORD wHeight)
4364 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4365 INT nItemCountPerColumn = 1;
4366 INT nColumnCount = 0;
4367 DWORD dwViewRect = 0;
4369 if (nItemCount == -1)
4370 nItemCount = infoPtr->nItemCount;
4372 if (uView == LVS_LIST)
4374 if (wHeight == 0xFFFF)
4376 /* use current height */
4377 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4380 if (wHeight < infoPtr->nItemHeight)
4381 wHeight = infoPtr->nItemHeight;
4383 if (nItemCount > 0)
4385 if (infoPtr->nItemHeight > 0)
4387 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4388 if (nItemCountPerColumn == 0)
4389 nItemCountPerColumn = 1;
4391 if (nItemCount % nItemCountPerColumn != 0)
4392 nColumnCount = nItemCount / nItemCountPerColumn;
4393 else
4394 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4398 /* Microsoft padding magic */
4399 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4400 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4402 dwViewRect = MAKELONG(wWidth, wHeight);
4404 else if (uView == LVS_REPORT)
4406 RECT rcBox;
4408 if (infoPtr->nItemCount > 0)
4410 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4411 wWidth = rcBox.right - rcBox.left;
4412 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4414 else
4416 /* use current height and width */
4417 if (wHeight == 0xffff)
4418 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4419 if (wWidth == 0xffff)
4420 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4423 dwViewRect = MAKELONG(wWidth, wHeight);
4425 else if (uView == LVS_SMALLICON)
4426 FIXME("uView == LVS_SMALLICON: not implemented\n");
4427 else if (uView == LVS_ICON)
4428 FIXME("uView == LVS_ICON: not implemented\n");
4430 return dwViewRect;
4434 /***
4435 * DESCRIPTION:
4436 * Create a drag image list for the specified item.
4438 * PARAMETER(S):
4439 * [I] infoPtr : valid pointer to the listview structure
4440 * [I] iItem : index of item
4441 * [O] lppt : Upperr-left corner of the image
4443 * RETURN:
4444 * Returns a handle to the image list if successful, NULL otherwise.
4446 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4448 RECT rcItem;
4449 SIZE size;
4450 POINT pos;
4451 HDC hdc, hdcOrig;
4452 HBITMAP hbmp, hOldbmp;
4453 HIMAGELIST dragList = 0;
4454 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4456 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4457 return 0;
4459 rcItem.left = LVIR_BOUNDS;
4460 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4461 return 0;
4463 lppt->x = rcItem.left;
4464 lppt->y = rcItem.top;
4466 size.cx = rcItem.right - rcItem.left;
4467 size.cy = rcItem.bottom - rcItem.top;
4469 hdcOrig = GetDC(infoPtr->hwndSelf);
4470 hdc = CreateCompatibleDC(hdcOrig);
4471 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4472 hOldbmp = SelectObject(hdc, hbmp);
4474 rcItem.left = rcItem.top = 0;
4475 rcItem.right = size.cx;
4476 rcItem.bottom = size.cy;
4477 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4479 pos.x = pos.y = 0;
4480 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4482 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4483 SelectObject(hdc, hOldbmp);
4484 ImageList_Add(dragList, hbmp, 0);
4486 else
4487 SelectObject(hdc, hOldbmp);
4489 DeleteObject(hbmp);
4490 DeleteDC(hdc);
4491 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4493 TRACE("ret=%p\n", dragList);
4495 return dragList;
4499 /***
4500 * DESCRIPTION:
4501 * Removes all listview items and subitems.
4503 * PARAMETER(S):
4504 * [I] infoPtr : valid pointer to the listview structure
4506 * RETURN:
4507 * SUCCESS : TRUE
4508 * FAILURE : FALSE
4510 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4512 NMLISTVIEW nmlv;
4513 HDPA hdpaSubItems = NULL;
4514 BOOL bSuppress;
4515 ITEMHDR *hdrItem;
4516 INT i, j;
4518 TRACE("()\n");
4520 /* we do it directly, to avoid notifications */
4521 ranges_clear(infoPtr->selectionRanges);
4522 infoPtr->nSelectionMark = -1;
4523 infoPtr->nFocusedItem = -1;
4524 SetRectEmpty(&infoPtr->rcFocus);
4525 /* But we are supposed to leave nHotItem as is! */
4528 /* send LVN_DELETEALLITEMS notification */
4529 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4530 nmlv.iItem = -1;
4531 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4533 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4535 /* send LVN_DELETEITEM notification, if not suppressed */
4536 if (!bSuppress) notify_deleteitem(infoPtr, i);
4537 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4539 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4540 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4542 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4543 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4544 Free(hdrItem);
4546 DPA_Destroy(hdpaSubItems);
4547 DPA_DeletePtr(infoPtr->hdpaItems, i);
4549 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4550 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4551 infoPtr->nItemCount --;
4554 if (!destroy)
4556 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4557 LISTVIEW_UpdateScroll(infoPtr);
4559 LISTVIEW_InvalidateList(infoPtr);
4561 return TRUE;
4564 /***
4565 * DESCRIPTION:
4566 * Scrolls, and updates the columns, when a column is changing width.
4568 * PARAMETER(S):
4569 * [I] infoPtr : valid pointer to the listview structure
4570 * [I] nColumn : column to scroll
4571 * [I] dx : amount of scroll, in pixels
4573 * RETURN:
4574 * None.
4576 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4578 COLUMN_INFO *lpColumnInfo;
4579 RECT rcOld, rcCol;
4580 POINT ptOrigin;
4581 INT nCol;
4583 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4584 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4585 rcCol = lpColumnInfo->rcHeader;
4586 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4587 rcCol.left = rcCol.right;
4589 /* adjust the other columns */
4590 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4592 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4593 lpColumnInfo->rcHeader.left += dx;
4594 lpColumnInfo->rcHeader.right += dx;
4597 /* do not update screen if not in report mode */
4598 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4600 /* if we have a focus, we must first erase the focus rect */
4601 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4603 /* Need to reset the item width when inserting a new column */
4604 infoPtr->nItemWidth += dx;
4606 LISTVIEW_UpdateScroll(infoPtr);
4607 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4609 /* scroll to cover the deleted column, and invalidate for redraw */
4610 rcOld = infoPtr->rcList;
4611 rcOld.left = ptOrigin.x + rcCol.left + dx;
4612 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4614 /* we can restore focus now */
4615 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4618 /***
4619 * DESCRIPTION:
4620 * Removes a column from the listview control.
4622 * PARAMETER(S):
4623 * [I] infoPtr : valid pointer to the listview structure
4624 * [I] nColumn : column index
4626 * RETURN:
4627 * SUCCESS : TRUE
4628 * FAILURE : FALSE
4630 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4632 RECT rcCol;
4634 TRACE("nColumn=%d\n", nColumn);
4636 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4637 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4639 /* While the MSDN specifically says that column zero should not be deleted,
4640 what actually happens is that the column itself is deleted but no items or subitems
4641 are removed.
4644 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4646 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4647 return FALSE;
4649 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4650 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4652 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4654 SUBITEM_INFO *lpSubItem, *lpDelItem;
4655 HDPA hdpaSubItems;
4656 INT nItem, nSubItem, i;
4658 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4660 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4661 nSubItem = 0;
4662 lpDelItem = 0;
4663 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4665 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4666 if (lpSubItem->iSubItem == nColumn)
4668 nSubItem = i;
4669 lpDelItem = lpSubItem;
4671 else if (lpSubItem->iSubItem > nColumn)
4673 lpSubItem->iSubItem--;
4677 /* if we found our subitem, zapp it */
4678 if (nSubItem > 0)
4680 /* free string */
4681 if (is_textW(lpDelItem->hdr.pszText))
4682 Free(lpDelItem->hdr.pszText);
4684 /* free item */
4685 Free(lpDelItem);
4687 /* free dpa memory */
4688 DPA_DeletePtr(hdpaSubItems, nSubItem);
4693 /* update the other column info */
4694 LISTVIEW_UpdateItemSize(infoPtr);
4695 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4696 LISTVIEW_InvalidateList(infoPtr);
4697 else
4698 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4700 return TRUE;
4703 /***
4704 * DESCRIPTION:
4705 * Invalidates the listview after an item's insertion or deletion.
4707 * PARAMETER(S):
4708 * [I] infoPtr : valid pointer to the listview structure
4709 * [I] nItem : item index
4710 * [I] dir : -1 if deleting, 1 if inserting
4712 * RETURN:
4713 * None
4715 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4717 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4718 INT nPerCol, nItemCol, nItemRow;
4719 RECT rcScroll;
4720 POINT Origin;
4722 /* if we don't refresh, what's the point of scrolling? */
4723 if (!is_redrawing(infoPtr)) return;
4725 assert (abs(dir) == 1);
4727 /* arrange icons if autoarrange is on */
4728 if (is_autoarrange(infoPtr))
4730 BOOL arrange = TRUE;
4731 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4732 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4733 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4736 /* scrollbars need updating */
4737 LISTVIEW_UpdateScroll(infoPtr);
4739 /* figure out the item's position */
4740 if (uView == LVS_REPORT)
4741 nPerCol = infoPtr->nItemCount + 1;
4742 else if (uView == LVS_LIST)
4743 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4744 else /* LVS_ICON, or LVS_SMALLICON */
4745 return;
4747 nItemCol = nItem / nPerCol;
4748 nItemRow = nItem % nPerCol;
4749 LISTVIEW_GetOrigin(infoPtr, &Origin);
4751 /* move the items below up a slot */
4752 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4753 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4754 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4755 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4756 OffsetRect(&rcScroll, Origin.x, Origin.y);
4757 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4758 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4760 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4761 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4762 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4765 /* report has only that column, so we're done */
4766 if (uView == LVS_REPORT) return;
4768 /* now for LISTs, we have to deal with the columns to the right */
4769 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4770 rcScroll.top = 0;
4771 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4772 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4773 OffsetRect(&rcScroll, Origin.x, Origin.y);
4774 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4775 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4776 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4779 /***
4780 * DESCRIPTION:
4781 * Removes an item from the listview control.
4783 * PARAMETER(S):
4784 * [I] infoPtr : valid pointer to the listview structure
4785 * [I] nItem : item index
4787 * RETURN:
4788 * SUCCESS : TRUE
4789 * FAILURE : FALSE
4791 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4793 LVITEMW item;
4794 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4795 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4797 TRACE("(nItem=%d)\n", nItem);
4799 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4801 /* remove selection, and focus */
4802 item.state = 0;
4803 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4804 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4806 /* send LVN_DELETEITEM notification. */
4807 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4809 /* we need to do this here, because we'll be deleting stuff */
4810 if (is_icon)
4811 LISTVIEW_InvalidateItem(infoPtr, nItem);
4813 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4815 HDPA hdpaSubItems;
4816 ITEMHDR *hdrItem;
4817 INT i;
4819 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4820 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4822 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4823 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4824 Free(hdrItem);
4826 DPA_Destroy(hdpaSubItems);
4829 if (is_icon)
4831 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4832 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4835 infoPtr->nItemCount--;
4836 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4838 /* now is the invalidation fun */
4839 if (!is_icon)
4840 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4841 return TRUE;
4845 /***
4846 * DESCRIPTION:
4847 * Callback implementation for editlabel control
4849 * PARAMETER(S):
4850 * [I] infoPtr : valid pointer to the listview structure
4851 * [I] pszText : modified text
4852 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4854 * RETURN:
4855 * SUCCESS : TRUE
4856 * FAILURE : FALSE
4858 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4860 HWND hwndSelf = infoPtr->hwndSelf;
4861 NMLVDISPINFOW dispInfo;
4862 INT editedItem = infoPtr->nEditLabelItem;
4863 BOOL bSame;
4865 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4867 infoPtr->nEditLabelItem = -1;
4869 ZeroMemory(&dispInfo, sizeof(dispInfo));
4870 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4871 dispInfo.item.iItem = editedItem;
4872 dispInfo.item.iSubItem = 0;
4873 dispInfo.item.stateMask = ~0;
4874 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4876 if (isW)
4877 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4878 else
4880 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4881 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4882 textfreeT(tmp, FALSE);
4884 if (bSame) return TRUE;
4886 /* add the text from the edit in */
4887 dispInfo.item.mask |= LVIF_TEXT;
4888 dispInfo.item.pszText = pszText;
4889 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4891 /* Do we need to update the Item Text */
4892 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4893 if (!IsWindow(hwndSelf))
4894 return FALSE;
4895 if (!pszText) return TRUE;
4897 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4899 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4900 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4901 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4903 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4904 return TRUE;
4908 ZeroMemory(&dispInfo, sizeof(dispInfo));
4909 dispInfo.item.mask = LVIF_TEXT;
4910 dispInfo.item.iItem = editedItem;
4911 dispInfo.item.iSubItem = 0;
4912 dispInfo.item.pszText = pszText;
4913 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4914 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4917 /***
4918 * DESCRIPTION:
4919 * Begin in place editing of specified list view item
4921 * PARAMETER(S):
4922 * [I] infoPtr : valid pointer to the listview structure
4923 * [I] nItem : item index
4924 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4926 * RETURN:
4927 * SUCCESS : TRUE
4928 * FAILURE : FALSE
4930 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4932 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4933 NMLVDISPINFOW dispInfo;
4934 RECT rect;
4935 HWND hwndSelf = infoPtr->hwndSelf;
4937 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4939 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4940 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4942 infoPtr->nEditLabelItem = nItem;
4944 /* Is the EditBox still there, if so remove it */
4945 if(infoPtr->hwndEdit != 0)
4947 SetFocus(infoPtr->hwndSelf);
4948 infoPtr->hwndEdit = 0;
4951 LISTVIEW_SetSelection(infoPtr, nItem);
4952 LISTVIEW_SetItemFocus(infoPtr, nItem);
4953 LISTVIEW_InvalidateItem(infoPtr, nItem);
4955 rect.left = LVIR_LABEL;
4956 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4958 ZeroMemory(&dispInfo, sizeof(dispInfo));
4959 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4960 dispInfo.item.iItem = nItem;
4961 dispInfo.item.iSubItem = 0;
4962 dispInfo.item.stateMask = ~0;
4963 dispInfo.item.pszText = szDispText;
4964 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4965 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4967 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4968 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4969 if (!infoPtr->hwndEdit) return 0;
4971 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4973 if (!IsWindow(hwndSelf))
4974 return 0;
4975 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4976 infoPtr->hwndEdit = 0;
4977 return 0;
4980 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4981 SetFocus(infoPtr->hwndEdit);
4982 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4983 return infoPtr->hwndEdit;
4987 /***
4988 * DESCRIPTION:
4989 * Ensures the specified item is visible, scrolling into view if necessary.
4991 * PARAMETER(S):
4992 * [I] infoPtr : valid pointer to the listview structure
4993 * [I] nItem : item index
4994 * [I] bPartial : partially or entirely visible
4996 * RETURN:
4997 * SUCCESS : TRUE
4998 * FAILURE : FALSE
5000 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5002 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5003 INT nScrollPosHeight = 0;
5004 INT nScrollPosWidth = 0;
5005 INT nHorzAdjust = 0;
5006 INT nVertAdjust = 0;
5007 INT nHorzDiff = 0;
5008 INT nVertDiff = 0;
5009 RECT rcItem, rcTemp;
5011 rcItem.left = LVIR_BOUNDS;
5012 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5014 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5016 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5018 /* scroll left/right, but in LVS_REPORT mode */
5019 if (uView == LVS_LIST)
5020 nScrollPosWidth = infoPtr->nItemWidth;
5021 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5022 nScrollPosWidth = 1;
5024 if (rcItem.left < infoPtr->rcList.left)
5026 nHorzAdjust = -1;
5027 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5029 else
5031 nHorzAdjust = 1;
5032 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5036 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5038 /* scroll up/down, but not in LVS_LIST mode */
5039 if (uView == LVS_REPORT)
5040 nScrollPosHeight = infoPtr->nItemHeight;
5041 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5042 nScrollPosHeight = 1;
5044 if (rcItem.top < infoPtr->rcList.top)
5046 nVertAdjust = -1;
5047 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5049 else
5051 nVertAdjust = 1;
5052 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5056 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5058 if (nScrollPosWidth)
5060 INT diff = nHorzDiff / nScrollPosWidth;
5061 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5062 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5065 if (nScrollPosHeight)
5067 INT diff = nVertDiff / nScrollPosHeight;
5068 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5069 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5072 return TRUE;
5075 /***
5076 * DESCRIPTION:
5077 * Searches for an item with specific characteristics.
5079 * PARAMETER(S):
5080 * [I] hwnd : window handle
5081 * [I] nStart : base item index
5082 * [I] lpFindInfo : item information to look for
5084 * RETURN:
5085 * SUCCESS : index of item
5086 * FAILURE : -1
5088 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5089 const LVFINDINFOW *lpFindInfo)
5091 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5092 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5093 BOOL bWrap = FALSE, bNearest = FALSE;
5094 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5095 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5096 POINT Position, Destination;
5097 LVITEMW lvItem;
5099 /* Search in virtual listviews should be done by application, not by
5100 listview control, so we just send LVN_ODFINDITEMW and return the result */
5101 if (infoPtr->dwStyle & LVS_OWNERDATA)
5103 NMLVFINDITEMW nmlv;
5105 nmlv.iStart = nStart;
5106 nmlv.lvfi = *lpFindInfo;
5107 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5110 if (!lpFindInfo || nItem < 0) return -1;
5112 lvItem.mask = 0;
5113 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5115 lvItem.mask |= LVIF_TEXT;
5116 lvItem.pszText = szDispText;
5117 lvItem.cchTextMax = DISP_TEXT_SIZE;
5120 if (lpFindInfo->flags & LVFI_WRAP)
5121 bWrap = TRUE;
5123 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5124 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5126 POINT Origin;
5127 RECT rcArea;
5129 LISTVIEW_GetOrigin(infoPtr, &Origin);
5130 Destination.x = lpFindInfo->pt.x - Origin.x;
5131 Destination.y = lpFindInfo->pt.y - Origin.y;
5132 switch(lpFindInfo->vkDirection)
5134 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5135 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5136 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5137 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5138 case VK_HOME: Destination.x = Destination.y = 0; break;
5139 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5140 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5141 case VK_END:
5142 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5143 Destination.x = rcArea.right;
5144 Destination.y = rcArea.bottom;
5145 break;
5146 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5148 bNearest = TRUE;
5150 else Destination.x = Destination.y = 0;
5152 /* if LVFI_PARAM is specified, all other flags are ignored */
5153 if (lpFindInfo->flags & LVFI_PARAM)
5155 lvItem.mask |= LVIF_PARAM;
5156 bNearest = FALSE;
5157 lvItem.mask &= ~LVIF_TEXT;
5160 again:
5161 for (; nItem < nLast; nItem++)
5163 lvItem.iItem = nItem;
5164 lvItem.iSubItem = 0;
5165 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5167 if (lvItem.mask & LVIF_PARAM)
5169 if (lpFindInfo->lParam == lvItem.lParam)
5170 return nItem;
5171 else
5172 continue;
5175 if (lvItem.mask & LVIF_TEXT)
5177 if (lpFindInfo->flags & LVFI_PARTIAL)
5179 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5181 else
5183 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5187 if (!bNearest) return nItem;
5189 /* This is very inefficient. To do a good job here,
5190 * we need a sorted array of (x,y) item positions */
5191 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5193 /* compute the distance^2 to the destination */
5194 xdist = Destination.x - Position.x;
5195 ydist = Destination.y - Position.y;
5196 dist = xdist * xdist + ydist * ydist;
5198 /* remember the distance, and item if it's closer */
5199 if (dist < mindist)
5201 mindist = dist;
5202 nNearestItem = nItem;
5206 if (bWrap)
5208 nItem = 0;
5209 nLast = min(nStart + 1, infoPtr->nItemCount);
5210 bWrap = FALSE;
5211 goto again;
5214 return nNearestItem;
5217 /***
5218 * DESCRIPTION:
5219 * Searches for an item with specific characteristics.
5221 * PARAMETER(S):
5222 * [I] hwnd : window handle
5223 * [I] nStart : base item index
5224 * [I] lpFindInfo : item information to look for
5226 * RETURN:
5227 * SUCCESS : index of item
5228 * FAILURE : -1
5230 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5231 const LVFINDINFOA *lpFindInfo)
5233 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5234 LVFINDINFOW fiw;
5235 INT res;
5236 LPWSTR strW = NULL;
5238 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5239 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5240 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5241 textfreeT(strW, FALSE);
5242 return res;
5245 /***
5246 * DESCRIPTION:
5247 * Retrieves the background image of the listview control.
5249 * PARAMETER(S):
5250 * [I] infoPtr : valid pointer to the listview structure
5251 * [O] lpBkImage : background image attributes
5253 * RETURN:
5254 * SUCCESS : TRUE
5255 * FAILURE : FALSE
5257 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5258 /* { */
5259 /* FIXME (listview, "empty stub!\n"); */
5260 /* return FALSE; */
5261 /* } */
5263 /***
5264 * DESCRIPTION:
5265 * Retrieves column attributes.
5267 * PARAMETER(S):
5268 * [I] infoPtr : valid pointer to the listview structure
5269 * [I] nColumn : column index
5270 * [IO] lpColumn : column information
5271 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5272 * otherwise it is in fact a LPLVCOLUMNA
5274 * RETURN:
5275 * SUCCESS : TRUE
5276 * FAILURE : FALSE
5278 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5280 COLUMN_INFO *lpColumnInfo;
5281 HDITEMW hdi;
5283 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5284 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5286 /* initialize memory */
5287 ZeroMemory(&hdi, sizeof(hdi));
5289 if (lpColumn->mask & LVCF_TEXT)
5291 hdi.mask |= HDI_TEXT;
5292 hdi.pszText = lpColumn->pszText;
5293 hdi.cchTextMax = lpColumn->cchTextMax;
5296 if (lpColumn->mask & LVCF_IMAGE)
5297 hdi.mask |= HDI_IMAGE;
5299 if (lpColumn->mask & LVCF_ORDER)
5300 hdi.mask |= HDI_ORDER;
5302 if (lpColumn->mask & LVCF_SUBITEM)
5303 hdi.mask |= HDI_LPARAM;
5305 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5307 if (lpColumn->mask & LVCF_FMT)
5308 lpColumn->fmt = lpColumnInfo->fmt;
5310 if (lpColumn->mask & LVCF_WIDTH)
5311 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5313 if (lpColumn->mask & LVCF_IMAGE)
5314 lpColumn->iImage = hdi.iImage;
5316 if (lpColumn->mask & LVCF_ORDER)
5317 lpColumn->iOrder = hdi.iOrder;
5319 if (lpColumn->mask & LVCF_SUBITEM)
5320 lpColumn->iSubItem = hdi.lParam;
5322 return TRUE;
5326 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5328 INT i;
5330 if (!lpiArray)
5331 return FALSE;
5333 /* FIXME: little hack */
5334 for (i = 0; i < iCount; i++)
5335 lpiArray[i] = i;
5337 return TRUE;
5340 /***
5341 * DESCRIPTION:
5342 * Retrieves the column width.
5344 * PARAMETER(S):
5345 * [I] infoPtr : valid pointer to the listview structure
5346 * [I] int : column index
5348 * RETURN:
5349 * SUCCESS : column width
5350 * FAILURE : zero
5352 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5354 INT nColumnWidth = 0;
5355 HDITEMW hdItem;
5357 TRACE("nColumn=%d\n", nColumn);
5359 /* we have a 'column' in LIST and REPORT mode only */
5360 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5362 case LVS_LIST:
5363 nColumnWidth = infoPtr->nItemWidth;
5364 break;
5365 case LVS_REPORT:
5366 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5367 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5368 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5370 * TODO: should we do the same in LVM_GETCOLUMN?
5372 hdItem.mask = HDI_WIDTH;
5373 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5375 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5376 return 0;
5378 nColumnWidth = hdItem.cxy;
5379 break;
5382 TRACE("nColumnWidth=%d\n", nColumnWidth);
5383 return nColumnWidth;
5386 /***
5387 * DESCRIPTION:
5388 * In list or report display mode, retrieves the number of items that can fit
5389 * vertically in the visible area. In icon or small icon display mode,
5390 * retrieves the total number of visible items.
5392 * PARAMETER(S):
5393 * [I] infoPtr : valid pointer to the listview structure
5395 * RETURN:
5396 * Number of fully visible items.
5398 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5400 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5402 case LVS_ICON:
5403 case LVS_SMALLICON:
5404 return infoPtr->nItemCount;
5405 case LVS_REPORT:
5406 return LISTVIEW_GetCountPerColumn(infoPtr);
5407 case LVS_LIST:
5408 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5410 assert(FALSE);
5411 return 0;
5414 /***
5415 * DESCRIPTION:
5416 * Retrieves an image list handle.
5418 * PARAMETER(S):
5419 * [I] infoPtr : valid pointer to the listview structure
5420 * [I] nImageList : image list identifier
5422 * RETURN:
5423 * SUCCESS : image list handle
5424 * FAILURE : NULL
5426 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5428 switch (nImageList)
5430 case LVSIL_NORMAL: return infoPtr->himlNormal;
5431 case LVSIL_SMALL: return infoPtr->himlSmall;
5432 case LVSIL_STATE: return infoPtr->himlState;
5434 return NULL;
5437 /* LISTVIEW_GetISearchString */
5439 /***
5440 * DESCRIPTION:
5441 * Retrieves item attributes.
5443 * PARAMETER(S):
5444 * [I] hwnd : window handle
5445 * [IO] lpLVItem : item info
5446 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5447 * if FALSE, then lpLVItem is a LPLVITEMA.
5449 * NOTE:
5450 * This is the internal 'GetItem' interface -- it tries to
5451 * be smart and avoid text copies, if possible, by modifying
5452 * lpLVItem->pszText to point to the text string. Please note
5453 * that this is not always possible (e.g. OWNERDATA), so on
5454 * entry you *must* supply valid values for pszText, and cchTextMax.
5455 * The only difference to the documented interface is that upon
5456 * return, you should use *only* the lpLVItem->pszText, rather than
5457 * the buffer pointer you provided on input. Most code already does
5458 * that, so it's not a problem.
5459 * For the two cases when the text must be copied (that is,
5460 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5462 * RETURN:
5463 * SUCCESS : TRUE
5464 * FAILURE : FALSE
5466 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5468 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5469 NMLVDISPINFOW dispInfo;
5470 ITEM_INFO *lpItem;
5471 ITEMHDR* pItemHdr;
5472 HDPA hdpaSubItems;
5473 INT isubitem;
5475 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5477 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5478 return FALSE;
5480 if (lpLVItem->mask == 0) return TRUE;
5482 /* make a local copy */
5483 isubitem = lpLVItem->iSubItem;
5485 /* a quick optimization if all we're asked is the focus state
5486 * these queries are worth optimising since they are common,
5487 * and can be answered in constant time, without the heavy accesses */
5488 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5489 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5491 lpLVItem->state = 0;
5492 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5493 lpLVItem->state |= LVIS_FOCUSED;
5494 return TRUE;
5497 ZeroMemory(&dispInfo, sizeof(dispInfo));
5499 /* if the app stores all the data, handle it separately */
5500 if (infoPtr->dwStyle & LVS_OWNERDATA)
5502 dispInfo.item.state = 0;
5504 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5505 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5507 /* NOTE: copy only fields which we _know_ are initialized, some apps
5508 * depend on the uninitialized fields being 0 */
5509 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5510 dispInfo.item.iItem = lpLVItem->iItem;
5511 dispInfo.item.iSubItem = isubitem;
5512 if (lpLVItem->mask & LVIF_TEXT)
5514 dispInfo.item.pszText = lpLVItem->pszText;
5515 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5517 if (lpLVItem->mask & LVIF_STATE)
5518 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5519 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5520 dispInfo.item.stateMask = lpLVItem->stateMask;
5521 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5523 /* full size structure expected - _WIN32IE >= 0x560 */
5524 *lpLVItem = dispInfo.item;
5526 else if (lpLVItem->mask & LVIF_INDENT)
5528 /* indent member expected - _WIN32IE >= 0x300 */
5529 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5531 else
5533 /* minimal structure expected */
5534 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5536 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5539 /* make sure lParam is zeroed out */
5540 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5542 /* we store only a little state, so if we're not asked, we're done */
5543 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5545 /* if focus is handled by us, report it */
5546 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5548 lpLVItem->state &= ~LVIS_FOCUSED;
5549 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5550 lpLVItem->state |= LVIS_FOCUSED;
5553 /* and do the same for selection, if we handle it */
5554 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5556 lpLVItem->state &= ~LVIS_SELECTED;
5557 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5558 lpLVItem->state |= LVIS_SELECTED;
5561 return TRUE;
5564 /* find the item and subitem structures before we proceed */
5565 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5566 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5567 assert (lpItem);
5569 if (isubitem)
5571 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5572 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5573 if (!lpSubItem)
5575 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5576 isubitem = 0;
5579 else
5580 pItemHdr = &lpItem->hdr;
5582 /* Do we need to query the state from the app? */
5583 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5585 dispInfo.item.mask |= LVIF_STATE;
5586 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5589 /* Do we need to enquire about the image? */
5590 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5591 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5593 dispInfo.item.mask |= LVIF_IMAGE;
5594 dispInfo.item.iImage = I_IMAGECALLBACK;
5597 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5598 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5600 dispInfo.item.mask |= LVIF_TEXT;
5601 dispInfo.item.pszText = lpLVItem->pszText;
5602 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5603 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5604 *dispInfo.item.pszText = '\0';
5607 /* If we don't have all the requested info, query the application */
5608 if (dispInfo.item.mask != 0)
5610 dispInfo.item.iItem = lpLVItem->iItem;
5611 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5612 dispInfo.item.lParam = lpItem->lParam;
5613 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5614 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5617 /* we should not store values for subitems */
5618 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5620 /* Now, handle the iImage field */
5621 if (dispInfo.item.mask & LVIF_IMAGE)
5623 lpLVItem->iImage = dispInfo.item.iImage;
5624 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5625 pItemHdr->iImage = dispInfo.item.iImage;
5627 else if (lpLVItem->mask & LVIF_IMAGE)
5629 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5630 lpLVItem->iImage = pItemHdr->iImage;
5631 else
5632 lpLVItem->iImage = 0;
5635 /* The pszText field */
5636 if (dispInfo.item.mask & LVIF_TEXT)
5638 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5639 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5641 lpLVItem->pszText = dispInfo.item.pszText;
5643 else if (lpLVItem->mask & LVIF_TEXT)
5645 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5646 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5649 /* Next is the lParam field */
5650 if (dispInfo.item.mask & LVIF_PARAM)
5652 lpLVItem->lParam = dispInfo.item.lParam;
5653 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5654 lpItem->lParam = dispInfo.item.lParam;
5656 else if (lpLVItem->mask & LVIF_PARAM)
5657 lpLVItem->lParam = lpItem->lParam;
5659 /* if this is a subitem, we're done */
5660 if (isubitem) return TRUE;
5662 /* ... the state field (this one is different due to uCallbackmask) */
5663 if (lpLVItem->mask & LVIF_STATE)
5665 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5666 if (dispInfo.item.mask & LVIF_STATE)
5668 lpLVItem->state &= ~dispInfo.item.stateMask;
5669 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5671 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5673 lpLVItem->state &= ~LVIS_FOCUSED;
5674 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5675 lpLVItem->state |= LVIS_FOCUSED;
5677 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5679 lpLVItem->state &= ~LVIS_SELECTED;
5680 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5681 lpLVItem->state |= LVIS_SELECTED;
5685 /* and last, but not least, the indent field */
5686 if (lpLVItem->mask & LVIF_INDENT)
5687 lpLVItem->iIndent = lpItem->iIndent;
5689 return TRUE;
5692 /***
5693 * DESCRIPTION:
5694 * Retrieves item attributes.
5696 * PARAMETER(S):
5697 * [I] hwnd : window handle
5698 * [IO] lpLVItem : item info
5699 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5700 * if FALSE, then lpLVItem is a LPLVITEMA.
5702 * NOTE:
5703 * This is the external 'GetItem' interface -- it properly copies
5704 * the text in the provided buffer.
5706 * RETURN:
5707 * SUCCESS : TRUE
5708 * FAILURE : FALSE
5710 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5712 LPWSTR pszText;
5713 BOOL bResult;
5715 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5716 return FALSE;
5718 pszText = lpLVItem->pszText;
5719 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5720 if (bResult && lpLVItem->pszText != pszText)
5721 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5722 lpLVItem->pszText = pszText;
5724 return bResult;
5728 /***
5729 * DESCRIPTION:
5730 * Retrieves the position (upper-left) of the listview control item.
5731 * Note that for LVS_ICON style, the upper-left is that of the icon
5732 * and not the bounding box.
5734 * PARAMETER(S):
5735 * [I] infoPtr : valid pointer to the listview structure
5736 * [I] nItem : item index
5737 * [O] lpptPosition : coordinate information
5739 * RETURN:
5740 * SUCCESS : TRUE
5741 * FAILURE : FALSE
5743 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5745 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5746 POINT Origin;
5748 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5750 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5752 LISTVIEW_GetOrigin(infoPtr, &Origin);
5753 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5755 if (uView == LVS_ICON)
5757 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5758 lpptPosition->y += ICON_TOP_PADDING;
5760 lpptPosition->x += Origin.x;
5761 lpptPosition->y += Origin.y;
5763 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5764 return TRUE;
5768 /***
5769 * DESCRIPTION:
5770 * Retrieves the bounding rectangle for a listview control item.
5772 * PARAMETER(S):
5773 * [I] infoPtr : valid pointer to the listview structure
5774 * [I] nItem : item index
5775 * [IO] lprc : bounding rectangle coordinates
5776 * lprc->left specifies the portion of the item for which the bounding
5777 * rectangle will be retrieved.
5779 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5780 * including the icon and label.
5782 * * For LVS_ICON
5783 * * Experiment shows that native control returns:
5784 * * width = min (48, length of text line)
5785 * * .left = position.x - (width - iconsize.cx)/2
5786 * * .right = .left + width
5787 * * height = #lines of text * ntmHeight + icon height + 8
5788 * * .top = position.y - 2
5789 * * .bottom = .top + height
5790 * * separation between items .y = itemSpacing.cy - height
5791 * * .x = itemSpacing.cx - width
5792 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5794 * * For LVS_ICON
5795 * * Experiment shows that native control returns:
5796 * * width = iconSize.cx + 16
5797 * * .left = position.x - (width - iconsize.cx)/2
5798 * * .right = .left + width
5799 * * height = iconSize.cy + 4
5800 * * .top = position.y - 2
5801 * * .bottom = .top + height
5802 * * separation between items .y = itemSpacing.cy - height
5803 * * .x = itemSpacing.cx - width
5804 * LVIR_LABEL Returns the bounding rectangle of the item text.
5806 * * For LVS_ICON
5807 * * Experiment shows that native control returns:
5808 * * width = text length
5809 * * .left = position.x - width/2
5810 * * .right = .left + width
5811 * * height = ntmH * linecount + 2
5812 * * .top = position.y + iconSize.cy + 6
5813 * * .bottom = .top + height
5814 * * separation between items .y = itemSpacing.cy - height
5815 * * .x = itemSpacing.cx - width
5816 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5817 * rectangles, but excludes columns in report view.
5819 * RETURN:
5820 * SUCCESS : TRUE
5821 * FAILURE : FALSE
5823 * NOTES
5824 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5825 * upon whether the window has the focus currently and on whether the item
5826 * is the one with the focus. Ensure that the control's record of which
5827 * item has the focus agrees with the items' records.
5829 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5831 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5832 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5833 BOOL doLabel = TRUE, oversizedBox = FALSE;
5834 POINT Position, Origin;
5835 LVITEMW lvItem;
5837 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5839 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5841 LISTVIEW_GetOrigin(infoPtr, &Origin);
5842 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5844 /* Be smart and try to figure out the minimum we have to do */
5845 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5846 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5847 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5848 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5849 oversizedBox = TRUE;
5851 /* get what we need from the item before hand, so we make
5852 * only one request. This can speed up things, if data
5853 * is stored on the app side */
5854 lvItem.mask = 0;
5855 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5856 if (doLabel) lvItem.mask |= LVIF_TEXT;
5857 lvItem.iItem = nItem;
5858 lvItem.iSubItem = 0;
5859 lvItem.pszText = szDispText;
5860 lvItem.cchTextMax = DISP_TEXT_SIZE;
5861 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5862 /* we got the state already up, simulate it here, to avoid a reget */
5863 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5865 lvItem.mask |= LVIF_STATE;
5866 lvItem.stateMask = LVIS_FOCUSED;
5867 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5870 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5871 lprc->left = LVIR_BOUNDS;
5872 switch(lprc->left)
5874 case LVIR_ICON:
5875 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5876 break;
5878 case LVIR_LABEL:
5879 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5880 break;
5882 case LVIR_BOUNDS:
5883 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5884 break;
5886 case LVIR_SELECTBOUNDS:
5887 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5888 break;
5890 default:
5891 WARN("Unknown value: %d\n", lprc->left);
5892 return FALSE;
5895 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5897 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5899 return TRUE;
5902 /***
5903 * DESCRIPTION:
5904 * Retrieves the spacing between listview control items.
5906 * PARAMETER(S):
5907 * [I] infoPtr : valid pointer to the listview structure
5908 * [IO] lprc : rectangle to receive the output
5909 * on input, lprc->top = nSubItem
5910 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5912 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5913 * not only those of the first column.
5914 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5916 * RETURN:
5917 * TRUE: success
5918 * FALSE: failure
5920 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5922 POINT Position;
5923 LVITEMW lvItem;
5924 INT nColumn;
5926 if (!lprc) return FALSE;
5928 nColumn = lprc->top;
5930 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5931 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5932 if (lprc->top == 0)
5933 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5935 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5937 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5939 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5941 lvItem.mask = 0;
5942 lvItem.iItem = nItem;
5943 lvItem.iSubItem = nColumn;
5945 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5946 switch(lprc->left)
5948 case LVIR_ICON:
5949 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5950 break;
5952 case LVIR_LABEL:
5953 case LVIR_BOUNDS:
5954 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5955 break;
5957 default:
5958 ERR("Unknown bounds=%d\n", lprc->left);
5959 return FALSE;
5962 OffsetRect(lprc, Position.x, Position.y);
5963 return TRUE;
5967 /***
5968 * DESCRIPTION:
5969 * Retrieves the width of a label.
5971 * PARAMETER(S):
5972 * [I] infoPtr : valid pointer to the listview structure
5974 * RETURN:
5975 * SUCCESS : string width (in pixels)
5976 * FAILURE : zero
5978 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5980 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5981 LVITEMW lvItem;
5983 TRACE("(nItem=%d)\n", nItem);
5985 lvItem.mask = LVIF_TEXT;
5986 lvItem.iItem = nItem;
5987 lvItem.iSubItem = 0;
5988 lvItem.pszText = szDispText;
5989 lvItem.cchTextMax = DISP_TEXT_SIZE;
5990 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5992 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5995 /***
5996 * DESCRIPTION:
5997 * Retrieves the spacing between listview control items.
5999 * PARAMETER(S):
6000 * [I] infoPtr : valid pointer to the listview structure
6001 * [I] bSmall : flag for small or large icon
6003 * RETURN:
6004 * Horizontal + vertical spacing
6006 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6008 LONG lResult;
6010 if (!bSmall)
6012 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6014 else
6016 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6017 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6018 else
6019 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6021 return lResult;
6024 /***
6025 * DESCRIPTION:
6026 * Retrieves the state of a listview control item.
6028 * PARAMETER(S):
6029 * [I] infoPtr : valid pointer to the listview structure
6030 * [I] nItem : item index
6031 * [I] uMask : state mask
6033 * RETURN:
6034 * State specified by the mask.
6036 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6038 LVITEMW lvItem;
6040 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6042 lvItem.iItem = nItem;
6043 lvItem.iSubItem = 0;
6044 lvItem.mask = LVIF_STATE;
6045 lvItem.stateMask = uMask;
6046 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6048 return lvItem.state & uMask;
6051 /***
6052 * DESCRIPTION:
6053 * Retrieves the text of a listview control item or subitem.
6055 * PARAMETER(S):
6056 * [I] hwnd : window handle
6057 * [I] nItem : item index
6058 * [IO] lpLVItem : item information
6059 * [I] isW : TRUE if lpLVItem is Unicode
6061 * RETURN:
6062 * SUCCESS : string length
6063 * FAILURE : 0
6065 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6067 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6069 lpLVItem->mask = LVIF_TEXT;
6070 lpLVItem->iItem = nItem;
6071 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6073 return textlenT(lpLVItem->pszText, isW);
6076 /***
6077 * DESCRIPTION:
6078 * Searches for an item based on properties + relationships.
6080 * PARAMETER(S):
6081 * [I] infoPtr : valid pointer to the listview structure
6082 * [I] nItem : item index
6083 * [I] uFlags : relationship flag
6085 * RETURN:
6086 * SUCCESS : item index
6087 * FAILURE : -1
6089 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6091 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6092 UINT uMask = 0;
6093 LVFINDINFOW lvFindInfo;
6094 INT nCountPerColumn;
6095 INT nCountPerRow;
6096 INT i;
6098 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6099 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6101 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6103 if (uFlags & LVNI_CUT)
6104 uMask |= LVIS_CUT;
6106 if (uFlags & LVNI_DROPHILITED)
6107 uMask |= LVIS_DROPHILITED;
6109 if (uFlags & LVNI_FOCUSED)
6110 uMask |= LVIS_FOCUSED;
6112 if (uFlags & LVNI_SELECTED)
6113 uMask |= LVIS_SELECTED;
6115 /* if we're asked for the focused item, that's only one,
6116 * so it's worth optimizing */
6117 if (uFlags & LVNI_FOCUSED)
6119 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6120 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6123 if (uFlags & LVNI_ABOVE)
6125 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6127 while (nItem >= 0)
6129 nItem--;
6130 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6131 return nItem;
6134 else
6136 /* Special case for autoarrange - move 'til the top of a list */
6137 if (is_autoarrange(infoPtr))
6139 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6140 while (nItem - nCountPerRow >= 0)
6142 nItem -= nCountPerRow;
6143 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6144 return nItem;
6146 return -1;
6148 lvFindInfo.flags = LVFI_NEARESTXY;
6149 lvFindInfo.vkDirection = VK_UP;
6150 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6151 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6153 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6154 return nItem;
6158 else if (uFlags & LVNI_BELOW)
6160 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6162 while (nItem < infoPtr->nItemCount)
6164 nItem++;
6165 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6166 return nItem;
6169 else
6171 /* Special case for autoarrange - move 'til the bottom of a list */
6172 if (is_autoarrange(infoPtr))
6174 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6175 while (nItem + nCountPerRow < infoPtr->nItemCount )
6177 nItem += nCountPerRow;
6178 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6179 return nItem;
6181 return -1;
6183 lvFindInfo.flags = LVFI_NEARESTXY;
6184 lvFindInfo.vkDirection = VK_DOWN;
6185 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6186 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6188 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6189 return nItem;
6193 else if (uFlags & LVNI_TOLEFT)
6195 if (uView == LVS_LIST)
6197 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6198 while (nItem - nCountPerColumn >= 0)
6200 nItem -= nCountPerColumn;
6201 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6202 return nItem;
6205 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6207 /* Special case for autoarrange - move 'ti the beginning of a row */
6208 if (is_autoarrange(infoPtr))
6210 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6211 while (nItem % nCountPerRow > 0)
6213 nItem --;
6214 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6215 return nItem;
6217 return -1;
6219 lvFindInfo.flags = LVFI_NEARESTXY;
6220 lvFindInfo.vkDirection = VK_LEFT;
6221 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6222 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6224 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6225 return nItem;
6229 else if (uFlags & LVNI_TORIGHT)
6231 if (uView == LVS_LIST)
6233 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6234 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6236 nItem += nCountPerColumn;
6237 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6238 return nItem;
6241 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6243 /* Special case for autoarrange - move 'til the end of a row */
6244 if (is_autoarrange(infoPtr))
6246 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6247 while (nItem % nCountPerRow < nCountPerRow - 1 )
6249 nItem ++;
6250 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6251 return nItem;
6253 return -1;
6255 lvFindInfo.flags = LVFI_NEARESTXY;
6256 lvFindInfo.vkDirection = VK_RIGHT;
6257 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6258 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6260 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6261 return nItem;
6265 else
6267 nItem++;
6269 /* search by index */
6270 for (i = nItem; i < infoPtr->nItemCount; i++)
6272 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6273 return i;
6277 return -1;
6280 /* LISTVIEW_GetNumberOfWorkAreas */
6282 /***
6283 * DESCRIPTION:
6284 * Retrieves the origin coordinates when in icon or small icon display mode.
6286 * PARAMETER(S):
6287 * [I] infoPtr : valid pointer to the listview structure
6288 * [O] lpptOrigin : coordinate information
6290 * RETURN:
6291 * None.
6293 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6295 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6296 INT nHorzPos = 0, nVertPos = 0;
6297 SCROLLINFO scrollInfo;
6299 scrollInfo.cbSize = sizeof(SCROLLINFO);
6300 scrollInfo.fMask = SIF_POS;
6302 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6303 nHorzPos = scrollInfo.nPos;
6304 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6305 nVertPos = scrollInfo.nPos;
6307 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6309 lpptOrigin->x = infoPtr->rcList.left;
6310 lpptOrigin->y = infoPtr->rcList.top;
6311 if (uView == LVS_LIST)
6312 nHorzPos *= infoPtr->nItemWidth;
6313 else if (uView == LVS_REPORT)
6314 nVertPos *= infoPtr->nItemHeight;
6316 lpptOrigin->x -= nHorzPos;
6317 lpptOrigin->y -= nVertPos;
6319 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6322 /***
6323 * DESCRIPTION:
6324 * Retrieves the width of a string.
6326 * PARAMETER(S):
6327 * [I] hwnd : window handle
6328 * [I] lpszText : text string to process
6329 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6331 * RETURN:
6332 * SUCCESS : string width (in pixels)
6333 * FAILURE : zero
6335 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6337 SIZE stringSize;
6339 stringSize.cx = 0;
6340 if (is_textT(lpszText, isW))
6342 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6343 HDC hdc = GetDC(infoPtr->hwndSelf);
6344 HFONT hOldFont = SelectObject(hdc, hFont);
6346 if (isW)
6347 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6348 else
6349 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6350 SelectObject(hdc, hOldFont);
6351 ReleaseDC(infoPtr->hwndSelf, hdc);
6353 return stringSize.cx;
6356 /***
6357 * DESCRIPTION:
6358 * Determines which listview item is located at the specified position.
6360 * PARAMETER(S):
6361 * [I] infoPtr : valid pointer to the listview structure
6362 * [IO] lpht : hit test information
6363 * [I] subitem : fill out iSubItem.
6364 * [I] select : return the index only if the hit selects the item
6366 * NOTE:
6367 * (mm 20001022): We must not allow iSubItem to be touched, for
6368 * an app might pass only a structure with space up to iItem!
6369 * (MS Office 97 does that for instance in the file open dialog)
6371 * RETURN:
6372 * SUCCESS : item index
6373 * FAILURE : -1
6375 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6377 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6378 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6379 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6380 POINT Origin, Position, opt;
6381 LVITEMW lvItem;
6382 ITERATOR i;
6383 INT iItem;
6385 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6387 lpht->flags = 0;
6388 lpht->iItem = -1;
6389 if (subitem) lpht->iSubItem = 0;
6391 if (infoPtr->rcList.left > lpht->pt.x)
6392 lpht->flags |= LVHT_TOLEFT;
6393 else if (infoPtr->rcList.right < lpht->pt.x)
6394 lpht->flags |= LVHT_TORIGHT;
6396 if (infoPtr->rcList.top > lpht->pt.y)
6397 lpht->flags |= LVHT_ABOVE;
6398 else if (infoPtr->rcList.bottom < lpht->pt.y)
6399 lpht->flags |= LVHT_BELOW;
6401 TRACE("lpht->flags=0x%x\n", lpht->flags);
6402 if (lpht->flags) return -1;
6404 lpht->flags |= LVHT_NOWHERE;
6406 LISTVIEW_GetOrigin(infoPtr, &Origin);
6408 /* first deal with the large items */
6409 rcSearch.left = lpht->pt.x;
6410 rcSearch.top = lpht->pt.y;
6411 rcSearch.right = rcSearch.left + 1;
6412 rcSearch.bottom = rcSearch.top + 1;
6414 iterator_frameditems(&i, infoPtr, &rcSearch);
6415 iterator_next(&i); /* go to first item in the sequence */
6416 iItem = i.nItem;
6417 iterator_destroy(&i);
6419 TRACE("lpht->iItem=%d\n", iItem);
6420 if (iItem == -1) return -1;
6422 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6423 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6424 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6425 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6426 lvItem.iItem = iItem;
6427 lvItem.iSubItem = 0;
6428 lvItem.pszText = szDispText;
6429 lvItem.cchTextMax = DISP_TEXT_SIZE;
6430 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6431 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6433 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6434 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6435 opt.x = lpht->pt.x - Position.x - Origin.x;
6436 opt.y = lpht->pt.y - Position.y - Origin.y;
6438 if (uView == LVS_REPORT)
6439 rcBounds = rcBox;
6440 else
6442 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6443 UnionRect(&rcBounds, &rcBounds, &rcState);
6445 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6446 if (!PtInRect(&rcBounds, opt)) return -1;
6448 if (PtInRect(&rcIcon, opt))
6449 lpht->flags |= LVHT_ONITEMICON;
6450 else if (PtInRect(&rcLabel, opt))
6451 lpht->flags |= LVHT_ONITEMLABEL;
6452 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6453 lpht->flags |= LVHT_ONITEMSTATEICON;
6454 if (lpht->flags & LVHT_ONITEM)
6455 lpht->flags &= ~LVHT_NOWHERE;
6457 TRACE("lpht->flags=0x%x\n", lpht->flags);
6458 if (uView == LVS_REPORT && subitem)
6460 INT j;
6462 rcBounds.right = rcBounds.left;
6463 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6465 rcBounds.left = rcBounds.right;
6466 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6467 if (PtInRect(&rcBounds, opt))
6469 lpht->iSubItem = j;
6470 break;
6475 if (select && !(uView == LVS_REPORT &&
6476 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6477 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6479 if (uView == LVS_REPORT)
6481 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6482 UnionRect(&rcBounds, &rcBounds, &rcState);
6484 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6486 return lpht->iItem = iItem;
6490 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6491 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6492 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6493 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6494 their own sort proc. when sending LVM_SORTITEMS.
6496 /* Platform SDK:
6497 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6499 LVS_SORTXXX must be specified,
6500 LVS_OWNERDRAW is not set,
6501 <item>.pszText is not LPSTR_TEXTCALLBACK.
6503 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6504 are sorted based on item text..."
6506 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6508 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
6509 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
6510 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6512 /* if we're sorting descending, negate the return value */
6513 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6516 /***
6517 * DESCRIPTION:
6518 * Inserts a new item in the listview control.
6520 * PARAMETER(S):
6521 * [I] infoPtr : valid pointer to the listview structure
6522 * [I] lpLVItem : item information
6523 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6525 * RETURN:
6526 * SUCCESS : new item index
6527 * FAILURE : -1
6529 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6531 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6532 INT nItem;
6533 HDPA hdpaSubItems;
6534 NMLISTVIEW nmlv;
6535 ITEM_INFO *lpItem;
6536 BOOL is_sorted, has_changed;
6537 LVITEMW item;
6538 HWND hwndSelf = infoPtr->hwndSelf;
6540 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6542 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6544 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6545 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6547 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6549 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6551 /* insert item in listview control data structure */
6552 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6553 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6555 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6556 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6558 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6560 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6561 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6562 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6563 if (nItem == -1) goto fail;
6564 infoPtr->nItemCount++;
6566 /* shift indices first so they don't get tangled */
6567 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6569 /* set the item attributes */
6570 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6572 /* full size structure expected - _WIN32IE >= 0x560 */
6573 item = *lpLVItem;
6575 else if (lpLVItem->mask & LVIF_INDENT)
6577 /* indent member expected - _WIN32IE >= 0x300 */
6578 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6580 else
6582 /* minimal structure expected */
6583 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6585 item.iItem = nItem;
6586 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6588 item.mask |= LVIF_STATE;
6589 item.stateMask |= LVIS_STATEIMAGEMASK;
6590 item.state &= ~LVIS_STATEIMAGEMASK;
6591 item.state |= INDEXTOSTATEIMAGEMASK(1);
6593 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6595 /* if we're sorted, sort the list, and update the index */
6596 if (is_sorted)
6598 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6599 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6600 assert(nItem != -1);
6603 /* make room for the position, if we are in the right mode */
6604 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6606 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6607 goto undo;
6608 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6610 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6611 goto undo;
6615 /* send LVN_INSERTITEM notification */
6616 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6617 nmlv.iItem = nItem;
6618 nmlv.lParam = lpItem->lParam;
6619 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6620 if (!IsWindow(hwndSelf))
6621 return -1;
6623 /* align items (set position of each item) */
6624 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6626 POINT pt;
6628 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6629 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6630 else
6631 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6633 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6636 /* now is the invalidation fun */
6637 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6638 return nItem;
6640 undo:
6641 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6642 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6643 infoPtr->nItemCount--;
6644 fail:
6645 DPA_DeletePtr(hdpaSubItems, 0);
6646 DPA_Destroy (hdpaSubItems);
6647 Free (lpItem);
6648 return -1;
6651 /***
6652 * DESCRIPTION:
6653 * Redraws a range of items.
6655 * PARAMETER(S):
6656 * [I] infoPtr : valid pointer to the listview structure
6657 * [I] nFirst : first item
6658 * [I] nLast : last item
6660 * RETURN:
6661 * SUCCESS : TRUE
6662 * FAILURE : FALSE
6664 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6666 INT i;
6668 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6669 max(nFirst, nLast) >= infoPtr->nItemCount)
6670 return FALSE;
6672 for (i = nFirst; i <= nLast; i++)
6673 LISTVIEW_InvalidateItem(infoPtr, i);
6675 return TRUE;
6678 /***
6679 * DESCRIPTION:
6680 * Scroll the content of a listview.
6682 * PARAMETER(S):
6683 * [I] infoPtr : valid pointer to the listview structure
6684 * [I] dx : horizontal scroll amount in pixels
6685 * [I] dy : vertical scroll amount in pixels
6687 * RETURN:
6688 * SUCCESS : TRUE
6689 * FAILURE : FALSE
6691 * COMMENTS:
6692 * If the control is in report mode (LVS_REPORT) the control can
6693 * be scrolled only in line increments. "dy" will be rounded to the
6694 * nearest number of pixels that are a whole line. Ex: if line height
6695 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6696 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6698 * For: (per experimentation with native control and CSpy ListView)
6699 * LVS_ICON dy=1 = 1 pixel (vertical only)
6700 * dx ignored
6701 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6702 * dx ignored
6703 * LVS_LIST dx=1 = 1 column (horizontal only)
6704 * but will only scroll 1 column per message
6705 * no matter what the value.
6706 * dy must be 0 or FALSE returned.
6707 * LVS_REPORT dx=1 = 1 pixel
6708 * dy= see above
6711 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6713 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6714 case LVS_REPORT:
6715 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6716 dy /= infoPtr->nItemHeight;
6717 break;
6718 case LVS_LIST:
6719 if (dy != 0) return FALSE;
6720 break;
6721 default: /* icon */
6722 dx = 0;
6723 break;
6726 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6727 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6729 return TRUE;
6732 /***
6733 * DESCRIPTION:
6734 * Sets the background color.
6736 * PARAMETER(S):
6737 * [I] infoPtr : valid pointer to the listview structure
6738 * [I] clrBk : background color
6740 * RETURN:
6741 * SUCCESS : TRUE
6742 * FAILURE : FALSE
6744 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6746 TRACE("(clrBk=%x)\n", clrBk);
6748 if(infoPtr->clrBk != clrBk) {
6749 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6750 infoPtr->clrBk = clrBk;
6751 if (clrBk == CLR_NONE)
6752 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6753 else
6754 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6755 LISTVIEW_InvalidateList(infoPtr);
6758 return TRUE;
6761 /* LISTVIEW_SetBkImage */
6763 /*** Helper for {Insert,Set}ColumnT *only* */
6764 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6765 const LVCOLUMNW *lpColumn, BOOL isW)
6767 if (lpColumn->mask & LVCF_FMT)
6769 /* format member is valid */
6770 lphdi->mask |= HDI_FORMAT;
6772 /* set text alignment (leftmost column must be left-aligned) */
6773 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6774 lphdi->fmt |= HDF_LEFT;
6775 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6776 lphdi->fmt |= HDF_RIGHT;
6777 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6778 lphdi->fmt |= HDF_CENTER;
6780 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6781 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6783 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6785 lphdi->fmt |= HDF_IMAGE;
6786 lphdi->iImage = I_IMAGECALLBACK;
6790 if (lpColumn->mask & LVCF_WIDTH)
6792 lphdi->mask |= HDI_WIDTH;
6793 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6795 /* make it fill the remainder of the controls width */
6796 RECT rcHeader;
6797 INT item_index;
6799 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6801 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6802 lphdi->cxy += rcHeader.right - rcHeader.left;
6805 /* retrieve the layout of the header */
6806 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6807 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6809 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6811 else
6812 lphdi->cxy = lpColumn->cx;
6815 if (lpColumn->mask & LVCF_TEXT)
6817 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6818 lphdi->fmt |= HDF_STRING;
6819 lphdi->pszText = lpColumn->pszText;
6820 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6823 if (lpColumn->mask & LVCF_IMAGE)
6825 lphdi->mask |= HDI_IMAGE;
6826 lphdi->iImage = lpColumn->iImage;
6829 if (lpColumn->mask & LVCF_ORDER)
6831 lphdi->mask |= HDI_ORDER;
6832 lphdi->iOrder = lpColumn->iOrder;
6837 /***
6838 * DESCRIPTION:
6839 * Inserts a new column.
6841 * PARAMETER(S):
6842 * [I] infoPtr : valid pointer to the listview structure
6843 * [I] nColumn : column index
6844 * [I] lpColumn : column information
6845 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6847 * RETURN:
6848 * SUCCESS : new column index
6849 * FAILURE : -1
6851 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6852 const LVCOLUMNW *lpColumn, BOOL isW)
6854 COLUMN_INFO *lpColumnInfo;
6855 INT nNewColumn;
6856 HDITEMW hdi;
6858 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6860 if (!lpColumn || nColumn < 0) return -1;
6861 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6863 ZeroMemory(&hdi, sizeof(HDITEMW));
6864 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6867 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6868 * (can be seen in SPY) otherwise column never gets added.
6870 if (!(lpColumn->mask & LVCF_WIDTH)) {
6871 hdi.mask |= HDI_WIDTH;
6872 hdi.cxy = 10;
6876 * when the iSubItem is available Windows copies it to the header lParam. It seems
6877 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6879 if (lpColumn->mask & LVCF_SUBITEM)
6881 hdi.mask |= HDI_LPARAM;
6882 hdi.lParam = lpColumn->iSubItem;
6885 /* insert item in header control */
6886 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6887 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6888 (WPARAM)nColumn, (LPARAM)&hdi);
6889 if (nNewColumn == -1) return -1;
6890 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6892 /* create our own column info */
6893 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6894 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6896 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6897 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6899 /* now we have to actually adjust the data */
6900 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6902 SUBITEM_INFO *lpSubItem;
6903 HDPA hdpaSubItems;
6904 INT nItem, i;
6906 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6908 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
6909 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6911 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
6912 if (lpSubItem->iSubItem >= nNewColumn)
6913 lpSubItem->iSubItem++;
6918 /* make space for the new column */
6919 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6920 LISTVIEW_UpdateItemSize(infoPtr);
6922 return nNewColumn;
6924 fail:
6925 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6926 if (lpColumnInfo)
6928 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6929 Free(lpColumnInfo);
6931 return -1;
6934 /***
6935 * DESCRIPTION:
6936 * Sets the attributes of a header item.
6938 * PARAMETER(S):
6939 * [I] infoPtr : valid pointer to the listview structure
6940 * [I] nColumn : column index
6941 * [I] lpColumn : column attributes
6942 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6944 * RETURN:
6945 * SUCCESS : TRUE
6946 * FAILURE : FALSE
6948 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6949 const LVCOLUMNW *lpColumn, BOOL isW)
6951 HDITEMW hdi, hdiget;
6952 BOOL bResult;
6954 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6956 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6958 ZeroMemory(&hdi, sizeof(HDITEMW));
6959 if (lpColumn->mask & LVCF_FMT)
6961 hdi.mask |= HDI_FORMAT;
6962 hdiget.mask = HDI_FORMAT;
6963 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6964 hdi.fmt = hdiget.fmt & HDF_STRING;
6966 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6968 /* set header item attributes */
6969 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6970 if (!bResult) return FALSE;
6972 if (lpColumn->mask & LVCF_FMT)
6974 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6975 int oldFmt = lpColumnInfo->fmt;
6977 lpColumnInfo->fmt = lpColumn->fmt;
6978 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6980 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6981 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6985 return TRUE;
6988 /***
6989 * DESCRIPTION:
6990 * Sets the column order array
6992 * PARAMETERS:
6993 * [I] infoPtr : valid pointer to the listview structure
6994 * [I] iCount : number of elements in column order array
6995 * [I] lpiArray : pointer to column order array
6997 * RETURN:
6998 * SUCCESS : TRUE
6999 * FAILURE : FALSE
7001 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7003 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7005 if (!lpiArray)
7006 return FALSE;
7008 return TRUE;
7012 /***
7013 * DESCRIPTION:
7014 * Sets the width of a column
7016 * PARAMETERS:
7017 * [I] infoPtr : valid pointer to the listview structure
7018 * [I] nColumn : column index
7019 * [I] cx : column width
7021 * RETURN:
7022 * SUCCESS : TRUE
7023 * FAILURE : FALSE
7025 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7027 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7028 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7029 INT max_cx = 0;
7030 HDITEMW hdi;
7032 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7034 /* set column width only if in report or list mode */
7035 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7037 /* take care of invalid cx values */
7038 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7039 else if (uView == LVS_LIST && cx < 1) return FALSE;
7041 /* resize all columns if in LVS_LIST mode */
7042 if(uView == LVS_LIST)
7044 infoPtr->nItemWidth = cx;
7045 LISTVIEW_InvalidateList(infoPtr);
7046 return TRUE;
7049 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7051 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7053 INT nLabelWidth;
7054 LVITEMW lvItem;
7056 lvItem.mask = LVIF_TEXT;
7057 lvItem.iItem = 0;
7058 lvItem.iSubItem = nColumn;
7059 lvItem.pszText = szDispText;
7060 lvItem.cchTextMax = DISP_TEXT_SIZE;
7061 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7063 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7064 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7065 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7067 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7068 max_cx += infoPtr->iconSize.cx;
7069 max_cx += TRAILING_LABEL_PADDING;
7072 /* autosize based on listview items width */
7073 if(cx == LVSCW_AUTOSIZE)
7074 cx = max_cx;
7075 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7077 /* if iCol is the last column make it fill the remainder of the controls width */
7078 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7080 RECT rcHeader;
7081 POINT Origin;
7083 LISTVIEW_GetOrigin(infoPtr, &Origin);
7084 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7086 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7088 else
7090 /* Despite what the MS docs say, if this is not the last
7091 column, then MS resizes the column to the width of the
7092 largest text string in the column, including headers
7093 and items. This is different from LVSCW_AUTOSIZE in that
7094 LVSCW_AUTOSIZE ignores the header string length. */
7095 cx = 0;
7097 /* retrieve header text */
7098 hdi.mask = HDI_TEXT;
7099 hdi.cchTextMax = DISP_TEXT_SIZE;
7100 hdi.pszText = szDispText;
7101 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7103 HDC hdc = GetDC(infoPtr->hwndSelf);
7104 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7105 SIZE size;
7107 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7108 cx = size.cx + TRAILING_HEADER_PADDING;
7109 /* FIXME: Take into account the header image, if one is present */
7110 SelectObject(hdc, old_font);
7111 ReleaseDC(infoPtr->hwndSelf, hdc);
7113 cx = max (cx, max_cx);
7117 if (cx < 0) return FALSE;
7119 /* call header to update the column change */
7120 hdi.mask = HDI_WIDTH;
7121 hdi.cxy = cx;
7122 TRACE("hdi.cxy=%d\n", hdi.cxy);
7123 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7126 /***
7127 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7130 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7132 HDC hdc_wnd, hdc;
7133 HBITMAP hbm_im, hbm_mask, hbm_orig;
7134 RECT rc;
7135 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7136 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7137 HIMAGELIST himl;
7139 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7140 ILC_COLOR | ILC_MASK, 2, 2);
7141 hdc_wnd = GetDC(infoPtr->hwndSelf);
7142 hdc = CreateCompatibleDC(hdc_wnd);
7143 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7144 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7145 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7147 rc.left = rc.top = 0;
7148 rc.right = GetSystemMetrics(SM_CXSMICON);
7149 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7151 hbm_orig = SelectObject(hdc, hbm_mask);
7152 FillRect(hdc, &rc, hbr_white);
7153 InflateRect(&rc, -3, -3);
7154 FillRect(hdc, &rc, hbr_black);
7156 SelectObject(hdc, hbm_im);
7157 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7158 SelectObject(hdc, hbm_orig);
7159 ImageList_Add(himl, hbm_im, hbm_mask);
7161 SelectObject(hdc, hbm_im);
7162 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7163 SelectObject(hdc, hbm_orig);
7164 ImageList_Add(himl, hbm_im, hbm_mask);
7166 DeleteObject(hbm_mask);
7167 DeleteObject(hbm_im);
7168 DeleteDC(hdc);
7170 return himl;
7173 /***
7174 * DESCRIPTION:
7175 * Sets the extended listview style.
7177 * PARAMETERS:
7178 * [I] infoPtr : valid pointer to the listview structure
7179 * [I] dwMask : mask
7180 * [I] dwStyle : style
7182 * RETURN:
7183 * SUCCESS : previous style
7184 * FAILURE : 0
7186 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7188 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7190 /* set new style */
7191 if (dwMask)
7192 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7193 else
7194 infoPtr->dwLvExStyle = dwExStyle;
7196 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7198 HIMAGELIST himl = 0;
7199 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7201 LVITEMW item;
7202 item.mask = LVIF_STATE;
7203 item.stateMask = LVIS_STATEIMAGEMASK;
7204 item.state = INDEXTOSTATEIMAGEMASK(1);
7205 LISTVIEW_SetItemState(infoPtr, -1, &item);
7207 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7209 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7212 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7214 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7215 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7216 dwStyle |= HDS_DRAGDROP;
7217 else
7218 dwStyle &= ~HDS_DRAGDROP;
7219 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7222 /* GRIDLINES adds decoration at top so changes sizes */
7223 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7225 LISTVIEW_UpdateSize(infoPtr);
7229 LISTVIEW_InvalidateList(infoPtr);
7230 return dwOldExStyle;
7233 /***
7234 * DESCRIPTION:
7235 * Sets the new hot cursor used during hot tracking and hover selection.
7237 * PARAMETER(S):
7238 * [I] infoPtr : valid pointer to the listview structure
7239 * [I] hCursor : the new hot cursor handle
7241 * RETURN:
7242 * Returns the previous hot cursor
7244 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7246 HCURSOR oldCursor = infoPtr->hHotCursor;
7248 infoPtr->hHotCursor = hCursor;
7250 return oldCursor;
7254 /***
7255 * DESCRIPTION:
7256 * Sets the hot item index.
7258 * PARAMETERS:
7259 * [I] infoPtr : valid pointer to the listview structure
7260 * [I] iIndex : index
7262 * RETURN:
7263 * SUCCESS : previous hot item index
7264 * FAILURE : -1 (no hot item)
7266 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7268 INT iOldIndex = infoPtr->nHotItem;
7270 infoPtr->nHotItem = iIndex;
7272 return iOldIndex;
7276 /***
7277 * DESCRIPTION:
7278 * Sets the amount of time the cursor must hover over an item before it is selected.
7280 * PARAMETER(S):
7281 * [I] infoPtr : valid pointer to the listview structure
7282 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7284 * RETURN:
7285 * Returns the previous hover time
7287 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7289 DWORD oldHoverTime = infoPtr->dwHoverTime;
7291 infoPtr->dwHoverTime = dwHoverTime;
7293 return oldHoverTime;
7296 /***
7297 * DESCRIPTION:
7298 * Sets spacing for icons of LVS_ICON style.
7300 * PARAMETER(S):
7301 * [I] infoPtr : valid pointer to the listview structure
7302 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7303 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7305 * RETURN:
7306 * MAKELONG(oldcx, oldcy)
7308 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7310 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7311 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7313 TRACE("requested=(%d,%d)\n", cx, cy);
7315 /* this is supported only for LVS_ICON style */
7316 if (uView != LVS_ICON) return oldspacing;
7318 /* set to defaults, if instructed to */
7319 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7320 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7322 /* if 0 then compute width
7323 * FIXME: Should scan each item and determine max width of
7324 * icon or label, then make that the width */
7325 if (cx == 0)
7326 cx = infoPtr->iconSpacing.cx;
7328 /* if 0 then compute height */
7329 if (cy == 0)
7330 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7331 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7334 infoPtr->iconSpacing.cx = cx;
7335 infoPtr->iconSpacing.cy = cy;
7337 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7338 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7339 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7340 infoPtr->ntmHeight);
7342 /* these depend on the iconSpacing */
7343 LISTVIEW_UpdateItemSize(infoPtr);
7345 return oldspacing;
7348 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7350 INT cx, cy;
7352 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7354 size->cx = cx;
7355 size->cy = cy;
7357 else
7359 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7360 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7364 /***
7365 * DESCRIPTION:
7366 * Sets image lists.
7368 * PARAMETER(S):
7369 * [I] infoPtr : valid pointer to the listview structure
7370 * [I] nType : image list type
7371 * [I] himl : image list handle
7373 * RETURN:
7374 * SUCCESS : old image list
7375 * FAILURE : NULL
7377 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7379 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7380 INT oldHeight = infoPtr->nItemHeight;
7381 HIMAGELIST himlOld = 0;
7383 TRACE("(nType=%d, himl=%p\n", nType, himl);
7385 switch (nType)
7387 case LVSIL_NORMAL:
7388 himlOld = infoPtr->himlNormal;
7389 infoPtr->himlNormal = himl;
7390 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7391 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7392 break;
7394 case LVSIL_SMALL:
7395 himlOld = infoPtr->himlSmall;
7396 infoPtr->himlSmall = himl;
7397 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7398 break;
7400 case LVSIL_STATE:
7401 himlOld = infoPtr->himlState;
7402 infoPtr->himlState = himl;
7403 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7404 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7405 break;
7407 default:
7408 ERR("Unknown icon type=%d\n", nType);
7409 return NULL;
7412 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7413 if (infoPtr->nItemHeight != oldHeight)
7414 LISTVIEW_UpdateScroll(infoPtr);
7416 return himlOld;
7419 /***
7420 * DESCRIPTION:
7421 * Preallocates memory (does *not* set the actual count of items !)
7423 * PARAMETER(S):
7424 * [I] infoPtr : valid pointer to the listview structure
7425 * [I] nItems : item count (projected number of items to allocate)
7426 * [I] dwFlags : update flags
7428 * RETURN:
7429 * SUCCESS : TRUE
7430 * FAILURE : FALSE
7432 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7434 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7436 if (infoPtr->dwStyle & LVS_OWNERDATA)
7438 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7439 INT nOldCount = infoPtr->nItemCount;
7441 if (nItems < nOldCount)
7443 RANGE range = { nItems, nOldCount };
7444 ranges_del(infoPtr->selectionRanges, range);
7445 if (infoPtr->nFocusedItem >= nItems)
7447 infoPtr->nFocusedItem = -1;
7448 SetRectEmpty(&infoPtr->rcFocus);
7452 infoPtr->nItemCount = nItems;
7453 LISTVIEW_UpdateScroll(infoPtr);
7455 /* the flags are valid only in ownerdata report and list modes */
7456 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7458 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7459 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7461 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7462 LISTVIEW_InvalidateList(infoPtr);
7463 else
7465 INT nFrom, nTo;
7466 POINT Origin;
7467 RECT rcErase;
7469 LISTVIEW_GetOrigin(infoPtr, &Origin);
7470 nFrom = min(nOldCount, nItems);
7471 nTo = max(nOldCount, nItems);
7473 if (uView == LVS_REPORT)
7475 rcErase.left = 0;
7476 rcErase.top = nFrom * infoPtr->nItemHeight;
7477 rcErase.right = infoPtr->nItemWidth;
7478 rcErase.bottom = nTo * infoPtr->nItemHeight;
7479 OffsetRect(&rcErase, Origin.x, Origin.y);
7480 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7481 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7483 else /* LVS_LIST */
7485 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7487 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7488 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7489 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7490 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7491 OffsetRect(&rcErase, Origin.x, Origin.y);
7492 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7493 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7495 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7496 rcErase.top = 0;
7497 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7498 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7499 OffsetRect(&rcErase, Origin.x, Origin.y);
7500 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7501 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7505 else
7507 /* According to MSDN for non-LVS_OWNERDATA this is just
7508 * a performance issue. The control allocates its internal
7509 * data structures for the number of items specified. It
7510 * cuts down on the number of memory allocations. Therefore
7511 * we will just issue a WARN here
7513 WARN("for non-ownerdata performance option not implemented.\n");
7516 return TRUE;
7519 /***
7520 * DESCRIPTION:
7521 * Sets the position of an item.
7523 * PARAMETER(S):
7524 * [I] infoPtr : valid pointer to the listview structure
7525 * [I] nItem : item index
7526 * [I] pt : coordinate
7528 * RETURN:
7529 * SUCCESS : TRUE
7530 * FAILURE : FALSE
7532 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7534 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7535 POINT Origin;
7537 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7539 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7540 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7542 LISTVIEW_GetOrigin(infoPtr, &Origin);
7544 /* This point value seems to be an undocumented feature.
7545 * The best guess is that it means either at the origin,
7546 * or at true beginning of the list. I will assume the origin. */
7547 if ((pt.x == -1) && (pt.y == -1))
7548 pt = Origin;
7550 if (uView == LVS_ICON)
7552 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7553 pt.y -= ICON_TOP_PADDING;
7555 pt.x -= Origin.x;
7556 pt.y -= Origin.y;
7558 infoPtr->bAutoarrange = FALSE;
7560 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7563 /***
7564 * DESCRIPTION:
7565 * Sets the state of one or many items.
7567 * PARAMETER(S):
7568 * [I] infoPtr : valid pointer to the listview structure
7569 * [I] nItem : item index
7570 * [I] lpLVItem : item or subitem info
7572 * RETURN:
7573 * SUCCESS : TRUE
7574 * FAILURE : FALSE
7576 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7578 BOOL bResult = TRUE;
7579 LVITEMW lvItem;
7581 lvItem.iItem = nItem;
7582 lvItem.iSubItem = 0;
7583 lvItem.mask = LVIF_STATE;
7584 lvItem.state = lpLVItem->state;
7585 lvItem.stateMask = lpLVItem->stateMask;
7586 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7588 if (nItem == -1)
7590 /* apply to all items */
7591 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7592 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7594 else
7595 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7598 * Update selection mark
7600 * Investigation on windows 2k showed that selection mark was updated
7601 * whenever a new selection was made, but if the selected item was
7602 * unselected it was not updated.
7604 * we are probably still not 100% accurate, but this at least sets the
7605 * proper selection mark when it is needed
7608 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7609 (infoPtr->nSelectionMark == -1))
7611 int i;
7612 for (i = 0; i < infoPtr->nItemCount; i++)
7614 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7616 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7618 infoPtr->nSelectionMark = i;
7619 break;
7622 else if (ranges_contain(infoPtr->selectionRanges, i))
7624 infoPtr->nSelectionMark = i;
7625 break;
7630 return bResult;
7633 /***
7634 * DESCRIPTION:
7635 * Sets the text of an item or subitem.
7637 * PARAMETER(S):
7638 * [I] hwnd : window handle
7639 * [I] nItem : item index
7640 * [I] lpLVItem : item or subitem info
7641 * [I] isW : TRUE if input is Unicode
7643 * RETURN:
7644 * SUCCESS : TRUE
7645 * FAILURE : FALSE
7647 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7649 LVITEMW lvItem;
7651 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7653 lvItem.iItem = nItem;
7654 lvItem.iSubItem = lpLVItem->iSubItem;
7655 lvItem.mask = LVIF_TEXT;
7656 lvItem.pszText = lpLVItem->pszText;
7657 lvItem.cchTextMax = lpLVItem->cchTextMax;
7659 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7661 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7664 /***
7665 * DESCRIPTION:
7666 * Set item index that marks the start of a multiple selection.
7668 * PARAMETER(S):
7669 * [I] infoPtr : valid pointer to the listview structure
7670 * [I] nIndex : index
7672 * RETURN:
7673 * Index number or -1 if there is no selection mark.
7675 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7677 INT nOldIndex = infoPtr->nSelectionMark;
7679 TRACE("(nIndex=%d)\n", nIndex);
7681 infoPtr->nSelectionMark = nIndex;
7683 return nOldIndex;
7686 /***
7687 * DESCRIPTION:
7688 * Sets the text background color.
7690 * PARAMETER(S):
7691 * [I] infoPtr : valid pointer to the listview structure
7692 * [I] clrTextBk : text background color
7694 * RETURN:
7695 * SUCCESS : TRUE
7696 * FAILURE : FALSE
7698 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7700 TRACE("(clrTextBk=%x)\n", clrTextBk);
7702 if (infoPtr->clrTextBk != clrTextBk)
7704 infoPtr->clrTextBk = clrTextBk;
7705 LISTVIEW_InvalidateList(infoPtr);
7708 return TRUE;
7711 /***
7712 * DESCRIPTION:
7713 * Sets the text foreground color.
7715 * PARAMETER(S):
7716 * [I] infoPtr : valid pointer to the listview structure
7717 * [I] clrText : text color
7719 * RETURN:
7720 * SUCCESS : TRUE
7721 * FAILURE : FALSE
7723 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7725 TRACE("(clrText=%x)\n", clrText);
7727 if (infoPtr->clrText != clrText)
7729 infoPtr->clrText = clrText;
7730 LISTVIEW_InvalidateList(infoPtr);
7733 return TRUE;
7736 /***
7737 * DESCRIPTION:
7738 * Determines which listview item is located at the specified position.
7740 * PARAMETER(S):
7741 * [I] infoPtr : valid pointer to the listview structure
7742 * [I] hwndNewToolTip : handle to new ToolTip
7744 * RETURN:
7745 * old tool tip
7747 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7749 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7750 infoPtr->hwndToolTip = hwndNewToolTip;
7751 return hwndOldToolTip;
7755 * DESCRIPTION:
7756 * sets the Unicode character format flag for the control
7757 * PARAMETER(S):
7758 * [I] infoPtr :valid pointer to the listview structure
7759 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7761 * RETURN:
7762 * Old Unicode Format
7764 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7766 BOOL rc = infoPtr->notifyFormat;
7767 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7768 return rc;
7771 /* LISTVIEW_SetWorkAreas */
7773 /***
7774 * DESCRIPTION:
7775 * Callback internally used by LISTVIEW_SortItems()
7777 * PARAMETER(S):
7778 * [I] first : pointer to first ITEM_INFO to compare
7779 * [I] second : pointer to second ITEM_INFO to compare
7780 * [I] lParam : HWND of control
7782 * RETURN:
7783 * if first comes before second : negative
7784 * if first comes after second : positive
7785 * if first and second are equivalent : zero
7787 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7789 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7790 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7791 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7793 /* Forward the call to the client defined callback */
7794 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7797 /***
7798 * DESCRIPTION:
7799 * Sorts the listview items.
7801 * PARAMETER(S):
7802 * [I] infoPtr : valid pointer to the listview structure
7803 * [I] pfnCompare : application-defined value
7804 * [I] lParamSort : pointer to comparison callback
7806 * RETURN:
7807 * SUCCESS : TRUE
7808 * FAILURE : FALSE
7810 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7812 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7813 HDPA hdpaSubItems;
7814 ITEM_INFO *lpItem;
7815 LPVOID selectionMarkItem;
7816 LVITEMW item;
7817 int i;
7819 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7821 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7823 if (!pfnCompare) return FALSE;
7824 if (!infoPtr->hdpaItems) return FALSE;
7826 /* if there are 0 or 1 items, there is no need to sort */
7827 if (infoPtr->nItemCount < 2) return TRUE;
7829 if (infoPtr->nFocusedItem >= 0)
7831 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7832 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7833 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7835 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7836 /* clear the lpItem->state for non-selected ones */
7837 /* remove the selection ranges */
7839 infoPtr->pfnCompare = pfnCompare;
7840 infoPtr->lParamSort = lParamSort;
7841 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7843 /* Adjust selections and indices so that they are the way they should
7844 * be after the sort (otherwise, the list items move around, but
7845 * whatever is at the item's previous original position will be
7846 * selected instead)
7848 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7849 for (i=0; i < infoPtr->nItemCount; i++)
7851 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7852 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7854 if (lpItem->state & LVIS_SELECTED)
7856 item.state = LVIS_SELECTED;
7857 item.stateMask = LVIS_SELECTED;
7858 LISTVIEW_SetItemState(infoPtr, i, &item);
7860 if (lpItem->state & LVIS_FOCUSED)
7862 infoPtr->nFocusedItem = i;
7863 lpItem->state &= ~LVIS_FOCUSED;
7866 if (selectionMarkItem != NULL)
7867 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7868 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7870 /* refresh the display */
7871 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7872 LISTVIEW_InvalidateList(infoPtr);
7874 return TRUE;
7877 /***
7878 * DESCRIPTION:
7879 * Update theme handle after a theme change.
7881 * PARAMETER(S):
7882 * [I] infoPtr : valid pointer to the listview structure
7884 * RETURN:
7885 * SUCCESS : 0
7886 * FAILURE : something else
7888 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7890 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7891 CloseThemeData(theme);
7892 OpenThemeData(infoPtr->hwndSelf, themeClass);
7893 return 0;
7896 /***
7897 * DESCRIPTION:
7898 * Updates an items or rearranges the listview control.
7900 * PARAMETER(S):
7901 * [I] infoPtr : valid pointer to the listview structure
7902 * [I] nItem : item index
7904 * RETURN:
7905 * SUCCESS : TRUE
7906 * FAILURE : FALSE
7908 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7910 TRACE("(nItem=%d)\n", nItem);
7912 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7914 /* rearrange with default alignment style */
7915 if (is_autoarrange(infoPtr))
7916 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7917 else
7918 LISTVIEW_InvalidateItem(infoPtr, nItem);
7920 return TRUE;
7923 /***
7924 * DESCRIPTION:
7925 * Draw the track line at the place defined in the infoPtr structure.
7926 * The line is drawn with a XOR pen so drawing the line for the second time
7927 * in the same place erases the line.
7929 * PARAMETER(S):
7930 * [I] infoPtr : valid pointer to the listview structure
7932 * RETURN:
7933 * SUCCESS : TRUE
7934 * FAILURE : FALSE
7936 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7938 HPEN hOldPen;
7939 HDC hdc;
7940 INT oldROP;
7942 if (infoPtr->xTrackLine == -1)
7943 return FALSE;
7945 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7946 return FALSE;
7947 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7948 oldROP = SetROP2(hdc, R2_XORPEN);
7949 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7950 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7951 SetROP2(hdc, oldROP);
7952 SelectObject(hdc, hOldPen);
7953 ReleaseDC(infoPtr->hwndSelf, hdc);
7954 return TRUE;
7957 /***
7958 * DESCRIPTION:
7959 * Called when an edit control should be displayed. This function is called after
7960 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7962 * PARAMETER(S):
7963 * [I] hwnd : Handle to the listview
7964 * [I] uMsg : WM_TIMER (ignored)
7965 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7966 * [I] dwTimer : The elapsed time (ignored)
7968 * RETURN:
7969 * None.
7971 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7973 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7974 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7976 KillTimer(hwnd, idEvent);
7977 editItem->fEnabled = FALSE;
7978 /* check if the item is still selected */
7979 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7980 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7983 /***
7984 * DESCRIPTION:
7985 * Creates the listview control - the WM_NCCREATE phase.
7987 * PARAMETER(S):
7988 * [I] hwnd : window handle
7989 * [I] lpcs : the create parameters
7991 * RETURN:
7992 * Success: TRUE
7993 * Failure: FALSE
7995 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7997 LISTVIEW_INFO *infoPtr;
7998 LOGFONTW logFont;
8000 TRACE("(lpcs=%p)\n", lpcs);
8002 /* initialize info pointer */
8003 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8004 if (!infoPtr) return FALSE;
8006 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8008 infoPtr->hwndSelf = hwnd;
8009 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8010 /* determine the type of structures to use */
8011 infoPtr->hwndNotify = lpcs->hwndParent;
8012 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8014 /* initialize color information */
8015 infoPtr->clrBk = CLR_NONE;
8016 infoPtr->clrText = CLR_DEFAULT;
8017 infoPtr->clrTextBk = CLR_DEFAULT;
8018 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8020 /* set default values */
8021 infoPtr->nFocusedItem = -1;
8022 infoPtr->nSelectionMark = -1;
8023 infoPtr->nHotItem = -1;
8024 infoPtr->bRedraw = TRUE;
8025 infoPtr->bNoItemMetrics = TRUE;
8026 infoPtr->bDoChangeNotify = TRUE;
8027 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8028 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8029 infoPtr->nEditLabelItem = -1;
8030 infoPtr->dwHoverTime = -1; /* default system hover time */
8031 infoPtr->nMeasureItemHeight = 0;
8032 infoPtr->xTrackLine = -1; /* no track line */
8033 infoPtr->itemEdit.fEnabled = FALSE;
8035 /* get default font (icon title) */
8036 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8037 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8038 infoPtr->hFont = infoPtr->hDefaultFont;
8039 LISTVIEW_SaveTextMetrics(infoPtr);
8041 /* allocate memory for the data structure */
8042 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8043 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8044 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8045 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8046 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8047 return TRUE;
8049 fail:
8050 DestroyWindow(infoPtr->hwndHeader);
8051 ranges_destroy(infoPtr->selectionRanges);
8052 DPA_Destroy(infoPtr->hdpaItems);
8053 DPA_Destroy(infoPtr->hdpaPosX);
8054 DPA_Destroy(infoPtr->hdpaPosY);
8055 DPA_Destroy(infoPtr->hdpaColumns);
8056 Free(infoPtr);
8057 return FALSE;
8060 /***
8061 * DESCRIPTION:
8062 * Creates the listview control - the WM_CREATE phase. Most of the data is
8063 * already set up in LISTVIEW_NCCreate
8065 * PARAMETER(S):
8066 * [I] hwnd : window handle
8067 * [I] lpcs : the create parameters
8069 * RETURN:
8070 * Success: 0
8071 * Failure: -1
8073 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8075 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8076 UINT uView = lpcs->style & LVS_TYPEMASK;
8077 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
8079 TRACE("(lpcs=%p)\n", lpcs);
8081 infoPtr->dwStyle = lpcs->style;
8082 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8083 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8085 /* setup creation flags */
8086 dFlags |= (LVS_NOSORTHEADER & lpcs->style) ? 0 : HDS_BUTTONS;
8087 dFlags |= (LVS_NOCOLUMNHEADER & lpcs->style) ? HDS_HIDDEN : 0;
8089 /* create header */
8090 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
8091 0, 0, 0, 0, hwnd, NULL,
8092 lpcs->hInstance, NULL);
8093 if (!infoPtr->hwndHeader) return -1;
8095 /* set header unicode format */
8096 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
8098 /* set header font */
8099 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8101 /* init item size to avoid division by 0 */
8102 LISTVIEW_UpdateItemSize (infoPtr);
8104 if (uView == LVS_REPORT)
8106 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8108 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8110 LISTVIEW_UpdateSize(infoPtr);
8111 LISTVIEW_UpdateScroll(infoPtr);
8114 OpenThemeData(hwnd, themeClass);
8116 /* initialize the icon sizes */
8117 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8118 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8119 return 0;
8122 /***
8123 * DESCRIPTION:
8124 * Destroys the listview control.
8126 * PARAMETER(S):
8127 * [I] infoPtr : valid pointer to the listview structure
8129 * RETURN:
8130 * Success: 0
8131 * Failure: -1
8133 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8135 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8136 CloseThemeData(theme);
8137 return 0;
8140 /***
8141 * DESCRIPTION:
8142 * Enables the listview control.
8144 * PARAMETER(S):
8145 * [I] infoPtr : valid pointer to the listview structure
8146 * [I] bEnable : specifies whether to enable or disable the window
8148 * RETURN:
8149 * SUCCESS : TRUE
8150 * FAILURE : FALSE
8152 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8154 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8155 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8156 return TRUE;
8159 /***
8160 * DESCRIPTION:
8161 * Erases the background of the listview control.
8163 * PARAMETER(S):
8164 * [I] infoPtr : valid pointer to the listview structure
8165 * [I] hdc : device context handle
8167 * RETURN:
8168 * SUCCESS : TRUE
8169 * FAILURE : FALSE
8171 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8173 RECT rc;
8175 TRACE("(hdc=%p)\n", hdc);
8177 if (!GetClipBox(hdc, &rc)) return FALSE;
8179 /* for double buffered controls we need to do this during refresh */
8180 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8182 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8186 /***
8187 * DESCRIPTION:
8188 * Helper function for LISTVIEW_[HV]Scroll *only*.
8189 * Performs vertical/horizontal scrolling by a give amount.
8191 * PARAMETER(S):
8192 * [I] infoPtr : valid pointer to the listview structure
8193 * [I] dx : amount of horizontal scroll
8194 * [I] dy : amount of vertical scroll
8196 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8198 /* now we can scroll the list */
8199 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8200 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8201 /* if we have focus, adjust rect */
8202 OffsetRect(&infoPtr->rcFocus, dx, dy);
8203 UpdateWindow(infoPtr->hwndSelf);
8206 /***
8207 * DESCRIPTION:
8208 * Performs vertical scrolling.
8210 * PARAMETER(S):
8211 * [I] infoPtr : valid pointer to the listview structure
8212 * [I] nScrollCode : scroll code
8213 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8214 * [I] hScrollWnd : scrollbar control window handle
8216 * RETURN:
8217 * Zero
8219 * NOTES:
8220 * SB_LINEUP/SB_LINEDOWN:
8221 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8222 * for LVS_REPORT is 1 line
8223 * for LVS_LIST cannot occur
8226 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8227 INT nScrollDiff, HWND hScrollWnd)
8229 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8230 INT nOldScrollPos, nNewScrollPos;
8231 SCROLLINFO scrollInfo;
8232 BOOL is_an_icon;
8234 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8235 debugscrollcode(nScrollCode), nScrollDiff);
8237 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8239 scrollInfo.cbSize = sizeof(SCROLLINFO);
8240 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8242 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8244 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8246 nOldScrollPos = scrollInfo.nPos;
8247 switch (nScrollCode)
8249 case SB_INTERNAL:
8250 break;
8252 case SB_LINEUP:
8253 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8254 break;
8256 case SB_LINEDOWN:
8257 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8258 break;
8260 case SB_PAGEUP:
8261 nScrollDiff = -scrollInfo.nPage;
8262 break;
8264 case SB_PAGEDOWN:
8265 nScrollDiff = scrollInfo.nPage;
8266 break;
8268 case SB_THUMBPOSITION:
8269 case SB_THUMBTRACK:
8270 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8271 break;
8273 default:
8274 nScrollDiff = 0;
8277 /* quit right away if pos isn't changing */
8278 if (nScrollDiff == 0) return 0;
8280 /* calculate new position, and handle overflows */
8281 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8282 if (nScrollDiff > 0) {
8283 if (nNewScrollPos < nOldScrollPos ||
8284 nNewScrollPos > scrollInfo.nMax)
8285 nNewScrollPos = scrollInfo.nMax;
8286 } else {
8287 if (nNewScrollPos > nOldScrollPos ||
8288 nNewScrollPos < scrollInfo.nMin)
8289 nNewScrollPos = scrollInfo.nMin;
8292 /* set the new position, and reread in case it changed */
8293 scrollInfo.fMask = SIF_POS;
8294 scrollInfo.nPos = nNewScrollPos;
8295 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8297 /* carry on only if it really changed */
8298 if (nNewScrollPos == nOldScrollPos) return 0;
8300 /* now adjust to client coordinates */
8301 nScrollDiff = nOldScrollPos - nNewScrollPos;
8302 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8304 /* and scroll the window */
8305 scroll_list(infoPtr, 0, nScrollDiff);
8307 return 0;
8310 /***
8311 * DESCRIPTION:
8312 * Performs horizontal scrolling.
8314 * PARAMETER(S):
8315 * [I] infoPtr : valid pointer to the listview structure
8316 * [I] nScrollCode : scroll code
8317 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8318 * [I] hScrollWnd : scrollbar control window handle
8320 * RETURN:
8321 * Zero
8323 * NOTES:
8324 * SB_LINELEFT/SB_LINERIGHT:
8325 * for LVS_ICON, LVS_SMALLICON 1 pixel
8326 * for LVS_REPORT is 1 pixel
8327 * for LVS_LIST is 1 column --> which is a 1 because the
8328 * scroll is based on columns not pixels
8331 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8332 INT nScrollDiff, HWND hScrollWnd)
8334 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8335 INT nOldScrollPos, nNewScrollPos;
8336 SCROLLINFO scrollInfo;
8338 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8339 debugscrollcode(nScrollCode), nScrollDiff);
8341 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8343 scrollInfo.cbSize = sizeof(SCROLLINFO);
8344 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8346 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8348 nOldScrollPos = scrollInfo.nPos;
8350 switch (nScrollCode)
8352 case SB_INTERNAL:
8353 break;
8355 case SB_LINELEFT:
8356 nScrollDiff = -1;
8357 break;
8359 case SB_LINERIGHT:
8360 nScrollDiff = 1;
8361 break;
8363 case SB_PAGELEFT:
8364 nScrollDiff = -scrollInfo.nPage;
8365 break;
8367 case SB_PAGERIGHT:
8368 nScrollDiff = scrollInfo.nPage;
8369 break;
8371 case SB_THUMBPOSITION:
8372 case SB_THUMBTRACK:
8373 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8374 break;
8376 default:
8377 nScrollDiff = 0;
8380 /* quit right away if pos isn't changing */
8381 if (nScrollDiff == 0) return 0;
8383 /* calculate new position, and handle overflows */
8384 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8385 if (nScrollDiff > 0) {
8386 if (nNewScrollPos < nOldScrollPos ||
8387 nNewScrollPos > scrollInfo.nMax)
8388 nNewScrollPos = scrollInfo.nMax;
8389 } else {
8390 if (nNewScrollPos > nOldScrollPos ||
8391 nNewScrollPos < scrollInfo.nMin)
8392 nNewScrollPos = scrollInfo.nMin;
8395 /* set the new position, and reread in case it changed */
8396 scrollInfo.fMask = SIF_POS;
8397 scrollInfo.nPos = nNewScrollPos;
8398 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8400 /* carry on only if it really changed */
8401 if (nNewScrollPos == nOldScrollPos) return 0;
8403 if(uView == LVS_REPORT)
8404 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8406 /* now adjust to client coordinates */
8407 nScrollDiff = nOldScrollPos - nNewScrollPos;
8408 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8410 /* and scroll the window */
8411 scroll_list(infoPtr, nScrollDiff, 0);
8413 return 0;
8416 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8418 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8419 INT gcWheelDelta = 0;
8420 INT pulScrollLines = 3;
8421 SCROLLINFO scrollInfo;
8423 TRACE("(wheelDelta=%d)\n", wheelDelta);
8425 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8426 gcWheelDelta -= wheelDelta;
8428 scrollInfo.cbSize = sizeof(SCROLLINFO);
8429 scrollInfo.fMask = SIF_POS;
8431 switch(uView)
8433 case LVS_ICON:
8434 case LVS_SMALLICON:
8436 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8437 * should be fixed in the future.
8439 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8440 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8441 break;
8443 case LVS_REPORT:
8444 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8446 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8447 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8448 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8450 break;
8452 case LVS_LIST:
8453 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8454 break;
8456 return 0;
8459 /***
8460 * DESCRIPTION:
8461 * ???
8463 * PARAMETER(S):
8464 * [I] infoPtr : valid pointer to the listview structure
8465 * [I] nVirtualKey : virtual key
8466 * [I] lKeyData : key data
8468 * RETURN:
8469 * Zero
8471 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8473 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8474 HWND hwndSelf = infoPtr->hwndSelf;
8475 INT nItem = -1;
8476 NMLVKEYDOWN nmKeyDown;
8478 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8480 /* send LVN_KEYDOWN notification */
8481 nmKeyDown.wVKey = nVirtualKey;
8482 nmKeyDown.flags = 0;
8483 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8484 if (!IsWindow(hwndSelf))
8485 return 0;
8487 switch (nVirtualKey)
8489 case VK_SPACE:
8490 nItem = infoPtr->nFocusedItem;
8491 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8492 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8493 break;
8495 case VK_RETURN:
8496 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8498 if (!notify(infoPtr, NM_RETURN)) return 0;
8499 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8501 break;
8503 case VK_HOME:
8504 if (infoPtr->nItemCount > 0)
8505 nItem = 0;
8506 break;
8508 case VK_END:
8509 if (infoPtr->nItemCount > 0)
8510 nItem = infoPtr->nItemCount - 1;
8511 break;
8513 case VK_LEFT:
8514 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8515 break;
8517 case VK_UP:
8518 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8519 break;
8521 case VK_RIGHT:
8522 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8523 break;
8525 case VK_DOWN:
8526 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8527 break;
8529 case VK_PRIOR:
8530 if (uView == LVS_REPORT)
8532 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8533 if (infoPtr->nFocusedItem == topidx)
8534 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8535 else
8536 nItem = topidx;
8538 else
8539 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8540 * LISTVIEW_GetCountPerRow(infoPtr);
8541 if(nItem < 0) nItem = 0;
8542 break;
8544 case VK_NEXT:
8545 if (uView == LVS_REPORT)
8547 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8548 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8549 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8550 nItem = infoPtr->nFocusedItem + cnt - 1;
8551 else
8552 nItem = topidx + cnt - 1;
8554 else
8555 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8556 * LISTVIEW_GetCountPerRow(infoPtr);
8557 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8558 break;
8561 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8562 LISTVIEW_KeySelection(infoPtr, nItem);
8564 return 0;
8567 /***
8568 * DESCRIPTION:
8569 * Kills the focus.
8571 * PARAMETER(S):
8572 * [I] infoPtr : valid pointer to the listview structure
8574 * RETURN:
8575 * Zero
8577 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8579 TRACE("()\n");
8581 /* if we did not have the focus, there's nothing to do */
8582 if (!infoPtr->bFocus) return 0;
8584 /* send NM_KILLFOCUS notification */
8585 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8587 /* if we have a focus rectagle, get rid of it */
8588 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8590 /* set window focus flag */
8591 infoPtr->bFocus = FALSE;
8593 /* invalidate the selected items before resetting focus flag */
8594 LISTVIEW_InvalidateSelectedItems(infoPtr);
8596 return 0;
8599 /***
8600 * DESCRIPTION:
8601 * Processes double click messages (left mouse button).
8603 * PARAMETER(S):
8604 * [I] infoPtr : valid pointer to the listview structure
8605 * [I] wKey : key flag
8606 * [I] x,y : mouse coordinate
8608 * RETURN:
8609 * Zero
8611 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8613 LVHITTESTINFO htInfo;
8615 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8617 /* Cancel the item edition if any */
8618 if (infoPtr->itemEdit.fEnabled)
8620 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8621 infoPtr->itemEdit.fEnabled = FALSE;
8624 /* send NM_RELEASEDCAPTURE notification */
8625 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8627 htInfo.pt.x = x;
8628 htInfo.pt.y = y;
8630 /* send NM_DBLCLK notification */
8631 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8632 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8634 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8635 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8637 return 0;
8640 /***
8641 * DESCRIPTION:
8642 * Processes mouse down messages (left mouse button).
8644 * PARAMETERS:
8645 * infoPtr [I ] valid pointer to the listview structure
8646 * wKey [I ] key flag
8647 * x,y [I ] mouse coordinate
8649 * RETURN:
8650 * Zero
8652 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8654 LVHITTESTINFO lvHitTestInfo;
8655 static BOOL bGroupSelect = TRUE;
8656 POINT pt = { x, y };
8657 INT nItem;
8659 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8661 /* send NM_RELEASEDCAPTURE notification */
8662 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8664 /* set left button down flag and record the click position */
8665 infoPtr->bLButtonDown = TRUE;
8666 infoPtr->ptClickPos = pt;
8667 infoPtr->bDragging = FALSE;
8669 lvHitTestInfo.pt.x = x;
8670 lvHitTestInfo.pt.y = y;
8672 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8673 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8674 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8676 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8678 toggle_checkbox_state(infoPtr, nItem);
8679 return 0;
8682 if (infoPtr->dwStyle & LVS_SINGLESEL)
8684 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8685 infoPtr->nEditLabelItem = nItem;
8686 else
8687 LISTVIEW_SetSelection(infoPtr, nItem);
8689 else
8691 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8693 if (bGroupSelect)
8695 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8696 LISTVIEW_SetItemFocus(infoPtr, nItem);
8697 infoPtr->nSelectionMark = nItem;
8699 else
8701 LVITEMW item;
8703 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8704 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8706 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8707 infoPtr->nSelectionMark = nItem;
8710 else if (wKey & MK_CONTROL)
8712 LVITEMW item;
8714 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8716 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8717 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8718 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8719 infoPtr->nSelectionMark = nItem;
8721 else if (wKey & MK_SHIFT)
8723 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8725 else
8727 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8728 infoPtr->nEditLabelItem = nItem;
8730 /* set selection (clears other pre-existing selections) */
8731 LISTVIEW_SetSelection(infoPtr, nItem);
8735 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8736 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8738 else
8740 /* remove all selections */
8741 LISTVIEW_DeselectAll(infoPtr);
8742 ReleaseCapture();
8745 return 0;
8748 /***
8749 * DESCRIPTION:
8750 * Processes mouse up messages (left mouse button).
8752 * PARAMETERS:
8753 * infoPtr [I ] valid pointer to the listview structure
8754 * wKey [I ] key flag
8755 * x,y [I ] mouse coordinate
8757 * RETURN:
8758 * Zero
8760 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8762 LVHITTESTINFO lvHitTestInfo;
8764 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8766 if (!infoPtr->bLButtonDown) return 0;
8768 lvHitTestInfo.pt.x = x;
8769 lvHitTestInfo.pt.y = y;
8771 /* send NM_CLICK notification */
8772 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8773 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8775 /* set left button flag */
8776 infoPtr->bLButtonDown = FALSE;
8778 if (infoPtr->bDragging)
8780 infoPtr->bDragging = FALSE;
8781 return 0;
8784 /* if we clicked on a selected item, edit the label */
8785 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8787 /* we want to make sure the user doesn't want to do a double click. So we will
8788 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8790 infoPtr->itemEdit.fEnabled = TRUE;
8791 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8792 SetTimer(infoPtr->hwndSelf,
8793 (UINT_PTR)&infoPtr->itemEdit,
8794 GetDoubleClickTime(),
8795 LISTVIEW_DelayedEditItem);
8798 if (!infoPtr->bFocus)
8799 SetFocus(infoPtr->hwndSelf);
8801 return 0;
8804 /***
8805 * DESCRIPTION:
8806 * Destroys the listview control (called after WM_DESTROY).
8808 * PARAMETER(S):
8809 * [I] infoPtr : valid pointer to the listview structure
8811 * RETURN:
8812 * Zero
8814 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8816 TRACE("()\n");
8818 /* delete all items */
8819 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8821 /* destroy data structure */
8822 DPA_Destroy(infoPtr->hdpaItems);
8823 DPA_Destroy(infoPtr->hdpaPosX);
8824 DPA_Destroy(infoPtr->hdpaPosY);
8825 DPA_Destroy(infoPtr->hdpaColumns);
8826 ranges_destroy(infoPtr->selectionRanges);
8828 /* destroy image lists */
8829 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8831 if (infoPtr->himlNormal)
8832 ImageList_Destroy(infoPtr->himlNormal);
8833 if (infoPtr->himlSmall)
8834 ImageList_Destroy(infoPtr->himlSmall);
8835 if (infoPtr->himlState)
8836 ImageList_Destroy(infoPtr->himlState);
8839 /* destroy font, bkgnd brush */
8840 infoPtr->hFont = 0;
8841 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8842 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8844 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8846 /* free listview info pointer*/
8847 Free(infoPtr);
8849 return 0;
8852 /***
8853 * DESCRIPTION:
8854 * Handles notifications from header.
8856 * PARAMETER(S):
8857 * [I] infoPtr : valid pointer to the listview structure
8858 * [I] nCtrlId : control identifier
8859 * [I] lpnmh : notification information
8861 * RETURN:
8862 * Zero
8864 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8866 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8867 HWND hwndSelf = infoPtr->hwndSelf;
8869 TRACE("(lpnmh=%p)\n", lpnmh);
8871 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8873 switch (lpnmh->hdr.code)
8875 case HDN_TRACKW:
8876 case HDN_TRACKA:
8878 COLUMN_INFO *lpColumnInfo;
8879 POINT ptOrigin;
8880 INT x;
8882 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8883 break;
8885 /* remove the old line (if any) */
8886 LISTVIEW_DrawTrackLine(infoPtr);
8888 /* compute & draw the new line */
8889 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8890 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8891 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8892 infoPtr->xTrackLine = x + ptOrigin.x;
8893 LISTVIEW_DrawTrackLine(infoPtr);
8894 break;
8897 case HDN_ENDTRACKA:
8898 case HDN_ENDTRACKW:
8899 /* remove the track line (if any) */
8900 LISTVIEW_DrawTrackLine(infoPtr);
8901 infoPtr->xTrackLine = -1;
8902 break;
8904 case HDN_ENDDRAG:
8905 FIXME("Changing column order not implemented\n");
8906 return TRUE;
8908 case HDN_ITEMCHANGINGW:
8909 case HDN_ITEMCHANGINGA:
8910 return notify_forward_header(infoPtr, lpnmh);
8912 case HDN_ITEMCHANGEDW:
8913 case HDN_ITEMCHANGEDA:
8915 COLUMN_INFO *lpColumnInfo;
8916 INT dx, cxy;
8918 notify_forward_header(infoPtr, lpnmh);
8919 if (!IsWindow(hwndSelf))
8920 break;
8922 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8924 HDITEMW hdi;
8926 hdi.mask = HDI_WIDTH;
8927 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8928 cxy = hdi.cxy;
8930 else
8931 cxy = lpnmh->pitem->cxy;
8933 /* determine how much we change since the last know position */
8934 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8935 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8936 if (dx != 0)
8938 lpColumnInfo->rcHeader.right += dx;
8939 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8940 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8941 else
8943 /* only needs to update the scrolls */
8944 infoPtr->nItemWidth += dx;
8945 LISTVIEW_UpdateScroll(infoPtr);
8947 LISTVIEW_UpdateItemSize(infoPtr);
8948 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8950 POINT ptOrigin;
8951 RECT rcCol = lpColumnInfo->rcHeader;
8953 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8954 OffsetRect(&rcCol, ptOrigin.x, 0);
8956 rcCol.top = infoPtr->rcList.top;
8957 rcCol.bottom = infoPtr->rcList.bottom;
8959 /* resizing left-aligned columns leaves most of the left side untouched */
8960 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8962 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8963 if (dx > 0)
8964 nMaxDirty += dx;
8965 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8968 /* when shrinking the last column clear the now unused field */
8969 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8970 rcCol.right -= dx;
8972 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8976 break;
8978 case HDN_ITEMCLICKW:
8979 case HDN_ITEMCLICKA:
8981 /* Handle sorting by Header Column */
8982 NMLISTVIEW nmlv;
8984 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8985 nmlv.iItem = -1;
8986 nmlv.iSubItem = lpnmh->iItem;
8987 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8989 break;
8991 case HDN_DIVIDERDBLCLICKW:
8992 case HDN_DIVIDERDBLCLICKA:
8993 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8994 break;
8997 return 0;
9000 /***
9001 * DESCRIPTION:
9002 * Paint non-client area of control.
9004 * PARAMETER(S):
9005 * [I] infoPtr : valid pointer to the listview structureof the sender
9006 * [I] region : update region
9008 * RETURN:
9009 * TRUE - frame was painted
9010 * FALSE - call default window proc
9012 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9014 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9015 HDC dc;
9016 RECT r;
9017 HRGN cliprgn;
9018 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9019 cyEdge = GetSystemMetrics (SM_CYEDGE);
9021 if (!theme) return FALSE;
9023 GetWindowRect(infoPtr->hwndSelf, &r);
9025 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9026 r.right - cxEdge, r.bottom - cyEdge);
9027 if (region != (HRGN)1)
9028 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9029 OffsetRect(&r, -r.left, -r.top);
9031 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9032 OffsetRect(&r, -r.left, -r.top);
9034 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9035 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9036 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9037 ReleaseDC(infoPtr->hwndSelf, dc);
9039 /* Call default proc to get the scrollbars etc. painted */
9040 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9042 return TRUE;
9045 /***
9046 * DESCRIPTION:
9047 * Determines the type of structure to use.
9049 * PARAMETER(S):
9050 * [I] infoPtr : valid pointer to the listview structureof the sender
9051 * [I] hwndFrom : listview window handle
9052 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9054 * RETURN:
9055 * Zero
9057 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9059 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9061 if (nCommand == NF_REQUERY)
9062 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9064 return infoPtr->notifyFormat;
9067 /***
9068 * DESCRIPTION:
9069 * Paints/Repaints the listview control.
9071 * PARAMETER(S):
9072 * [I] infoPtr : valid pointer to the listview structure
9073 * [I] hdc : device context handle
9075 * RETURN:
9076 * Zero
9078 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9080 TRACE("(hdc=%p)\n", hdc);
9082 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9084 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9086 infoPtr->bNoItemMetrics = FALSE;
9087 LISTVIEW_UpdateItemSize(infoPtr);
9088 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9089 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9090 LISTVIEW_UpdateScroll(infoPtr);
9093 UpdateWindow(infoPtr->hwndHeader);
9095 if (hdc)
9096 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9097 else
9099 PAINTSTRUCT ps;
9101 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9102 if (!hdc) return 1;
9103 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9104 EndPaint(infoPtr->hwndSelf, &ps);
9107 return 0;
9111 /***
9112 * DESCRIPTION:
9113 * Paints/Repaints the listview control.
9115 * PARAMETER(S):
9116 * [I] infoPtr : valid pointer to the listview structure
9117 * [I] hdc : device context handle
9118 * [I] options : drawing options
9120 * RETURN:
9121 * Zero
9123 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9125 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9127 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9128 return 0;
9130 if (options & PRF_ERASEBKGND)
9131 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9133 if (options & PRF_CLIENT)
9134 LISTVIEW_Paint(infoPtr, hdc);
9136 return 0;
9140 /***
9141 * DESCRIPTION:
9142 * Processes double click messages (right mouse button).
9144 * PARAMETER(S):
9145 * [I] infoPtr : valid pointer to the listview structure
9146 * [I] wKey : key flag
9147 * [I] x,y : mouse coordinate
9149 * RETURN:
9150 * Zero
9152 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9154 LVHITTESTINFO lvHitTestInfo;
9156 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9158 /* send NM_RELEASEDCAPTURE notification */
9159 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9161 /* send NM_RDBLCLK notification */
9162 lvHitTestInfo.pt.x = x;
9163 lvHitTestInfo.pt.y = y;
9164 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9165 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9167 return 0;
9170 /***
9171 * DESCRIPTION:
9172 * Processes mouse down messages (right mouse button).
9174 * PARAMETER(S):
9175 * [I] infoPtr : valid pointer to the listview structure
9176 * [I] wKey : key flag
9177 * [I] x,y : mouse coordinate
9179 * RETURN:
9180 * Zero
9182 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9184 LVHITTESTINFO lvHitTestInfo;
9185 INT nItem;
9187 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9189 /* send NM_RELEASEDCAPTURE notification */
9190 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9192 /* make sure the listview control window has the focus */
9193 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9195 /* set right button down flag */
9196 infoPtr->bRButtonDown = TRUE;
9198 /* determine the index of the selected item */
9199 lvHitTestInfo.pt.x = x;
9200 lvHitTestInfo.pt.y = y;
9201 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9203 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9205 LISTVIEW_SetItemFocus(infoPtr, nItem);
9206 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9207 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9208 LISTVIEW_SetSelection(infoPtr, nItem);
9210 else
9212 LISTVIEW_DeselectAll(infoPtr);
9215 return 0;
9218 /***
9219 * DESCRIPTION:
9220 * Processes mouse up messages (right mouse button).
9222 * PARAMETER(S):
9223 * [I] infoPtr : valid pointer to the listview structure
9224 * [I] wKey : key flag
9225 * [I] x,y : mouse coordinate
9227 * RETURN:
9228 * Zero
9230 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9232 LVHITTESTINFO lvHitTestInfo;
9233 POINT pt;
9235 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9237 if (!infoPtr->bRButtonDown) return 0;
9239 /* set button flag */
9240 infoPtr->bRButtonDown = FALSE;
9242 /* Send NM_RClICK notification */
9243 lvHitTestInfo.pt.x = x;
9244 lvHitTestInfo.pt.y = y;
9245 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9246 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9248 /* Change to screen coordinate for WM_CONTEXTMENU */
9249 pt = lvHitTestInfo.pt;
9250 ClientToScreen(infoPtr->hwndSelf, &pt);
9252 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9253 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9254 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9256 return 0;
9260 /***
9261 * DESCRIPTION:
9262 * Sets the cursor.
9264 * PARAMETER(S):
9265 * [I] infoPtr : valid pointer to the listview structure
9266 * [I] hwnd : window handle of window containing the cursor
9267 * [I] nHittest : hit-test code
9268 * [I] wMouseMsg : ideintifier of the mouse message
9270 * RETURN:
9271 * TRUE if cursor is set
9272 * FALSE otherwise
9274 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9276 LVHITTESTINFO lvHitTestInfo;
9278 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9280 if(!infoPtr->hHotCursor) return FALSE;
9282 GetCursorPos(&lvHitTestInfo.pt);
9283 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9285 SetCursor(infoPtr->hHotCursor);
9287 return TRUE;
9290 /***
9291 * DESCRIPTION:
9292 * Sets the focus.
9294 * PARAMETER(S):
9295 * [I] infoPtr : valid pointer to the listview structure
9296 * [I] hwndLoseFocus : handle of previously focused window
9298 * RETURN:
9299 * Zero
9301 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9303 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9305 /* if we have the focus already, there's nothing to do */
9306 if (infoPtr->bFocus) return 0;
9308 /* send NM_SETFOCUS notification */
9309 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9311 /* set window focus flag */
9312 infoPtr->bFocus = TRUE;
9314 /* put the focus rect back on */
9315 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9317 /* redraw all visible selected items */
9318 LISTVIEW_InvalidateSelectedItems(infoPtr);
9320 return 0;
9323 /***
9324 * DESCRIPTION:
9325 * Sets the font.
9327 * PARAMETER(S):
9328 * [I] infoPtr : valid pointer to the listview structure
9329 * [I] fRedraw : font handle
9330 * [I] fRedraw : redraw flag
9332 * RETURN:
9333 * Zero
9335 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9337 HFONT oldFont = infoPtr->hFont;
9339 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9341 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9342 if (infoPtr->hFont == oldFont) return 0;
9344 LISTVIEW_SaveTextMetrics(infoPtr);
9346 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9348 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9349 LISTVIEW_UpdateSize(infoPtr);
9350 LISTVIEW_UpdateScroll(infoPtr);
9353 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9355 return 0;
9358 /***
9359 * DESCRIPTION:
9360 * Message handling for WM_SETREDRAW.
9361 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9363 * PARAMETER(S):
9364 * [I] infoPtr : valid pointer to the listview structure
9365 * [I] bRedraw: state of redraw flag
9367 * RETURN:
9368 * DefWinProc return value
9370 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9372 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9374 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9375 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9377 infoPtr->bRedraw = bRedraw;
9379 if(!bRedraw) return 0;
9381 if (is_autoarrange(infoPtr))
9382 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9383 LISTVIEW_UpdateScroll(infoPtr);
9385 /* despite what the WM_SETREDRAW docs says, apps expect us
9386 * to invalidate the listview here... stupid! */
9387 LISTVIEW_InvalidateList(infoPtr);
9389 return 0;
9392 /***
9393 * DESCRIPTION:
9394 * Resizes the listview control. This function processes WM_SIZE
9395 * messages. At this time, the width and height are not used.
9397 * PARAMETER(S):
9398 * [I] infoPtr : valid pointer to the listview structure
9399 * [I] Width : new width
9400 * [I] Height : new height
9402 * RETURN:
9403 * Zero
9405 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9407 RECT rcOld = infoPtr->rcList;
9409 TRACE("(width=%d, height=%d)\n", Width, Height);
9411 LISTVIEW_UpdateSize(infoPtr);
9412 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9414 /* do not bother with display related stuff if we're not redrawing */
9415 if (!is_redrawing(infoPtr)) return 0;
9417 if (is_autoarrange(infoPtr))
9418 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9420 LISTVIEW_UpdateScroll(infoPtr);
9422 /* refresh all only for lists whose height changed significantly */
9423 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9424 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9425 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9426 LISTVIEW_InvalidateList(infoPtr);
9428 return 0;
9431 /***
9432 * DESCRIPTION:
9433 * Sets the size information.
9435 * PARAMETER(S):
9436 * [I] infoPtr : valid pointer to the listview structure
9438 * RETURN:
9439 * None
9441 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9443 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9445 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9447 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9449 if (uView == LVS_LIST)
9451 /* Apparently the "LIST" style is supposed to have the same
9452 * number of items in a column even if there is no scroll bar.
9453 * Since if a scroll bar already exists then the bottom is already
9454 * reduced, only reduce if the scroll bar does not currently exist.
9455 * The "2" is there to mimic the native control. I think it may be
9456 * related to either padding or edges. (GLA 7/2002)
9458 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9459 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9460 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9462 else if (uView == LVS_REPORT)
9464 HDLAYOUT hl;
9465 WINDOWPOS wp;
9467 hl.prc = &infoPtr->rcList;
9468 hl.pwpos = &wp;
9469 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9470 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9471 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9472 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9473 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9474 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9476 infoPtr->rcList.top = max(wp.cy, 0);
9477 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9480 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9483 /***
9484 * DESCRIPTION:
9485 * Processes WM_STYLECHANGED messages.
9487 * PARAMETER(S):
9488 * [I] infoPtr : valid pointer to the listview structure
9489 * [I] wStyleType : window style type (normal or extended)
9490 * [I] lpss : window style information
9492 * RETURN:
9493 * Zero
9495 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9496 const STYLESTRUCT *lpss)
9498 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9499 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9500 UINT style;
9502 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9503 wStyleType, lpss->styleOld, lpss->styleNew);
9505 if (wStyleType != GWL_STYLE) return 0;
9507 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9508 /* what if LVS_OWNERDATA changed? */
9509 /* or LVS_SINGLESEL */
9510 /* or LVS_SORT{AS,DES}CENDING */
9512 infoPtr->dwStyle = lpss->styleNew;
9514 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9515 ((lpss->styleNew & WS_HSCROLL) == 0))
9516 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9518 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9519 ((lpss->styleNew & WS_VSCROLL) == 0))
9520 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9522 if (uNewView != uOldView)
9524 SIZE oldIconSize = infoPtr->iconSize;
9525 HIMAGELIST himl;
9527 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9528 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9530 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9531 SetRectEmpty(&infoPtr->rcFocus);
9533 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9534 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9536 if (uNewView == LVS_ICON)
9538 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9540 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9541 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9542 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9545 else if (uNewView == LVS_REPORT)
9547 HDLAYOUT hl;
9548 WINDOWPOS wp;
9550 hl.prc = &infoPtr->rcList;
9551 hl.pwpos = &wp;
9552 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9553 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9554 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9555 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9558 LISTVIEW_UpdateItemSize(infoPtr);
9561 if (uNewView == LVS_REPORT)
9563 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9565 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9567 /* Turn off the header control */
9568 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9569 TRACE("Hide header control, was 0x%08x\n", style);
9570 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9571 } else {
9572 /* Turn on the header control */
9573 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9575 TRACE("Show header control, was 0x%08x\n", style);
9576 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9582 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9583 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9584 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9586 /* update the size of the client area */
9587 LISTVIEW_UpdateSize(infoPtr);
9589 /* add scrollbars if needed */
9590 LISTVIEW_UpdateScroll(infoPtr);
9592 /* invalidate client area + erase background */
9593 LISTVIEW_InvalidateList(infoPtr);
9595 return 0;
9598 /***
9599 * DESCRIPTION:
9600 * Window procedure of the listview control.
9603 static LRESULT WINAPI
9604 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9606 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9608 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9610 if (!infoPtr && (uMsg != WM_NCCREATE))
9611 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9613 switch (uMsg)
9615 case LVM_APPROXIMATEVIEWRECT:
9616 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9617 LOWORD(lParam), HIWORD(lParam));
9618 case LVM_ARRANGE:
9619 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9621 /* case LVM_CANCELEDITLABEL: */
9623 case LVM_CREATEDRAGIMAGE:
9624 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9626 case LVM_DELETEALLITEMS:
9627 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9629 case LVM_DELETECOLUMN:
9630 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9632 case LVM_DELETEITEM:
9633 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9635 case LVM_EDITLABELW:
9636 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9638 case LVM_EDITLABELA:
9639 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9641 /* case LVM_ENABLEGROUPVIEW: */
9643 case LVM_ENSUREVISIBLE:
9644 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9646 case LVM_FINDITEMW:
9647 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9649 case LVM_FINDITEMA:
9650 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9652 case LVM_GETBKCOLOR:
9653 return infoPtr->clrBk;
9655 /* case LVM_GETBKIMAGE: */
9657 case LVM_GETCALLBACKMASK:
9658 return infoPtr->uCallbackMask;
9660 case LVM_GETCOLUMNA:
9661 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9663 case LVM_GETCOLUMNW:
9664 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9666 case LVM_GETCOLUMNORDERARRAY:
9667 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9669 case LVM_GETCOLUMNWIDTH:
9670 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9672 case LVM_GETCOUNTPERPAGE:
9673 return LISTVIEW_GetCountPerPage(infoPtr);
9675 case LVM_GETEDITCONTROL:
9676 return (LRESULT)infoPtr->hwndEdit;
9678 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9679 return infoPtr->dwLvExStyle;
9681 /* case LVM_GETGROUPINFO: */
9683 /* case LVM_GETGROUPMETRICS: */
9685 case LVM_GETHEADER:
9686 return (LRESULT)infoPtr->hwndHeader;
9688 case LVM_GETHOTCURSOR:
9689 return (LRESULT)infoPtr->hHotCursor;
9691 case LVM_GETHOTITEM:
9692 return infoPtr->nHotItem;
9694 case LVM_GETHOVERTIME:
9695 return infoPtr->dwHoverTime;
9697 case LVM_GETIMAGELIST:
9698 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9700 /* case LVM_GETINSERTMARK: */
9702 /* case LVM_GETINSERTMARKCOLOR: */
9704 /* case LVM_GETINSERTMARKRECT: */
9706 case LVM_GETISEARCHSTRINGA:
9707 case LVM_GETISEARCHSTRINGW:
9708 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9709 return FALSE;
9711 case LVM_GETITEMA:
9712 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9714 case LVM_GETITEMW:
9715 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9717 case LVM_GETITEMCOUNT:
9718 return infoPtr->nItemCount;
9720 case LVM_GETITEMPOSITION:
9721 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9723 case LVM_GETITEMRECT:
9724 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9726 case LVM_GETITEMSPACING:
9727 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9729 case LVM_GETITEMSTATE:
9730 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9732 case LVM_GETITEMTEXTA:
9733 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9735 case LVM_GETITEMTEXTW:
9736 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9738 case LVM_GETNEXTITEM:
9739 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9741 case LVM_GETNUMBEROFWORKAREAS:
9742 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9743 return 1;
9745 case LVM_GETORIGIN:
9746 if (!lParam) return FALSE;
9747 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9748 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9749 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9750 return TRUE;
9752 /* case LVM_GETOUTLINECOLOR: */
9754 /* case LVM_GETSELECTEDCOLUMN: */
9756 case LVM_GETSELECTEDCOUNT:
9757 return LISTVIEW_GetSelectedCount(infoPtr);
9759 case LVM_GETSELECTIONMARK:
9760 return infoPtr->nSelectionMark;
9762 case LVM_GETSTRINGWIDTHA:
9763 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9765 case LVM_GETSTRINGWIDTHW:
9766 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9768 case LVM_GETSUBITEMRECT:
9769 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9771 case LVM_GETTEXTBKCOLOR:
9772 return infoPtr->clrTextBk;
9774 case LVM_GETTEXTCOLOR:
9775 return infoPtr->clrText;
9777 /* case LVM_GETTILEINFO: */
9779 /* case LVM_GETTILEVIEWINFO: */
9781 case LVM_GETTOOLTIPS:
9782 if( !infoPtr->hwndToolTip )
9783 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9784 return (LRESULT)infoPtr->hwndToolTip;
9786 case LVM_GETTOPINDEX:
9787 return LISTVIEW_GetTopIndex(infoPtr);
9789 case LVM_GETUNICODEFORMAT:
9790 return (infoPtr->notifyFormat == NFR_UNICODE);
9792 /* case LVM_GETVIEW: */
9794 case LVM_GETVIEWRECT:
9795 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9797 case LVM_GETWORKAREAS:
9798 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9799 return FALSE;
9801 /* case LVM_HASGROUP: */
9803 case LVM_HITTEST:
9804 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9806 case LVM_INSERTCOLUMNA:
9807 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9809 case LVM_INSERTCOLUMNW:
9810 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9812 /* case LVM_INSERTGROUP: */
9814 /* case LVM_INSERTGROUPSORTED: */
9816 case LVM_INSERTITEMA:
9817 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9819 case LVM_INSERTITEMW:
9820 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9822 /* case LVM_INSERTMARKHITTEST: */
9824 /* case LVM_ISGROUPVIEWENABLED: */
9826 /* case LVM_MAPIDTOINDEX: */
9828 /* case LVM_MAPINDEXTOID: */
9830 /* case LVM_MOVEGROUP: */
9832 /* case LVM_MOVEITEMTOGROUP: */
9834 case LVM_REDRAWITEMS:
9835 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9837 /* case LVM_REMOVEALLGROUPS: */
9839 /* case LVM_REMOVEGROUP: */
9841 case LVM_SCROLL:
9842 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9844 case LVM_SETBKCOLOR:
9845 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9847 /* case LVM_SETBKIMAGE: */
9849 case LVM_SETCALLBACKMASK:
9850 infoPtr->uCallbackMask = (UINT)wParam;
9851 return TRUE;
9853 case LVM_SETCOLUMNA:
9854 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9856 case LVM_SETCOLUMNW:
9857 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9859 case LVM_SETCOLUMNORDERARRAY:
9860 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9862 case LVM_SETCOLUMNWIDTH:
9863 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9865 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9866 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9868 /* case LVM_SETGROUPINFO: */
9870 /* case LVM_SETGROUPMETRICS: */
9872 case LVM_SETHOTCURSOR:
9873 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9875 case LVM_SETHOTITEM:
9876 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9878 case LVM_SETHOVERTIME:
9879 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9881 case LVM_SETICONSPACING:
9882 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9884 case LVM_SETIMAGELIST:
9885 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9887 /* case LVM_SETINFOTIP: */
9889 /* case LVM_SETINSERTMARK: */
9891 /* case LVM_SETINSERTMARKCOLOR: */
9893 case LVM_SETITEMA:
9894 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9896 case LVM_SETITEMW:
9897 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9899 case LVM_SETITEMCOUNT:
9900 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9902 case LVM_SETITEMPOSITION:
9904 POINT pt;
9905 pt.x = (short)LOWORD(lParam);
9906 pt.y = (short)HIWORD(lParam);
9907 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9910 case LVM_SETITEMPOSITION32:
9911 if (lParam == 0) return FALSE;
9912 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9914 case LVM_SETITEMSTATE:
9915 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9917 case LVM_SETITEMTEXTA:
9918 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9920 case LVM_SETITEMTEXTW:
9921 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9923 /* case LVM_SETOUTLINECOLOR: */
9925 /* case LVM_SETSELECTEDCOLUMN: */
9927 case LVM_SETSELECTIONMARK:
9928 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9930 case LVM_SETTEXTBKCOLOR:
9931 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9933 case LVM_SETTEXTCOLOR:
9934 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9936 /* case LVM_SETTILEINFO: */
9938 /* case LVM_SETTILEVIEWINFO: */
9940 /* case LVM_SETTILEWIDTH: */
9942 case LVM_SETTOOLTIPS:
9943 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9945 case LVM_SETUNICODEFORMAT:
9946 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9948 /* case LVM_SETVIEW: */
9950 /* case LVM_SETWORKAREAS: */
9952 /* case LVM_SORTGROUPS: */
9954 case LVM_SORTITEMS:
9955 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9957 /* LVM_SORTITEMSEX: */
9959 case LVM_SUBITEMHITTEST:
9960 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9962 case LVM_UPDATE:
9963 return LISTVIEW_Update(infoPtr, (INT)wParam);
9965 case WM_CHAR:
9966 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9968 case WM_COMMAND:
9969 return LISTVIEW_Command(infoPtr, wParam, lParam);
9971 case WM_NCCREATE:
9972 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9974 case WM_CREATE:
9975 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9977 case WM_DESTROY:
9978 return LISTVIEW_Destroy(infoPtr);
9980 case WM_ENABLE:
9981 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9983 case WM_ERASEBKGND:
9984 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9986 case WM_GETDLGCODE:
9987 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9989 case WM_GETFONT:
9990 return (LRESULT)infoPtr->hFont;
9992 case WM_HSCROLL:
9993 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9995 case WM_KEYDOWN:
9996 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9998 case WM_KILLFOCUS:
9999 return LISTVIEW_KillFocus(infoPtr);
10001 case WM_LBUTTONDBLCLK:
10002 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10004 case WM_LBUTTONDOWN:
10005 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10007 case WM_LBUTTONUP:
10008 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10010 case WM_MOUSEMOVE:
10011 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10013 case WM_MOUSEHOVER:
10014 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10016 case WM_NCDESTROY:
10017 return LISTVIEW_NCDestroy(infoPtr);
10019 case WM_NCPAINT:
10020 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10021 return 0;
10022 goto fwd_msg;
10024 case WM_NOTIFY:
10025 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10026 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10027 else return 0;
10029 case WM_NOTIFYFORMAT:
10030 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10032 case WM_PRINTCLIENT:
10033 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10035 case WM_PAINT:
10036 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10038 case WM_RBUTTONDBLCLK:
10039 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10041 case WM_RBUTTONDOWN:
10042 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10044 case WM_RBUTTONUP:
10045 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10047 case WM_SETCURSOR:
10048 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10049 return TRUE;
10050 goto fwd_msg;
10052 case WM_SETFOCUS:
10053 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10055 case WM_SETFONT:
10056 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10058 case WM_SETREDRAW:
10059 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10061 case WM_SIZE:
10062 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10064 case WM_STYLECHANGED:
10065 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10067 case WM_SYSCOLORCHANGE:
10068 COMCTL32_RefreshSysColors();
10069 return 0;
10071 /* case WM_TIMER: */
10072 case WM_THEMECHANGED:
10073 return LISTVIEW_ThemeChanged(infoPtr);
10075 case WM_VSCROLL:
10076 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10078 case WM_MOUSEWHEEL:
10079 if (wParam & (MK_SHIFT | MK_CONTROL))
10080 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10081 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10083 case WM_WINDOWPOSCHANGED:
10084 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10086 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10087 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10088 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10090 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10092 MEASUREITEMSTRUCT mis;
10093 mis.CtlType = ODT_LISTVIEW;
10094 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10095 mis.itemID = -1;
10096 mis.itemWidth = 0;
10097 mis.itemData = 0;
10098 mis.itemHeight= infoPtr->nItemHeight;
10099 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10100 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10101 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10104 LISTVIEW_UpdateSize(infoPtr);
10105 LISTVIEW_UpdateScroll(infoPtr);
10107 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10109 /* case WM_WININICHANGE: */
10111 default:
10112 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10113 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10115 fwd_msg:
10116 /* call default window procedure */
10117 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10122 /***
10123 * DESCRIPTION:
10124 * Registers the window class.
10126 * PARAMETER(S):
10127 * None
10129 * RETURN:
10130 * None
10132 void LISTVIEW_Register(void)
10134 WNDCLASSW wndClass;
10136 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10137 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10138 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10139 wndClass.cbClsExtra = 0;
10140 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10141 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10142 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10143 wndClass.lpszClassName = WC_LISTVIEWW;
10144 RegisterClassW(&wndClass);
10147 /***
10148 * DESCRIPTION:
10149 * Unregisters the window class.
10151 * PARAMETER(S):
10152 * None
10154 * RETURN:
10155 * None
10157 void LISTVIEW_Unregister(void)
10159 UnregisterClassW(WC_LISTVIEWW, NULL);
10162 /***
10163 * DESCRIPTION:
10164 * Handle any WM_COMMAND messages
10166 * PARAMETER(S):
10167 * [I] infoPtr : valid pointer to the listview structure
10168 * [I] wParam : the first message parameter
10169 * [I] lParam : the second message parameter
10171 * RETURN:
10172 * Zero.
10174 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10176 switch (HIWORD(wParam))
10178 case EN_UPDATE:
10181 * Adjust the edit window size
10183 WCHAR buffer[1024];
10184 HDC hdc = GetDC(infoPtr->hwndEdit);
10185 HFONT hFont, hOldFont = 0;
10186 RECT rect;
10187 SIZE sz;
10189 if (!infoPtr->hwndEdit || !hdc) return 0;
10190 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10191 GetWindowRect(infoPtr->hwndEdit, &rect);
10193 /* Select font to get the right dimension of the string */
10194 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10195 if(hFont != 0)
10197 hOldFont = SelectObject(hdc, hFont);
10200 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10202 TEXTMETRICW textMetric;
10204 /* Add Extra spacing for the next character */
10205 GetTextMetricsW(hdc, &textMetric);
10206 sz.cx += (textMetric.tmMaxCharWidth * 2);
10208 SetWindowPos (
10209 infoPtr->hwndEdit,
10210 HWND_TOP,
10213 sz.cx,
10214 rect.bottom - rect.top,
10215 SWP_DRAWFRAME|SWP_NOMOVE);
10217 if(hFont != 0)
10218 SelectObject(hdc, hOldFont);
10220 ReleaseDC(infoPtr->hwndEdit, hdc);
10222 break;
10225 default:
10226 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10229 return 0;
10233 /***
10234 * DESCRIPTION:
10235 * Subclassed edit control windproc function
10237 * PARAMETER(S):
10238 * [I] hwnd : the edit window handle
10239 * [I] uMsg : the message that is to be processed
10240 * [I] wParam : first message parameter
10241 * [I] lParam : second message parameter
10242 * [I] isW : TRUE if input is Unicode
10244 * RETURN:
10245 * Zero.
10247 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10249 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10250 BOOL cancel = FALSE;
10252 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10253 hwnd, uMsg, wParam, lParam, isW);
10255 switch (uMsg)
10257 case WM_GETDLGCODE:
10258 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10260 case WM_KILLFOCUS:
10261 break;
10263 case WM_DESTROY:
10265 WNDPROC editProc = infoPtr->EditWndProc;
10266 infoPtr->EditWndProc = 0;
10267 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10268 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10271 case WM_KEYDOWN:
10272 if (VK_ESCAPE == (INT)wParam)
10274 cancel = TRUE;
10275 break;
10277 else if (VK_RETURN == (INT)wParam)
10278 break;
10280 default:
10281 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10284 /* kill the edit */
10285 if (infoPtr->hwndEdit)
10287 LPWSTR buffer = NULL;
10289 infoPtr->hwndEdit = 0;
10290 if (!cancel)
10292 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10294 if (len)
10296 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10298 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10299 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10303 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10305 Free(buffer);
10308 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10309 return 0;
10312 /***
10313 * DESCRIPTION:
10314 * Subclassed edit control Unicode windproc function
10316 * PARAMETER(S):
10317 * [I] hwnd : the edit window handle
10318 * [I] uMsg : the message that is to be processed
10319 * [I] wParam : first message parameter
10320 * [I] lParam : second message parameter
10322 * RETURN:
10324 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10326 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10329 /***
10330 * DESCRIPTION:
10331 * Subclassed edit control ANSI windproc function
10333 * PARAMETER(S):
10334 * [I] hwnd : the edit window handle
10335 * [I] uMsg : the message that is to be processed
10336 * [I] wParam : first message parameter
10337 * [I] lParam : second message parameter
10339 * RETURN:
10341 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10343 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10346 /***
10347 * DESCRIPTION:
10348 * Creates a subclassed edit control
10350 * PARAMETER(S):
10351 * [I] infoPtr : valid pointer to the listview structure
10352 * [I] text : initial text for the edit
10353 * [I] style : the window style
10354 * [I] isW : TRUE if input is Unicode
10356 * RETURN:
10358 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10359 INT x, INT y, INT width, INT height, BOOL isW)
10361 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10362 HWND hedit;
10363 SIZE sz;
10364 HDC hdc;
10365 HDC hOldFont=0;
10366 TEXTMETRICW textMetric;
10367 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10369 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10371 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10372 hdc = GetDC(infoPtr->hwndSelf);
10374 /* Select the font to get appropriate metric dimensions */
10375 if(infoPtr->hFont != 0)
10376 hOldFont = SelectObject(hdc, infoPtr->hFont);
10378 /*Get String Length in pixels */
10379 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10381 /*Add Extra spacing for the next character */
10382 GetTextMetricsW(hdc, &textMetric);
10383 sz.cx += (textMetric.tmMaxCharWidth * 2);
10385 if(infoPtr->hFont != 0)
10386 SelectObject(hdc, hOldFont);
10388 ReleaseDC(infoPtr->hwndSelf, hdc);
10389 if (isW)
10390 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10391 else
10392 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10394 if (!hedit) return 0;
10396 infoPtr->EditWndProc = (WNDPROC)
10397 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10398 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10400 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10402 return hedit;