comctl32: Don't rearrange icons / update scroll bar when destroying a listview.
[wine.git] / dlls / comctl32 / listview.c
blobce909fe3259052e922095f1127764a5f30aa9a5f
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_ONECLICKACTIVATE
99 * -- LVS_EX_REGIONAL
100 * -- LVS_EX_SIMPLESELECT
101 * -- LVS_EX_TRACKSELECT
102 * -- LVS_EX_TWOCLICKACTIVATE
103 * -- LVS_EX_UNDERLINECOLD
104 * -- LVS_EX_UNDERLINEHOT
106 * Notifications:
107 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
108 * -- LVN_GETINFOTIP
109 * -- LVN_HOTTRACK
110 * -- LVN_MARQUEEBEGIN
111 * -- LVN_ODFINDITEM
112 * -- LVN_SETDISPINFO
113 * -- NM_HOVER
114 * -- LVN_BEGINRDRAG
116 * Messages:
117 * -- LVM_CANCELEDITLABEL
118 * -- LVM_ENABLEGROUPVIEW
119 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
120 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
121 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
122 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
123 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
124 * -- LVM_GETINSERTMARKRECT
125 * -- LVM_GETNUMBEROFWORKAREAS
126 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
127 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
128 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
129 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
130 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
131 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
132 * -- LVM_GETVIEW, LVM_SETVIEW
133 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
134 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
135 * -- LVM_INSERTGROUPSORTED
136 * -- LVM_INSERTMARKHITTEST
137 * -- LVM_ISGROUPVIEWENABLED
138 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
139 * -- LVM_MOVEGROUP
140 * -- LVM_MOVEITEMTOGROUP
141 * -- LVM_SETINFOTIP
142 * -- LVM_SETTILEWIDTH
143 * -- LVM_SORTGROUPS
144 * -- LVM_SORTITEMSEX
146 * Macros:
147 * -- ListView_GetCheckSate, ListView_SetCheckState
148 * -- ListView_GetHoverTime, ListView_SetHoverTime
149 * -- ListView_GetISearchString
150 * -- ListView_GetNumberOfWorkAreas
151 * -- ListView_GetOrigin
152 * -- ListView_GetTextBkColor
153 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
154 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
155 * -- ListView_SortItemsEx
157 * Functions:
158 * -- LVGroupComparE
160 * Known differences in message stream from native control (not known if
161 * these differences cause problems):
162 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
163 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
164 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
165 * processing for "USEDOUBLECLICKTIME".
168 #include "config.h"
169 #include "wine/port.h"
171 #include <assert.h>
172 #include <ctype.h>
173 #include <string.h>
174 #include <stdlib.h>
175 #include <stdarg.h>
176 #include <stdio.h>
178 #include "windef.h"
179 #include "winbase.h"
180 #include "winnt.h"
181 #include "wingdi.h"
182 #include "winuser.h"
183 #include "winnls.h"
184 #include "commctrl.h"
185 #include "comctl32.h"
186 #include "uxtheme.h"
188 #include "wine/debug.h"
189 #include "wine/unicode.h"
191 WINE_DEFAULT_DEBUG_CHANNEL(listview);
193 /* make sure you set this to 0 for production use! */
194 #define DEBUG_RANGES 1
196 typedef struct tagCOLUMN_INFO
198 RECT rcHeader; /* tracks the header's rectangle */
199 int fmt; /* same as LVCOLUMN.fmt */
200 } COLUMN_INFO;
202 typedef struct tagITEMHDR
204 LPWSTR pszText;
205 INT iImage;
206 } ITEMHDR, *LPITEMHDR;
208 typedef struct tagSUBITEM_INFO
210 ITEMHDR hdr;
211 INT iSubItem;
212 } SUBITEM_INFO;
214 typedef struct tagITEM_INFO
216 ITEMHDR hdr;
217 UINT state;
218 LPARAM lParam;
219 INT iIndent;
220 } ITEM_INFO;
222 typedef struct tagRANGE
224 INT lower;
225 INT upper;
226 } RANGE;
228 typedef struct tagRANGES
230 HDPA hdpa;
231 } *RANGES;
233 typedef struct tagITERATOR
235 INT nItem;
236 INT nSpecial;
237 RANGE range;
238 RANGES ranges;
239 INT index;
240 } ITERATOR;
242 typedef struct tagDELAYED_ITEM_EDIT
244 BOOL fEnabled;
245 INT iItem;
246 } DELAYED_ITEM_EDIT;
248 typedef struct tagLISTVIEW_INFO
250 HWND hwndSelf;
251 HBRUSH hBkBrush;
252 COLORREF clrBk;
253 COLORREF clrText;
254 COLORREF clrTextBk;
255 HIMAGELIST himlNormal;
256 HIMAGELIST himlSmall;
257 HIMAGELIST himlState;
258 BOOL bLButtonDown;
259 BOOL bRButtonDown;
260 POINT ptClickPos; /* point where the user clicked */
261 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
262 INT nItemHeight;
263 INT nItemWidth;
264 RANGES selectionRanges;
265 INT nSelectionMark;
266 INT nHotItem;
267 SHORT notifyFormat;
268 HWND hwndNotify;
269 RECT rcList; /* This rectangle is really the window
270 * client rectangle possibly reduced by the
271 * horizontal scroll bar and/or header - see
272 * LISTVIEW_UpdateSize. This rectangle offset
273 * by the LISTVIEW_GetOrigin value is in
274 * client coordinates */
275 SIZE iconSize;
276 SIZE iconSpacing;
277 SIZE iconStateSize;
278 UINT uCallbackMask;
279 HWND hwndHeader;
280 HCURSOR hHotCursor;
281 HFONT hDefaultFont;
282 HFONT hFont;
283 INT ntmHeight; /* Some cached metrics of the font used */
284 INT ntmMaxCharWidth; /* by the listview to draw items */
285 INT nEllipsisWidth;
286 BOOL bRedraw; /* Turns on/off repaints & invalidations */
287 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
288 BOOL bFocus;
289 BOOL bDoChangeNotify; /* send change notification messages? */
290 INT nFocusedItem;
291 RECT rcFocus;
292 DWORD dwStyle; /* the cached window GWL_STYLE */
293 DWORD dwLvExStyle; /* extended listview style */
294 INT nItemCount; /* the number of items in the list */
295 HDPA hdpaItems; /* array ITEM_INFO pointers */
296 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
297 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
298 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
299 POINT currIconPos; /* this is the position next icon will be placed */
300 PFNLVCOMPARE pfnCompare;
301 LPARAM lParamSort;
302 HWND hwndEdit;
303 WNDPROC EditWndProc;
304 INT nEditLabelItem;
305 DWORD dwHoverTime;
306 HWND hwndToolTip;
308 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
310 DWORD lastKeyPressTimestamp;
311 WPARAM charCode;
312 INT nSearchParamLength;
313 WCHAR szSearchParam[ MAX_PATH ];
314 BOOL bIsDrawing;
315 INT nMeasureItemHeight;
316 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
317 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
318 } LISTVIEW_INFO;
321 * constants
323 /* How many we debug buffer to allocate */
324 #define DEBUG_BUFFERS 20
325 /* The size of a single debug bbuffer */
326 #define DEBUG_BUFFER_SIZE 256
328 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
329 #define SB_INTERNAL -1
331 /* maximum size of a label */
332 #define DISP_TEXT_SIZE 512
334 /* padding for items in list and small icon display modes */
335 #define WIDTH_PADDING 12
337 /* padding for items in list, report and small icon display modes */
338 #define HEIGHT_PADDING 1
340 /* offset of items in report display mode */
341 #define REPORT_MARGINX 2
343 /* padding for icon in large icon display mode
344 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
345 * that HITTEST will see.
346 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
347 * ICON_TOP_PADDING - sum of the two above.
348 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
349 * LABEL_HOR_PADDING - between text and sides of box
350 * LABEL_VERT_PADDING - between bottom of text and end of box
352 * ICON_LR_PADDING - additional width above icon size.
353 * ICON_LR_HALF - half of the above value
355 #define ICON_TOP_PADDING_NOTHITABLE 2
356 #define ICON_TOP_PADDING_HITABLE 2
357 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
358 #define ICON_BOTTOM_PADDING 4
359 #define LABEL_HOR_PADDING 5
360 #define LABEL_VERT_PADDING 7
361 #define ICON_LR_PADDING 16
362 #define ICON_LR_HALF (ICON_LR_PADDING/2)
364 /* default label width for items in list and small icon display modes */
365 #define DEFAULT_LABEL_WIDTH 40
367 /* default column width for items in list display mode */
368 #define DEFAULT_COLUMN_WIDTH 128
370 /* Size of "line" scroll for V & H scrolls */
371 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
373 /* Padding between image and label */
374 #define IMAGE_PADDING 2
376 /* Padding behind the label */
377 #define TRAILING_LABEL_PADDING 12
378 #define TRAILING_HEADER_PADDING 11
380 /* Border for the icon caption */
381 #define CAPTION_BORDER 2
383 /* Standard DrawText flags */
384 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
385 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
386 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
388 /* Image index from state */
389 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
391 /* The time in milliseconds to reset the search in the list */
392 #define KEY_DELAY 450
394 /* Dump the LISTVIEW_INFO structure to the debug channel */
395 #define LISTVIEW_DUMP(iP) do { \
396 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
397 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
398 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
399 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
400 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
401 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
402 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
403 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
404 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
405 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
406 } while(0)
408 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
411 * forward declarations
413 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
414 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
415 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
416 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
417 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
418 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
419 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
420 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
421 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
422 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
423 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
424 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
425 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
426 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
427 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
428 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
429 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
430 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
431 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
432 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
433 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
434 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
435 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
436 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
437 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
438 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
439 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
441 /******** Text handling functions *************************************/
443 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
444 * text string. The string may be ANSI or Unicode, in which case
445 * the boolean isW tells us the type of the string.
447 * The name of the function tell what type of strings it expects:
448 * W: Unicode, T: ANSI/Unicode - function of isW
451 static inline BOOL is_textW(LPCWSTR text)
453 return text != NULL && text != LPSTR_TEXTCALLBACKW;
456 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
458 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
459 return is_textW(text);
462 static inline int textlenT(LPCWSTR text, BOOL isW)
464 return !is_textT(text, isW) ? 0 :
465 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
468 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
470 if (isDestW)
471 if (isSrcW) lstrcpynW(dest, src, max);
472 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
473 else
474 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
475 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
478 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
480 LPWSTR wstr = (LPWSTR)text;
482 if (!isW && is_textT(text, isW))
484 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
485 wstr = Alloc(len * sizeof(WCHAR));
486 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
488 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
489 return wstr;
492 static inline void textfreeT(LPWSTR wstr, BOOL isW)
494 if (!isW && is_textT(wstr, isW)) Free (wstr);
498 * dest is a pointer to a Unicode string
499 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
501 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
503 BOOL bResult = TRUE;
505 if (src == LPSTR_TEXTCALLBACKW)
507 if (is_textW(*dest)) Free(*dest);
508 *dest = LPSTR_TEXTCALLBACKW;
510 else
512 LPWSTR pszText = textdupTtoW(src, isW);
513 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
514 bResult = Str_SetPtrW(dest, pszText);
515 textfreeT(pszText, isW);
517 return bResult;
521 * compares a Unicode to a Unicode/ANSI text string
523 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
525 if (!aw) return bt ? -1 : 0;
526 if (!bt) return aw ? 1 : 0;
527 if (aw == LPSTR_TEXTCALLBACKW)
528 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
529 if (bt != LPSTR_TEXTCALLBACKW)
531 LPWSTR bw = textdupTtoW(bt, isW);
532 int r = bw ? lstrcmpW(aw, bw) : 1;
533 textfreeT(bw, isW);
534 return r;
537 return 1;
540 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
542 int res;
544 n = min(min(n, strlenW(s1)), strlenW(s2));
545 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
546 return res ? res - sizeof(WCHAR) : res;
549 /******** Debugging functions *****************************************/
551 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
553 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
554 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
557 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
559 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
560 n = min(textlenT(text, isW), n);
561 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
564 static char* debug_getbuf(void)
566 static int index = 0;
567 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
568 return buffers[index++ % DEBUG_BUFFERS];
571 static inline const char* debugrange(const RANGE *lprng)
573 if (!lprng) return "(null)";
574 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
577 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
579 char* buf = debug_getbuf(), *text = buf;
580 int len, size = DEBUG_BUFFER_SIZE;
582 if (pScrollInfo == NULL) return "(null)";
583 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
584 if (len == -1) goto end; buf += len; size -= len;
585 if (pScrollInfo->fMask & SIF_RANGE)
586 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
587 else len = 0;
588 if (len == -1) goto end; buf += len; size -= len;
589 if (pScrollInfo->fMask & SIF_PAGE)
590 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
591 else len = 0;
592 if (len == -1) goto end; buf += len; size -= len;
593 if (pScrollInfo->fMask & SIF_POS)
594 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
595 else len = 0;
596 if (len == -1) goto end; buf += len; size -= len;
597 if (pScrollInfo->fMask & SIF_TRACKPOS)
598 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
599 else len = 0;
600 if (len == -1) goto end; buf += len; size -= len;
601 goto undo;
602 end:
603 buf = text + strlen(text);
604 undo:
605 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
606 return text;
609 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
611 if (!plvnm) return "(null)";
612 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
613 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
614 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
615 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
618 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
620 char* buf = debug_getbuf(), *text = buf;
621 int len, size = DEBUG_BUFFER_SIZE;
623 if (lpLVItem == NULL) return "(null)";
624 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
625 if (len == -1) goto end; buf += len; size -= len;
626 if (lpLVItem->mask & LVIF_STATE)
627 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
628 else len = 0;
629 if (len == -1) goto end; buf += len; size -= len;
630 if (lpLVItem->mask & LVIF_TEXT)
631 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
632 else len = 0;
633 if (len == -1) goto end; buf += len; size -= len;
634 if (lpLVItem->mask & LVIF_IMAGE)
635 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
636 else len = 0;
637 if (len == -1) goto end; buf += len; size -= len;
638 if (lpLVItem->mask & LVIF_PARAM)
639 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
640 else len = 0;
641 if (len == -1) goto end; buf += len; size -= len;
642 if (lpLVItem->mask & LVIF_INDENT)
643 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
644 else len = 0;
645 if (len == -1) goto end; buf += len; size -= len;
646 goto undo;
647 end:
648 buf = text + strlen(text);
649 undo:
650 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
651 return text;
654 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
656 char* buf = debug_getbuf(), *text = buf;
657 int len, size = DEBUG_BUFFER_SIZE;
659 if (lpColumn == NULL) return "(null)";
660 len = snprintf(buf, size, "{");
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_SUBITEM)
663 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
664 else len = 0;
665 if (len == -1) goto end; buf += len; size -= len;
666 if (lpColumn->mask & LVCF_FMT)
667 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
668 else len = 0;
669 if (len == -1) goto end; buf += len; size -= len;
670 if (lpColumn->mask & LVCF_WIDTH)
671 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
672 else len = 0;
673 if (len == -1) goto end; buf += len; size -= len;
674 if (lpColumn->mask & LVCF_TEXT)
675 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
676 else len = 0;
677 if (len == -1) goto end; buf += len; size -= len;
678 if (lpColumn->mask & LVCF_IMAGE)
679 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
680 else len = 0;
681 if (len == -1) goto end; buf += len; size -= len;
682 if (lpColumn->mask & LVCF_ORDER)
683 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
684 else len = 0;
685 if (len == -1) goto end; buf += len; size -= len;
686 goto undo;
687 end:
688 buf = text + strlen(text);
689 undo:
690 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
691 return text;
694 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
696 if (!lpht) return "(null)";
698 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
699 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
702 /* Return the corresponding text for a given scroll value */
703 static inline LPCSTR debugscrollcode(int nScrollCode)
705 switch(nScrollCode)
707 case SB_LINELEFT: return "SB_LINELEFT";
708 case SB_LINERIGHT: return "SB_LINERIGHT";
709 case SB_PAGELEFT: return "SB_PAGELEFT";
710 case SB_PAGERIGHT: return "SB_PAGERIGHT";
711 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
712 case SB_THUMBTRACK: return "SB_THUMBTRACK";
713 case SB_ENDSCROLL: return "SB_ENDSCROLL";
714 case SB_INTERNAL: return "SB_INTERNAL";
715 default: return "unknown";
720 /******** Notification functions i************************************/
722 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
724 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
725 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
728 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
730 LRESULT result;
732 TRACE("(code=%d)\n", code);
734 pnmh->hwndFrom = infoPtr->hwndSelf;
735 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
736 pnmh->code = code;
737 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
739 TRACE(" <= %ld\n", result);
741 return result;
744 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
746 NMHDR nmh;
747 HWND hwnd = infoPtr->hwndSelf;
748 notify_hdr(infoPtr, code, &nmh);
749 return IsWindow(hwnd);
752 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
754 NMITEMACTIVATE nmia;
755 LVITEMW item;
757 if (htInfo) {
758 nmia.uNewState = 0;
759 nmia.uOldState = 0;
760 nmia.uChanged = 0;
761 nmia.uKeyFlags = 0;
763 item.mask = LVIF_PARAM|LVIF_STATE;
764 item.iItem = htInfo->iItem;
765 item.iSubItem = 0;
766 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
767 nmia.lParam = item.lParam;
768 nmia.uOldState = item.state;
769 nmia.uNewState = item.state | LVIS_ACTIVATING;
770 nmia.uChanged = LVIF_STATE;
773 nmia.iItem = htInfo->iItem;
774 nmia.iSubItem = htInfo->iSubItem;
775 nmia.ptAction = htInfo->pt;
777 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
778 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
779 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
781 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
784 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
786 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
787 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
790 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
792 NMLISTVIEW nmlv;
793 LVITEMW item;
794 HWND hwnd = infoPtr->hwndSelf;
796 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
797 ZeroMemory(&nmlv, sizeof(nmlv));
798 nmlv.iItem = lvht->iItem;
799 nmlv.iSubItem = lvht->iSubItem;
800 nmlv.ptAction = lvht->pt;
801 item.mask = LVIF_PARAM;
802 item.iItem = lvht->iItem;
803 item.iSubItem = 0;
804 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
805 notify_listview(infoPtr, code, &nmlv);
806 return IsWindow(hwnd);
809 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
811 NMLISTVIEW nmlv;
812 LVITEMW item;
813 HWND hwnd = infoPtr->hwndSelf;
815 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
816 nmlv.iItem = nItem;
817 item.mask = LVIF_PARAM;
818 item.iItem = nItem;
819 item.iSubItem = 0;
820 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
821 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
822 return IsWindow(hwnd);
825 static int get_ansi_notification(INT unicodeNotificationCode)
827 switch (unicodeNotificationCode)
829 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
830 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
831 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
832 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
833 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
834 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
836 ERR("unknown notification %x\n", unicodeNotificationCode);
837 assert(FALSE);
838 return 0;
842 Send notification. depends on dispinfoW having same
843 structure as dispinfoA.
844 infoPtr : listview struct
845 notificationCode : *Unicode* notification code
846 pdi : dispinfo structure (can be unicode or ansi)
847 isW : TRUE if dispinfo is Unicode
849 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
851 BOOL bResult = FALSE;
852 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
853 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
854 LPWSTR pszTempBuf = NULL, savPszText = NULL;
856 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
858 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
859 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
862 if (convertToAnsi || convertToUnicode)
864 if (notificationCode != LVN_GETDISPINFOW)
866 cchTempBufMax = convertToUnicode ?
867 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
868 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
870 else
872 cchTempBufMax = pdi->item.cchTextMax;
873 *pdi->item.pszText = 0; /* make sure we don't process garbage */
876 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
877 if (!pszTempBuf) return FALSE;
879 if (convertToUnicode)
880 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
881 pszTempBuf, cchTempBufMax);
882 else
883 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
884 cchTempBufMax, NULL, NULL);
886 savCchTextMax = pdi->item.cchTextMax;
887 savPszText = pdi->item.pszText;
888 pdi->item.pszText = pszTempBuf;
889 pdi->item.cchTextMax = cchTempBufMax;
892 if (infoPtr->notifyFormat == NFR_ANSI)
893 realNotifCode = get_ansi_notification(notificationCode);
894 else
895 realNotifCode = notificationCode;
896 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
897 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
899 if (convertToUnicode || convertToAnsi)
901 if (convertToUnicode) /* note : pointer can be changed by app ! */
902 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
903 savCchTextMax, NULL, NULL);
904 else
905 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
906 savPszText, savCchTextMax);
907 pdi->item.pszText = savPszText; /* restores our buffer */
908 pdi->item.cchTextMax = savCchTextMax;
909 Free (pszTempBuf);
911 return bResult;
914 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
915 const RECT *rcBounds, const LVITEMW *lplvItem)
917 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
918 lpnmlvcd->nmcd.hdc = hdc;
919 lpnmlvcd->nmcd.rc = *rcBounds;
920 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
921 lpnmlvcd->clrText = infoPtr->clrText;
922 if (!lplvItem) return;
923 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
924 lpnmlvcd->iSubItem = lplvItem->iSubItem;
925 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
926 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
927 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
928 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
931 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
933 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
934 DWORD result;
936 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
937 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
938 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
939 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
940 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
941 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
942 return result;
945 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
947 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
948 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
949 if (lpnmlvcd->clrText == CLR_DEFAULT)
950 lpnmlvcd->clrText = comctl32_color.clrWindowText;
952 /* apparently, for selected items, we have to override the returned values */
953 if (!SubItem)
955 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
957 if (infoPtr->bFocus)
959 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
960 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
962 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
964 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
965 lpnmlvcd->clrText = comctl32_color.clrBtnText;
970 /* Set the text attributes */
971 if (lpnmlvcd->clrTextBk != CLR_NONE)
973 SetBkMode(hdc, OPAQUE);
974 SetBkColor(hdc,lpnmlvcd->clrTextBk);
976 else
977 SetBkMode(hdc, TRANSPARENT);
978 SetTextColor(hdc, lpnmlvcd->clrText);
981 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
983 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
986 /******** Item iterator functions **********************************/
988 static RANGES ranges_create(int count);
989 static void ranges_destroy(RANGES ranges);
990 static BOOL ranges_add(RANGES ranges, RANGE range);
991 static BOOL ranges_del(RANGES ranges, RANGE range);
992 static void ranges_dump(RANGES ranges);
994 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
996 RANGE range = { nItem, nItem + 1 };
998 return ranges_add(ranges, range);
1001 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1003 RANGE range = { nItem, nItem + 1 };
1005 return ranges_del(ranges, range);
1008 /***
1009 * ITERATOR DOCUMENTATION
1011 * The iterator functions allow for easy, and convenient iteration
1012 * over items of interest in the list. Typically, you create a
1013 * iterator, use it, and destroy it, as such:
1014 * ITERATOR i;
1016 * iterator_xxxitems(&i, ...);
1017 * while (iterator_{prev,next}(&i)
1019 * //code which uses i.nItem
1021 * iterator_destroy(&i);
1023 * where xxx is either: framed, or visible.
1024 * Note that it is important that the code destroys the iterator
1025 * after it's done with it, as the creation of the iterator may
1026 * allocate memory, which thus needs to be freed.
1028 * You can iterate both forwards, and backwards through the list,
1029 * by using iterator_next or iterator_prev respectively.
1031 * Lower numbered items are draw on top of higher number items in
1032 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1033 * items may overlap). So, to test items, you should use
1034 * iterator_next
1035 * which lists the items top to bottom (in Z-order).
1036 * For drawing items, you should use
1037 * iterator_prev
1038 * which lists the items bottom to top (in Z-order).
1039 * If you keep iterating over the items after the end-of-items
1040 * marker (-1) is returned, the iterator will start from the
1041 * beginning. Typically, you don't need to test for -1,
1042 * because iterator_{next,prev} will return TRUE if more items
1043 * are to be iterated over, or FALSE otherwise.
1045 * Note: the iterator is defined to be bidirectional. That is,
1046 * any number of prev followed by any number of next, or
1047 * five versa, should leave the iterator at the same item:
1048 * prev * n, next * n = next * n, prev * n
1050 * The iterator has a notion of an out-of-order, special item,
1051 * which sits at the start of the list. This is used in
1052 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1053 * which needs to be first, as it may overlap other items.
1055 * The code is a bit messy because we have:
1056 * - a special item to deal with
1057 * - simple range, or composite range
1058 * - empty range.
1059 * If you find bugs, or want to add features, please make sure you
1060 * always check/modify *both* iterator_prev, and iterator_next.
1063 /****
1064 * This function iterates through the items in increasing order,
1065 * but prefixed by the special item, then -1. That is:
1066 * special, 1, 2, 3, ..., n, -1.
1067 * Each item is listed only once.
1069 static inline BOOL iterator_next(ITERATOR* i)
1071 if (i->nItem == -1)
1073 i->nItem = i->nSpecial;
1074 if (i->nItem != -1) return TRUE;
1076 if (i->nItem == i->nSpecial)
1078 if (i->ranges) i->index = 0;
1079 goto pickarange;
1082 i->nItem++;
1083 testitem:
1084 if (i->nItem == i->nSpecial) i->nItem++;
1085 if (i->nItem < i->range.upper) return TRUE;
1087 pickarange:
1088 if (i->ranges)
1090 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1091 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1092 else goto end;
1094 else if (i->nItem >= i->range.upper) goto end;
1096 i->nItem = i->range.lower;
1097 if (i->nItem >= 0) goto testitem;
1098 end:
1099 i->nItem = -1;
1100 return FALSE;
1103 /****
1104 * This function iterates through the items in decreasing order,
1105 * followed by the special item, then -1. That is:
1106 * n, n-1, ..., 3, 2, 1, special, -1.
1107 * Each item is listed only once.
1109 static inline BOOL iterator_prev(ITERATOR* i)
1111 BOOL start = FALSE;
1113 if (i->nItem == -1)
1115 start = TRUE;
1116 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1117 goto pickarange;
1119 if (i->nItem == i->nSpecial)
1121 i->nItem = -1;
1122 return FALSE;
1125 testitem:
1126 i->nItem--;
1127 if (i->nItem == i->nSpecial) i->nItem--;
1128 if (i->nItem >= i->range.lower) return TRUE;
1130 pickarange:
1131 if (i->ranges)
1133 if (i->index > 0)
1134 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1135 else goto end;
1137 else if (!start && i->nItem < i->range.lower) goto end;
1139 i->nItem = i->range.upper;
1140 if (i->nItem > 0) goto testitem;
1141 end:
1142 return (i->nItem = i->nSpecial) != -1;
1145 static RANGE iterator_range(const ITERATOR *i)
1147 RANGE range;
1149 if (!i->ranges) return i->range;
1151 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1153 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1154 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1156 else range.lower = range.upper = 0;
1158 return range;
1161 /***
1162 * Releases resources associated with this ierator.
1164 static inline void iterator_destroy(const ITERATOR *i)
1166 ranges_destroy(i->ranges);
1169 /***
1170 * Create an empty iterator.
1172 static inline BOOL iterator_empty(ITERATOR* i)
1174 ZeroMemory(i, sizeof(*i));
1175 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1176 return TRUE;
1179 /***
1180 * Create an iterator over a range.
1182 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1184 iterator_empty(i);
1185 i->range = range;
1186 return TRUE;
1189 /***
1190 * Create an iterator over a bunch of ranges.
1191 * Please note that the iterator will take ownership of the ranges,
1192 * and will free them upon destruction.
1194 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1196 iterator_empty(i);
1197 i->ranges = ranges;
1198 return TRUE;
1201 /***
1202 * Creates an iterator over the items which intersect lprc.
1204 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1206 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1207 RECT frame = *lprc, rcItem, rcTemp;
1208 POINT Origin;
1210 /* in case we fail, we want to return an empty iterator */
1211 if (!iterator_empty(i)) return FALSE;
1213 LISTVIEW_GetOrigin(infoPtr, &Origin);
1215 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1216 OffsetRect(&frame, -Origin.x, -Origin.y);
1218 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1220 INT nItem;
1222 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1224 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1225 if (IntersectRect(&rcTemp, &rcItem, lprc))
1226 i->nSpecial = infoPtr->nFocusedItem;
1228 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1229 /* to do better here, we need to have PosX, and PosY sorted */
1230 TRACE("building icon ranges:\n");
1231 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1233 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1234 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1235 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1236 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1237 if (IntersectRect(&rcTemp, &rcItem, &frame))
1238 ranges_additem(i->ranges, nItem);
1240 return TRUE;
1242 else if (uView == LVS_REPORT)
1244 RANGE range;
1246 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1247 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1249 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1250 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1251 if (range.upper <= range.lower) return TRUE;
1252 if (!iterator_rangeitems(i, range)) return FALSE;
1253 TRACE(" report=%s\n", debugrange(&i->range));
1255 else
1257 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1258 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1259 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1260 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1261 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1262 INT lower = nFirstCol * nPerCol + nFirstRow;
1263 RANGE item_range;
1264 INT nCol;
1266 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1267 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1269 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1271 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1272 TRACE("building list ranges:\n");
1273 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1275 item_range.lower = nCol * nPerCol + nFirstRow;
1276 if(item_range.lower >= infoPtr->nItemCount) break;
1277 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1278 TRACE(" list=%s\n", debugrange(&item_range));
1279 ranges_add(i->ranges, item_range);
1283 return TRUE;
1286 /***
1287 * Creates an iterator over the items which intersect the visible region of hdc.
1289 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1291 POINT Origin, Position;
1292 RECT rcItem, rcClip;
1293 INT rgntype;
1295 rgntype = GetClipBox(hdc, &rcClip);
1296 if (rgntype == NULLREGION) return iterator_empty(i);
1297 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1298 if (rgntype == SIMPLEREGION) return TRUE;
1300 /* first deal with the special item */
1301 if (i->nSpecial != -1)
1303 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1304 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1307 /* if we can't deal with the region, we'll just go with the simple range */
1308 LISTVIEW_GetOrigin(infoPtr, &Origin);
1309 TRACE("building visible range:\n");
1310 if (!i->ranges && i->range.lower < i->range.upper)
1312 if (!(i->ranges = ranges_create(50))) return TRUE;
1313 if (!ranges_add(i->ranges, i->range))
1315 ranges_destroy(i->ranges);
1316 i->ranges = 0;
1317 return TRUE;
1321 /* now delete the invisible items from the list */
1322 while(iterator_next(i))
1324 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1325 rcItem.left = Position.x + Origin.x;
1326 rcItem.top = Position.y + Origin.y;
1327 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1328 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1329 if (!RectVisible(hdc, &rcItem))
1330 ranges_delitem(i->ranges, i->nItem);
1332 /* the iterator should restart on the next iterator_next */
1333 TRACE("done\n");
1335 return TRUE;
1338 /******** Misc helper functions ************************************/
1340 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1341 WPARAM wParam, LPARAM lParam, BOOL isW)
1343 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1344 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1347 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1349 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1351 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1352 (uView == LVS_ICON || uView == LVS_SMALLICON);
1355 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1357 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1358 if(state == 1 || state == 2)
1360 LVITEMW lvitem;
1361 state ^= 3;
1362 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1363 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1364 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1368 /******** Internal API functions ************************************/
1370 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1372 static COLUMN_INFO mainItem;
1374 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1375 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1376 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1379 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1381 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1384 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1386 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1389 /* Listview invalidation functions: use _only_ these functions to invalidate */
1391 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1393 return infoPtr->bRedraw;
1396 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1398 if(!is_redrawing(infoPtr)) return;
1399 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1400 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1403 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1405 RECT rcBox;
1407 if(!is_redrawing(infoPtr)) return;
1408 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1409 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1412 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1414 POINT Origin, Position;
1415 RECT rcBox;
1417 if(!is_redrawing(infoPtr)) return;
1418 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1419 LISTVIEW_GetOrigin(infoPtr, &Origin);
1420 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1421 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1422 rcBox.top = 0;
1423 rcBox.bottom = infoPtr->nItemHeight;
1424 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1425 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1428 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1430 LISTVIEW_InvalidateRect(infoPtr, NULL);
1433 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1435 RECT rcCol;
1437 if(!is_redrawing(infoPtr)) return;
1438 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1439 rcCol.top = infoPtr->rcList.top;
1440 rcCol.bottom = infoPtr->rcList.bottom;
1441 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1444 /***
1445 * DESCRIPTION:
1446 * Retrieves the number of items that can fit vertically in the client area.
1448 * PARAMETER(S):
1449 * [I] infoPtr : valid pointer to the listview structure
1451 * RETURN:
1452 * Number of items per row.
1454 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1456 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1458 return max(nListWidth/infoPtr->nItemWidth, 1);
1461 /***
1462 * DESCRIPTION:
1463 * Retrieves the number of items that can fit horizontally in the client
1464 * area.
1466 * PARAMETER(S):
1467 * [I] infoPtr : valid pointer to the listview structure
1469 * RETURN:
1470 * Number of items per column.
1472 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1474 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1476 return max(nListHeight / infoPtr->nItemHeight, 1);
1480 /*************************************************************************
1481 * LISTVIEW_ProcessLetterKeys
1483 * Processes keyboard messages generated by pressing the letter keys
1484 * on the keyboard.
1485 * What this does is perform a case insensitive search from the
1486 * current position with the following quirks:
1487 * - If two chars or more are pressed in quick succession we search
1488 * for the corresponding string (e.g. 'abc').
1489 * - If there is a delay we wipe away the current search string and
1490 * restart with just that char.
1491 * - If the user keeps pressing the same character, whether slowly or
1492 * fast, so that the search string is entirely composed of this
1493 * character ('aaaaa' for instance), then we search for first item
1494 * that starting with that character.
1495 * - If the user types the above character in quick succession, then
1496 * we must also search for the corresponding string ('aaaaa'), and
1497 * go to that string if there is a match.
1499 * PARAMETERS
1500 * [I] hwnd : handle to the window
1501 * [I] charCode : the character code, the actual character
1502 * [I] keyData : key data
1504 * RETURNS
1506 * Zero.
1508 * BUGS
1510 * - The current implementation has a list of characters it will
1511 * accept and it ignores everything else. In particular it will
1512 * ignore accentuated characters which seems to match what
1513 * Windows does. But I'm not sure it makes sense to follow
1514 * Windows there.
1515 * - We don't sound a beep when the search fails.
1517 * SEE ALSO
1519 * TREEVIEW_ProcessLetterKeys
1521 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1523 INT nItem;
1524 INT endidx,idx;
1525 LVITEMW item;
1526 WCHAR buffer[MAX_PATH];
1527 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1529 /* simple parameter checking */
1530 if (!charCode || !keyData) return 0;
1532 /* only allow the valid WM_CHARs through */
1533 if (!isalnum(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 charCode != '<' && charCode != ',' && charCode != '~')
1543 return 0;
1545 /* if there's one item or less, there is no where to go */
1546 if (infoPtr->nItemCount <= 1) return 0;
1548 /* update the search parameters */
1549 infoPtr->lastKeyPressTimestamp = GetTickCount();
1550 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1551 if (infoPtr->nSearchParamLength < MAX_PATH)
1552 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1553 if (infoPtr->charCode != charCode)
1554 infoPtr->charCode = charCode = 0;
1555 } else {
1556 infoPtr->charCode=charCode;
1557 infoPtr->szSearchParam[0]=charCode;
1558 infoPtr->nSearchParamLength=1;
1559 /* Redundant with the 1 char string */
1560 charCode=0;
1563 /* and search from the current position */
1564 nItem=-1;
1565 if (infoPtr->nFocusedItem >= 0) {
1566 endidx=infoPtr->nFocusedItem;
1567 idx=endidx;
1568 /* if looking for single character match,
1569 * then we must always move forward
1571 if (infoPtr->nSearchParamLength == 1)
1572 idx++;
1573 } else {
1574 endidx=infoPtr->nItemCount;
1575 idx=0;
1577 do {
1578 if (idx == infoPtr->nItemCount) {
1579 if (endidx == infoPtr->nItemCount || endidx == 0)
1580 break;
1581 idx=0;
1584 /* get item */
1585 item.mask = LVIF_TEXT;
1586 item.iItem = idx;
1587 item.iSubItem = 0;
1588 item.pszText = buffer;
1589 item.cchTextMax = MAX_PATH;
1590 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1592 /* check for a match */
1593 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1594 nItem=idx;
1595 break;
1596 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1597 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1598 /* This would work but we must keep looking for a longer match */
1599 nItem=idx;
1601 idx++;
1602 } while (idx != endidx);
1604 if (nItem != -1)
1605 LISTVIEW_KeySelection(infoPtr, nItem);
1607 return 0;
1610 /*************************************************************************
1611 * LISTVIEW_UpdateHeaderSize [Internal]
1613 * Function to resize the header control
1615 * PARAMS
1616 * [I] hwnd : handle to a window
1617 * [I] nNewScrollPos : scroll pos to set
1619 * RETURNS
1620 * None.
1622 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1624 RECT winRect;
1625 POINT point[2];
1627 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1629 GetWindowRect(infoPtr->hwndHeader, &winRect);
1630 point[0].x = winRect.left;
1631 point[0].y = winRect.top;
1632 point[1].x = winRect.right;
1633 point[1].y = winRect.bottom;
1635 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1636 point[0].x = -nNewScrollPos;
1637 point[1].x += nNewScrollPos;
1639 SetWindowPos(infoPtr->hwndHeader,0,
1640 point[0].x,point[0].y,point[1].x,point[1].y,
1641 SWP_NOZORDER | SWP_NOACTIVATE);
1644 /***
1645 * DESCRIPTION:
1646 * Update the scrollbars. This functions should be called whenever
1647 * the content, size or view changes.
1649 * PARAMETER(S):
1650 * [I] infoPtr : valid pointer to the listview structure
1652 * RETURN:
1653 * None
1655 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1657 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1658 SCROLLINFO horzInfo, vertInfo;
1659 INT dx, dy;
1661 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1663 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1664 horzInfo.cbSize = sizeof(SCROLLINFO);
1665 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1667 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1668 if (uView == LVS_LIST)
1670 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1671 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1673 /* scroll by at least one column per page */
1674 if(horzInfo.nPage < infoPtr->nItemWidth)
1675 horzInfo.nPage = infoPtr->nItemWidth;
1677 horzInfo.nPage /= infoPtr->nItemWidth;
1679 else if (uView == LVS_REPORT)
1681 horzInfo.nMax = infoPtr->nItemWidth;
1683 else /* LVS_ICON, or LVS_SMALLICON */
1685 RECT rcView;
1687 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1690 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1691 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1692 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1693 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1694 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1696 /* Setting the horizontal scroll can change the listview size
1697 * (and potentially everything else) so we need to recompute
1698 * everything again for the vertical scroll
1701 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1702 vertInfo.cbSize = sizeof(SCROLLINFO);
1703 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1705 if (uView == LVS_REPORT)
1707 vertInfo.nMax = infoPtr->nItemCount;
1709 /* scroll by at least one page */
1710 if(vertInfo.nPage < infoPtr->nItemHeight)
1711 vertInfo.nPage = infoPtr->nItemHeight;
1713 vertInfo.nPage /= infoPtr->nItemHeight;
1715 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1717 RECT rcView;
1719 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1722 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1723 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1724 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1725 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1726 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1728 /* Change of the range may have changed the scroll pos. If so move the content */
1729 if (dx != 0 || dy != 0)
1731 RECT listRect;
1732 listRect = infoPtr->rcList;
1733 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1734 SW_ERASE | SW_INVALIDATE);
1737 /* Update the Header Control */
1738 if (uView == LVS_REPORT)
1740 horzInfo.fMask = SIF_POS;
1741 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1742 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1747 /***
1748 * DESCRIPTION:
1749 * Shows/hides the focus rectangle.
1751 * PARAMETER(S):
1752 * [I] infoPtr : valid pointer to the listview structure
1753 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1755 * RETURN:
1756 * None
1758 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1760 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1761 HDC hdc;
1763 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1765 if (infoPtr->nFocusedItem < 0) return;
1767 /* we need some gymnastics in ICON mode to handle large items */
1768 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1770 RECT rcBox;
1772 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1773 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1775 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1776 return;
1780 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1782 /* for some reason, owner draw should work only in report mode */
1783 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1785 DRAWITEMSTRUCT dis;
1786 LVITEMW item;
1788 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1789 HFONT hOldFont = SelectObject(hdc, hFont);
1791 item.iItem = infoPtr->nFocusedItem;
1792 item.iSubItem = 0;
1793 item.mask = LVIF_PARAM;
1794 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1796 ZeroMemory(&dis, sizeof(dis));
1797 dis.CtlType = ODT_LISTVIEW;
1798 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1799 dis.itemID = item.iItem;
1800 dis.itemAction = ODA_FOCUS;
1801 if (fShow) dis.itemState |= ODS_FOCUS;
1802 dis.hwndItem = infoPtr->hwndSelf;
1803 dis.hDC = hdc;
1804 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1805 dis.itemData = item.lParam;
1807 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1809 SelectObject(hdc, hOldFont);
1811 else
1813 DrawFocusRect(hdc, &infoPtr->rcFocus);
1815 done:
1816 ReleaseDC(infoPtr->hwndSelf, hdc);
1819 /***
1820 * Invalidates all visible selected items.
1822 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1824 ITERATOR i;
1826 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1827 while(iterator_next(&i))
1829 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1830 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1832 iterator_destroy(&i);
1836 /***
1837 * DESCRIPTION: [INTERNAL]
1838 * Computes an item's (left,top) corner, relative to rcView.
1839 * That is, the position has NOT been made relative to the Origin.
1840 * This is deliberate, to avoid computing the Origin over, and
1841 * over again, when this function is called in a loop. Instead,
1842 * one can factor the computation of the Origin before the loop,
1843 * and offset the value returned by this function, on every iteration.
1845 * PARAMETER(S):
1846 * [I] infoPtr : valid pointer to the listview structure
1847 * [I] nItem : item number
1848 * [O] lpptOrig : item top, left corner
1850 * RETURN:
1851 * None.
1853 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1855 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1857 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1859 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1861 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1862 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1864 else if (uView == LVS_LIST)
1866 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1867 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1868 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1870 else /* LVS_REPORT */
1872 lpptPosition->x = 0;
1873 lpptPosition->y = nItem * infoPtr->nItemHeight;
1877 /***
1878 * DESCRIPTION: [INTERNAL]
1879 * Compute the rectangles of an item. This is to localize all
1880 * the computations in one place. If you are not interested in some
1881 * of these values, simply pass in a NULL -- the function is smart
1882 * enough to compute only what's necessary. The function computes
1883 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1884 * one, the BOX rectangle. This rectangle is very cheap to compute,
1885 * and is guaranteed to contain all the other rectangles. Computing
1886 * the ICON rect is also cheap, but all the others are potentially
1887 * expensive. This gives an easy and effective optimization when
1888 * searching (like point inclusion, or rectangle intersection):
1889 * first test against the BOX, and if TRUE, test against the desired
1890 * rectangle.
1891 * If the function does not have all the necessary information
1892 * to computed the requested rectangles, will crash with a
1893 * failed assertion. This is done so we catch all programming
1894 * errors, given that the function is called only from our code.
1896 * We have the following 'special' meanings for a few fields:
1897 * * If LVIS_FOCUSED is set, we assume the item has the focus
1898 * This is important in ICON mode, where it might get a larger
1899 * then usual rectangle
1901 * Please note that subitem support works only in REPORT mode.
1903 * PARAMETER(S):
1904 * [I] infoPtr : valid pointer to the listview structure
1905 * [I] lpLVItem : item to compute the measures for
1906 * [O] lprcBox : ptr to Box rectangle
1907 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1908 * [0] lprcSelectBox : ptr to select box rectangle
1909 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1910 * [O] lprcIcon : ptr to Icon rectangle
1911 * Same as LVM_GETITEMRECT with LVIR_ICON
1912 * [O] lprcStateIcon: ptr to State Icon rectangle
1913 * [O] lprcLabel : ptr to Label rectangle
1914 * Same as LVM_GETITEMRECT with LVIR_LABEL
1916 * RETURN:
1917 * None.
1919 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1920 LPRECT lprcBox, LPRECT lprcSelectBox,
1921 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1923 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1924 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1925 RECT Box, SelectBox, Icon, Label;
1926 COLUMN_INFO *lpColumnInfo = NULL;
1927 SIZE labelSize = { 0, 0 };
1929 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1931 /* Be smart and try to figure out the minimum we have to do */
1932 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1933 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1935 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1936 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1938 if (lprcSelectBox) doSelectBox = TRUE;
1939 if (lprcLabel) doLabel = TRUE;
1940 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1941 if (doSelectBox)
1943 doIcon = TRUE;
1944 doLabel = TRUE;
1947 /************************************************************/
1948 /* compute the box rectangle (it should be cheap to do) */
1949 /************************************************************/
1950 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1951 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1953 if (lpLVItem->iSubItem)
1955 Box = lpColumnInfo->rcHeader;
1957 else
1959 Box.left = 0;
1960 Box.right = infoPtr->nItemWidth;
1962 Box.top = 0;
1963 Box.bottom = infoPtr->nItemHeight;
1965 /******************************************************************/
1966 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1967 /******************************************************************/
1968 if (doIcon)
1970 LONG state_width = 0;
1972 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1973 state_width = infoPtr->iconStateSize.cx;
1975 if (uView == LVS_ICON)
1977 Icon.left = Box.left + state_width;
1978 if (infoPtr->himlNormal)
1979 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1980 Icon.top = Box.top + ICON_TOP_PADDING;
1981 Icon.right = Icon.left;
1982 Icon.bottom = Icon.top;
1983 if (infoPtr->himlNormal)
1985 Icon.right += infoPtr->iconSize.cx;
1986 Icon.bottom += infoPtr->iconSize.cy;
1989 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1991 Icon.left = Box.left + state_width;
1993 if (uView == LVS_REPORT)
1994 Icon.left += REPORT_MARGINX;
1996 Icon.top = Box.top;
1997 Icon.right = Icon.left;
1998 if (infoPtr->himlSmall &&
1999 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2000 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2001 Icon.right += infoPtr->iconSize.cx;
2002 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2004 if(lprcIcon) *lprcIcon = Icon;
2005 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2007 /* TODO: is this correct? */
2008 if (lprcStateIcon)
2010 lprcStateIcon->left = Icon.left - state_width;
2011 lprcStateIcon->right = Icon.left;
2012 lprcStateIcon->top = Icon.top;
2013 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2014 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2017 else Icon.right = 0;
2019 /************************************************************/
2020 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2021 /************************************************************/
2022 if (doLabel)
2024 /* calculate how far to the right can the label stretch */
2025 Label.right = Box.right;
2026 if (uView == LVS_REPORT)
2028 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2031 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2033 labelSize.cx = infoPtr->nItemWidth;
2034 labelSize.cy = infoPtr->nItemHeight;
2035 goto calc_label;
2038 /* we need the text in non owner draw mode */
2039 assert(lpLVItem->mask & LVIF_TEXT);
2040 if (is_textT(lpLVItem->pszText, TRUE))
2042 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2043 HDC hdc = GetDC(infoPtr->hwndSelf);
2044 HFONT hOldFont = SelectObject(hdc, hFont);
2045 UINT uFormat;
2046 RECT rcText;
2048 /* compute rough rectangle where the label will go */
2049 SetRectEmpty(&rcText);
2050 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2051 rcText.bottom = infoPtr->nItemHeight;
2052 if (uView == LVS_ICON)
2053 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2055 /* now figure out the flags */
2056 if (uView == LVS_ICON)
2057 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2058 else
2059 uFormat = LV_SL_DT_FLAGS;
2061 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2063 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2064 labelSize.cy = rcText.bottom - rcText.top;
2066 SelectObject(hdc, hOldFont);
2067 ReleaseDC(infoPtr->hwndSelf, hdc);
2070 calc_label:
2071 if (uView == LVS_ICON)
2073 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2074 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2075 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2076 Label.right = Label.left + labelSize.cx;
2077 Label.bottom = Label.top + infoPtr->nItemHeight;
2078 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2080 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2081 labelSize.cy /= infoPtr->ntmHeight;
2082 labelSize.cy = max(labelSize.cy, 1);
2083 labelSize.cy *= infoPtr->ntmHeight;
2085 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2087 else if (uView == LVS_REPORT)
2089 Label.left = Icon.right;
2090 Label.top = Box.top;
2091 Label.right = lpColumnInfo->rcHeader.right;
2092 Label.bottom = Label.top + infoPtr->nItemHeight;
2094 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2096 Label.left = Icon.right;
2097 Label.top = Box.top;
2098 Label.right = min(Label.left + labelSize.cx, Label.right);
2099 Label.bottom = Label.top + infoPtr->nItemHeight;
2102 if (lprcLabel) *lprcLabel = Label;
2103 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2106 /************************************************************/
2107 /* compute STATEICON bounding box */
2108 /************************************************************/
2109 if (doSelectBox)
2111 if (uView == LVS_REPORT)
2113 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2114 SelectBox.top = Box.top;
2115 SelectBox.bottom = Box.bottom;
2116 if (lpLVItem->iSubItem == 0)
2118 /* we need the indent in report mode */
2119 assert(lpLVItem->mask & LVIF_INDENT);
2120 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2122 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2124 else
2126 UnionRect(&SelectBox, &Icon, &Label);
2128 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2129 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2132 /* Fix the Box if necessary */
2133 if (lprcBox)
2135 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2136 else *lprcBox = Box;
2138 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2141 /***
2142 * DESCRIPTION: [INTERNAL]
2144 * PARAMETER(S):
2145 * [I] infoPtr : valid pointer to the listview structure
2146 * [I] nItem : item number
2147 * [O] lprcBox : ptr to Box rectangle
2149 * RETURN:
2150 * None.
2152 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2154 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2155 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2156 POINT Position, Origin;
2157 LVITEMW lvItem;
2159 LISTVIEW_GetOrigin(infoPtr, &Origin);
2160 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2162 /* Be smart and try to figure out the minimum we have to do */
2163 lvItem.mask = 0;
2164 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2165 lvItem.mask |= LVIF_TEXT;
2166 lvItem.iItem = nItem;
2167 lvItem.iSubItem = 0;
2168 lvItem.pszText = szDispText;
2169 lvItem.cchTextMax = DISP_TEXT_SIZE;
2170 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2171 if (uView == LVS_ICON)
2173 lvItem.mask |= LVIF_STATE;
2174 lvItem.stateMask = LVIS_FOCUSED;
2175 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2177 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2179 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2183 /***
2184 * DESCRIPTION:
2185 * Returns the current icon position, and advances it along the top.
2186 * The returned position is not offset by Origin.
2188 * PARAMETER(S):
2189 * [I] infoPtr : valid pointer to the listview structure
2190 * [O] lpPos : will get the current icon position
2192 * RETURN:
2193 * None
2195 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2197 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2199 *lpPos = infoPtr->currIconPos;
2201 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2202 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2204 infoPtr->currIconPos.x = 0;
2205 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2209 /***
2210 * DESCRIPTION:
2211 * Returns the current icon position, and advances it down the left edge.
2212 * The returned position is not offset by Origin.
2214 * PARAMETER(S):
2215 * [I] infoPtr : valid pointer to the listview structure
2216 * [O] lpPos : will get the current icon position
2218 * RETURN:
2219 * None
2221 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2223 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2225 *lpPos = infoPtr->currIconPos;
2227 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2228 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2230 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2231 infoPtr->currIconPos.y = 0;
2235 /***
2236 * DESCRIPTION:
2237 * Moves an icon to the specified position.
2238 * It takes care of invalidating the item, etc.
2240 * PARAMETER(S):
2241 * [I] infoPtr : valid pointer to the listview structure
2242 * [I] nItem : the item to move
2243 * [I] lpPos : the new icon position
2244 * [I] isNew : flags the item as being new
2246 * RETURN:
2247 * Success: TRUE
2248 * Failure: FALSE
2250 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2252 POINT old;
2254 if (!isNew)
2256 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2257 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2259 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2260 LISTVIEW_InvalidateItem(infoPtr, nItem);
2263 /* Allocating a POINTER for every item is too resource intensive,
2264 * so we'll keep the (x,y) in different arrays */
2265 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2266 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2268 LISTVIEW_InvalidateItem(infoPtr, nItem);
2270 return TRUE;
2273 /***
2274 * DESCRIPTION:
2275 * Arranges listview items in icon display mode.
2277 * PARAMETER(S):
2278 * [I] infoPtr : valid pointer to the listview structure
2279 * [I] nAlignCode : alignment code
2281 * RETURN:
2282 * SUCCESS : TRUE
2283 * FAILURE : FALSE
2285 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2287 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2288 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2289 POINT pos;
2290 INT i;
2292 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2294 TRACE("nAlignCode=%d\n", nAlignCode);
2296 if (nAlignCode == LVA_DEFAULT)
2298 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2299 else nAlignCode = LVA_ALIGNTOP;
2302 switch (nAlignCode)
2304 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2305 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2306 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2307 default: return FALSE;
2310 infoPtr->bAutoarrange = TRUE;
2311 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2312 for (i = 0; i < infoPtr->nItemCount; i++)
2314 next_pos(infoPtr, &pos);
2315 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2318 return TRUE;
2321 /***
2322 * DESCRIPTION:
2323 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2325 * PARAMETER(S):
2326 * [I] infoPtr : valid pointer to the listview structure
2327 * [O] lprcView : bounding rectangle
2329 * RETURN:
2330 * SUCCESS : TRUE
2331 * FAILURE : FALSE
2333 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2335 INT i, x, y;
2337 SetRectEmpty(lprcView);
2339 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2341 case LVS_ICON:
2342 case LVS_SMALLICON:
2343 for (i = 0; i < infoPtr->nItemCount; i++)
2345 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2346 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2347 lprcView->right = max(lprcView->right, x);
2348 lprcView->bottom = max(lprcView->bottom, y);
2350 if (infoPtr->nItemCount > 0)
2352 lprcView->right += infoPtr->nItemWidth;
2353 lprcView->bottom += infoPtr->nItemHeight;
2355 break;
2357 case LVS_LIST:
2358 y = LISTVIEW_GetCountPerColumn(infoPtr);
2359 x = infoPtr->nItemCount / y;
2360 if (infoPtr->nItemCount % y) x++;
2361 lprcView->right = x * infoPtr->nItemWidth;
2362 lprcView->bottom = y * infoPtr->nItemHeight;
2363 break;
2365 case LVS_REPORT:
2366 lprcView->right = infoPtr->nItemWidth;
2367 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2368 break;
2372 /***
2373 * DESCRIPTION:
2374 * Retrieves the bounding rectangle of all the items.
2376 * PARAMETER(S):
2377 * [I] infoPtr : valid pointer to the listview structure
2378 * [O] lprcView : bounding rectangle
2380 * RETURN:
2381 * SUCCESS : TRUE
2382 * FAILURE : FALSE
2384 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2386 POINT ptOrigin;
2388 TRACE("(lprcView=%p)\n", lprcView);
2390 if (!lprcView) return FALSE;
2392 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2393 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2394 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2396 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2398 return TRUE;
2401 /***
2402 * DESCRIPTION:
2403 * Retrieves the subitem pointer associated with the subitem index.
2405 * PARAMETER(S):
2406 * [I] hdpaSubItems : DPA handle for a specific item
2407 * [I] nSubItem : index of subitem
2409 * RETURN:
2410 * SUCCESS : subitem pointer
2411 * FAILURE : NULL
2413 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2415 SUBITEM_INFO *lpSubItem;
2416 INT i;
2418 /* we should binary search here if need be */
2419 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2421 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2422 if (lpSubItem->iSubItem == nSubItem)
2423 return lpSubItem;
2426 return NULL;
2430 /***
2431 * DESCRIPTION:
2432 * Calculates the desired item width.
2434 * PARAMETER(S):
2435 * [I] infoPtr : valid pointer to the listview structure
2437 * RETURN:
2438 * The desired item width.
2440 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2442 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2443 INT nItemWidth = 0;
2445 TRACE("uView=%d\n", uView);
2447 if (uView == LVS_ICON)
2448 nItemWidth = infoPtr->iconSpacing.cx;
2449 else if (uView == LVS_REPORT)
2451 RECT rcHeader;
2453 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2455 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2456 nItemWidth = rcHeader.right;
2459 else /* LVS_SMALLICON, or LVS_LIST */
2461 INT i;
2463 for (i = 0; i < infoPtr->nItemCount; i++)
2464 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2466 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2467 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2469 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2472 return max(nItemWidth, 1);
2475 /***
2476 * DESCRIPTION:
2477 * Calculates the desired item height.
2479 * PARAMETER(S):
2480 * [I] infoPtr : valid pointer to the listview structure
2482 * RETURN:
2483 * The desired item height.
2485 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2487 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2488 INT nItemHeight;
2490 TRACE("uView=%d\n", uView);
2492 if (uView == LVS_ICON)
2493 nItemHeight = infoPtr->iconSpacing.cy;
2494 else
2496 nItemHeight = infoPtr->ntmHeight;
2497 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2498 nItemHeight++;
2499 if (infoPtr->himlState)
2500 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2501 if (infoPtr->himlSmall)
2502 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2503 if (infoPtr->himlState || infoPtr->himlSmall)
2504 nItemHeight += HEIGHT_PADDING;
2505 if (infoPtr->nMeasureItemHeight > 0)
2506 nItemHeight = infoPtr->nMeasureItemHeight;
2509 return max(nItemHeight, 1);
2512 /***
2513 * DESCRIPTION:
2514 * Updates the width, and height of an item.
2516 * PARAMETER(S):
2517 * [I] infoPtr : valid pointer to the listview structure
2519 * RETURN:
2520 * None.
2522 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2524 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2525 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2529 /***
2530 * DESCRIPTION:
2531 * Retrieves and saves important text metrics info for the current
2532 * Listview font.
2534 * PARAMETER(S):
2535 * [I] infoPtr : valid pointer to the listview structure
2538 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2540 HDC hdc = GetDC(infoPtr->hwndSelf);
2541 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2542 HFONT hOldFont = SelectObject(hdc, hFont);
2543 TEXTMETRICW tm;
2544 SIZE sz;
2546 if (GetTextMetricsW(hdc, &tm))
2548 infoPtr->ntmHeight = tm.tmHeight;
2549 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2552 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2553 infoPtr->nEllipsisWidth = sz.cx;
2555 SelectObject(hdc, hOldFont);
2556 ReleaseDC(infoPtr->hwndSelf, hdc);
2558 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2561 /***
2562 * DESCRIPTION:
2563 * A compare function for ranges
2565 * PARAMETER(S)
2566 * [I] range1 : pointer to range 1;
2567 * [I] range2 : pointer to range 2;
2568 * [I] flags : flags
2570 * RETURNS:
2571 * > 0 : if range 1 > range 2
2572 * < 0 : if range 2 > range 1
2573 * = 0 : if range intersects range 2
2575 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2577 INT cmp;
2579 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2580 cmp = -1;
2581 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2582 cmp = 1;
2583 else
2584 cmp = 0;
2586 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2588 return cmp;
2591 #if DEBUG_RANGES
2592 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2593 #else
2594 #define ranges_check(ranges, desc) do { } while(0)
2595 #endif
2597 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2599 INT i;
2600 RANGE *prev, *curr;
2602 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2603 assert (ranges);
2604 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2605 ranges_dump(ranges);
2606 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2607 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2608 assert (prev->lower >= 0 && prev->lower < prev->upper);
2609 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2611 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2612 assert (prev->upper <= curr->lower);
2613 assert (curr->lower < curr->upper);
2614 prev = curr;
2616 TRACE("--- Done checking---\n");
2619 static RANGES ranges_create(int count)
2621 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2622 if (!ranges) return NULL;
2623 ranges->hdpa = DPA_Create(count);
2624 if (ranges->hdpa) return ranges;
2625 Free(ranges);
2626 return NULL;
2629 static void ranges_clear(RANGES ranges)
2631 INT i;
2633 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2634 Free(DPA_GetPtr(ranges->hdpa, i));
2635 DPA_DeleteAllPtrs(ranges->hdpa);
2639 static void ranges_destroy(RANGES ranges)
2641 if (!ranges) return;
2642 ranges_clear(ranges);
2643 DPA_Destroy(ranges->hdpa);
2644 Free(ranges);
2647 static RANGES ranges_clone(RANGES ranges)
2649 RANGES clone;
2650 INT i;
2652 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2654 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2656 RANGE *newrng = Alloc(sizeof(RANGE));
2657 if (!newrng) goto fail;
2658 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2659 DPA_SetPtr(clone->hdpa, i, newrng);
2661 return clone;
2663 fail:
2664 TRACE ("clone failed\n");
2665 ranges_destroy(clone);
2666 return NULL;
2669 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2671 INT i;
2673 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2674 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2676 return ranges;
2679 static void ranges_dump(RANGES ranges)
2681 INT i;
2683 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2684 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2687 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2689 RANGE srchrng = { nItem, nItem + 1 };
2691 TRACE("(nItem=%d)\n", nItem);
2692 ranges_check(ranges, "before contain");
2693 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2696 static INT ranges_itemcount(RANGES ranges)
2698 INT i, count = 0;
2700 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2702 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2703 count += sel->upper - sel->lower;
2706 return count;
2709 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2711 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2712 INT index;
2714 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2715 if (index == -1) return TRUE;
2717 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2719 chkrng = DPA_GetPtr(ranges->hdpa, index);
2720 if (chkrng->lower >= nItem)
2721 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2722 if (chkrng->upper > nItem)
2723 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2725 return TRUE;
2728 static BOOL ranges_add(RANGES ranges, RANGE range)
2730 RANGE srchrgn;
2731 INT index;
2733 TRACE("(%s)\n", debugrange(&range));
2734 ranges_check(ranges, "before add");
2736 /* try find overlapping regions first */
2737 srchrgn.lower = range.lower - 1;
2738 srchrgn.upper = range.upper + 1;
2739 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2741 if (index == -1)
2743 RANGE *newrgn;
2745 TRACE("Adding new range\n");
2747 /* create the brand new range to insert */
2748 newrgn = Alloc(sizeof(RANGE));
2749 if(!newrgn) goto fail;
2750 *newrgn = range;
2752 /* figure out where to insert it */
2753 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2754 TRACE("index=%d\n", index);
2755 if (index == -1) index = 0;
2757 /* and get it over with */
2758 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2760 Free(newrgn);
2761 goto fail;
2764 else
2766 RANGE *chkrgn, *mrgrgn;
2767 INT fromindex, mergeindex;
2769 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2770 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2772 chkrgn->lower = min(range.lower, chkrgn->lower);
2773 chkrgn->upper = max(range.upper, chkrgn->upper);
2775 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2777 /* merge now common ranges */
2778 fromindex = 0;
2779 srchrgn.lower = chkrgn->lower - 1;
2780 srchrgn.upper = chkrgn->upper + 1;
2784 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2785 if (mergeindex == -1) break;
2786 if (mergeindex == index)
2788 fromindex = index + 1;
2789 continue;
2792 TRACE("Merge with index %i\n", mergeindex);
2794 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2795 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2796 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2797 Free(mrgrgn);
2798 DPA_DeletePtr(ranges->hdpa, mergeindex);
2799 if (mergeindex < index) index --;
2800 } while(1);
2803 ranges_check(ranges, "after add");
2804 return TRUE;
2806 fail:
2807 ranges_check(ranges, "failed add");
2808 return FALSE;
2811 static BOOL ranges_del(RANGES ranges, RANGE range)
2813 RANGE *chkrgn;
2814 INT index;
2816 TRACE("(%s)\n", debugrange(&range));
2817 ranges_check(ranges, "before del");
2819 /* we don't use DPAS_SORTED here, since we need *
2820 * to find the first overlapping range */
2821 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2822 while(index != -1)
2824 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2826 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2828 /* case 1: Same range */
2829 if ( (chkrgn->upper == range.upper) &&
2830 (chkrgn->lower == range.lower) )
2832 DPA_DeletePtr(ranges->hdpa, index);
2833 break;
2835 /* case 2: engulf */
2836 else if ( (chkrgn->upper <= range.upper) &&
2837 (chkrgn->lower >= range.lower) )
2839 DPA_DeletePtr(ranges->hdpa, index);
2841 /* case 3: overlap upper */
2842 else if ( (chkrgn->upper <= range.upper) &&
2843 (chkrgn->lower < range.lower) )
2845 chkrgn->upper = range.lower;
2847 /* case 4: overlap lower */
2848 else if ( (chkrgn->upper > range.upper) &&
2849 (chkrgn->lower >= range.lower) )
2851 chkrgn->lower = range.upper;
2852 break;
2854 /* case 5: fully internal */
2855 else
2857 RANGE tmprgn = *chkrgn, *newrgn;
2859 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2860 newrgn->lower = chkrgn->lower;
2861 newrgn->upper = range.lower;
2862 chkrgn->lower = range.upper;
2863 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2865 Free(newrgn);
2866 goto fail;
2868 chkrgn = &tmprgn;
2869 break;
2872 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2875 ranges_check(ranges, "after del");
2876 return TRUE;
2878 fail:
2879 ranges_check(ranges, "failed del");
2880 return FALSE;
2883 /***
2884 * DESCRIPTION:
2885 * Removes all selection ranges
2887 * Parameters(s):
2888 * [I] infoPtr : valid pointer to the listview structure
2889 * [I] toSkip : item range to skip removing the selection
2891 * RETURNS:
2892 * SUCCESS : TRUE
2893 * FAILURE : TRUE
2895 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2897 LVITEMW lvItem;
2898 ITERATOR i;
2899 RANGES clone;
2901 TRACE("()\n");
2903 lvItem.state = 0;
2904 lvItem.stateMask = LVIS_SELECTED;
2906 /* need to clone the DPA because callbacks can change it */
2907 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2908 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2909 while(iterator_next(&i))
2910 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2911 /* note that the iterator destructor will free the cloned range */
2912 iterator_destroy(&i);
2914 return TRUE;
2917 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2919 RANGES toSkip;
2921 if (!(toSkip = ranges_create(1))) return FALSE;
2922 if (nItem != -1) ranges_additem(toSkip, nItem);
2923 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2924 ranges_destroy(toSkip);
2925 return TRUE;
2928 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2930 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2933 /***
2934 * DESCRIPTION:
2935 * Retrieves the number of items that are marked as selected.
2937 * PARAMETER(S):
2938 * [I] infoPtr : valid pointer to the listview structure
2940 * RETURN:
2941 * Number of items selected.
2943 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2945 INT nSelectedCount = 0;
2947 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2949 INT i;
2950 for (i = 0; i < infoPtr->nItemCount; i++)
2952 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2953 nSelectedCount++;
2956 else
2957 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2959 TRACE("nSelectedCount=%d\n", nSelectedCount);
2960 return nSelectedCount;
2963 /***
2964 * DESCRIPTION:
2965 * Manages the item focus.
2967 * PARAMETER(S):
2968 * [I] infoPtr : valid pointer to the listview structure
2969 * [I] nItem : item index
2971 * RETURN:
2972 * TRUE : focused item changed
2973 * FALSE : focused item has NOT changed
2975 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2977 INT oldFocus = infoPtr->nFocusedItem;
2978 LVITEMW lvItem;
2980 if (nItem == infoPtr->nFocusedItem) return FALSE;
2982 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2983 lvItem.stateMask = LVIS_FOCUSED;
2984 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2986 return oldFocus != infoPtr->nFocusedItem;
2989 /* Helper function for LISTVIEW_ShiftIndices *only* */
2990 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2992 if (nShiftItem < nItem) return nShiftItem;
2994 if (nShiftItem > nItem) return nShiftItem + direction;
2996 if (direction > 0) return nShiftItem + direction;
2998 return min(nShiftItem, infoPtr->nItemCount - 1);
3002 * DESCRIPTION:
3003 * Updates the various indices after an item has been inserted or deleted.
3005 * PARAMETER(S):
3006 * [I] infoPtr : valid pointer to the listview structure
3007 * [I] nItem : item index
3008 * [I] direction : Direction of shift, +1 or -1.
3010 * RETURN:
3011 * None
3013 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3015 INT nNewFocus;
3016 BOOL bOldChange;
3018 /* temporarily disable change notification while shifting items */
3019 bOldChange = infoPtr->bDoChangeNotify;
3020 infoPtr->bDoChangeNotify = FALSE;
3022 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3024 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3026 assert(abs(direction) == 1);
3028 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3030 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3031 if (nNewFocus != infoPtr->nFocusedItem)
3032 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3034 /* But we are not supposed to modify nHotItem! */
3036 infoPtr->bDoChangeNotify = bOldChange;
3041 * DESCRIPTION:
3042 * Adds a block of selections.
3044 * PARAMETER(S):
3045 * [I] infoPtr : valid pointer to the listview structure
3046 * [I] nItem : item index
3048 * RETURN:
3049 * Whether the window is still valid.
3051 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3053 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3054 INT nLast = max(infoPtr->nSelectionMark, nItem);
3055 HWND hwndSelf = infoPtr->hwndSelf;
3056 NMLVODSTATECHANGE nmlv;
3057 LVITEMW item;
3058 BOOL bOldChange;
3059 INT i;
3061 /* Temporarily disable change notification
3062 * If the control is LVS_OWNERDATA, we need to send
3063 * only one LVN_ODSTATECHANGED notification.
3064 * See MSDN documentation for LVN_ITEMCHANGED.
3066 bOldChange = infoPtr->bDoChangeNotify;
3067 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3069 if (nFirst == -1) nFirst = nItem;
3071 item.state = LVIS_SELECTED;
3072 item.stateMask = LVIS_SELECTED;
3074 for (i = nFirst; i <= nLast; i++)
3075 LISTVIEW_SetItemState(infoPtr,i,&item);
3077 ZeroMemory(&nmlv, sizeof(nmlv));
3078 nmlv.iFrom = nFirst;
3079 nmlv.iTo = nLast;
3080 nmlv.uNewState = 0;
3081 nmlv.uOldState = item.state;
3083 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3084 if (!IsWindow(hwndSelf))
3085 return FALSE;
3086 infoPtr->bDoChangeNotify = bOldChange;
3087 return TRUE;
3091 /***
3092 * DESCRIPTION:
3093 * Sets a single group selection.
3095 * PARAMETER(S):
3096 * [I] infoPtr : valid pointer to the listview structure
3097 * [I] nItem : item index
3099 * RETURN:
3100 * None
3102 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3104 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3105 RANGES selection;
3106 LVITEMW item;
3107 ITERATOR i;
3108 BOOL bOldChange;
3110 if (!(selection = ranges_create(100))) return;
3112 item.state = LVIS_SELECTED;
3113 item.stateMask = LVIS_SELECTED;
3115 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3117 if (infoPtr->nSelectionMark == -1)
3119 infoPtr->nSelectionMark = nItem;
3120 ranges_additem(selection, nItem);
3122 else
3124 RANGE sel;
3126 sel.lower = min(infoPtr->nSelectionMark, nItem);
3127 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3128 ranges_add(selection, sel);
3131 else
3133 RECT rcItem, rcSel, rcSelMark;
3134 POINT ptItem;
3136 rcItem.left = LVIR_BOUNDS;
3137 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3138 rcSelMark.left = LVIR_BOUNDS;
3139 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3140 UnionRect(&rcSel, &rcItem, &rcSelMark);
3141 iterator_frameditems(&i, infoPtr, &rcSel);
3142 while(iterator_next(&i))
3144 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3145 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3147 iterator_destroy(&i);
3150 bOldChange = infoPtr->bDoChangeNotify;
3151 infoPtr->bDoChangeNotify = FALSE;
3153 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3156 iterator_rangesitems(&i, selection);
3157 while(iterator_next(&i))
3158 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3159 /* this will also destroy the selection */
3160 iterator_destroy(&i);
3162 infoPtr->bDoChangeNotify = bOldChange;
3164 LISTVIEW_SetItemFocus(infoPtr, nItem);
3167 /***
3168 * DESCRIPTION:
3169 * Sets a single selection.
3171 * PARAMETER(S):
3172 * [I] infoPtr : valid pointer to the listview structure
3173 * [I] nItem : item index
3175 * RETURN:
3176 * None
3178 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3180 LVITEMW lvItem;
3182 TRACE("nItem=%d\n", nItem);
3184 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3186 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3187 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3188 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3190 infoPtr->nSelectionMark = nItem;
3193 /***
3194 * DESCRIPTION:
3195 * Set selection(s) with keyboard.
3197 * PARAMETER(S):
3198 * [I] infoPtr : valid pointer to the listview structure
3199 * [I] nItem : item index
3201 * RETURN:
3202 * SUCCESS : TRUE (needs to be repainted)
3203 * FAILURE : FALSE (nothing has changed)
3205 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3207 /* FIXME: pass in the state */
3208 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3209 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3210 BOOL bResult = FALSE;
3212 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3213 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3215 if (infoPtr->dwStyle & LVS_SINGLESEL)
3217 bResult = TRUE;
3218 LISTVIEW_SetSelection(infoPtr, nItem);
3220 else
3222 if (wShift)
3224 bResult = TRUE;
3225 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3227 else if (wCtrl)
3229 LVITEMW lvItem;
3230 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3231 lvItem.stateMask = LVIS_SELECTED;
3232 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3234 if (lvItem.state & LVIS_SELECTED)
3235 infoPtr->nSelectionMark = nItem;
3237 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3239 else
3241 bResult = TRUE;
3242 LISTVIEW_SetSelection(infoPtr, nItem);
3245 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3248 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3249 return bResult;
3252 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3254 LVHITTESTINFO lvHitTestInfo;
3256 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3257 lvHitTestInfo.pt.x = pt.x;
3258 lvHitTestInfo.pt.y = pt.y;
3260 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3262 lpLVItem->mask = LVIF_PARAM;
3263 lpLVItem->iItem = lvHitTestInfo.iItem;
3264 lpLVItem->iSubItem = 0;
3266 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3269 /***
3270 * DESCRIPTION:
3271 * Called when the mouse is being actively tracked and has hovered for a specified
3272 * amount of time
3274 * PARAMETER(S):
3275 * [I] infoPtr : valid pointer to the listview structure
3276 * [I] fwKeys : key indicator
3277 * [I] x,y : mouse position
3279 * RETURN:
3280 * 0 if the message was processed, non-zero if there was an error
3282 * INFO:
3283 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3284 * over the item for a certain period of time.
3287 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3289 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3291 LVITEMW item;
3292 POINT pt;
3294 pt.x = x;
3295 pt.y = y;
3297 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3298 LISTVIEW_SetSelection(infoPtr, item.iItem);
3301 return 0;
3304 /***
3305 * DESCRIPTION:
3306 * Called whenever WM_MOUSEMOVE is received.
3308 * PARAMETER(S):
3309 * [I] infoPtr : valid pointer to the listview structure
3310 * [I] fwKeys : key indicator
3311 * [I] x,y : mouse position
3313 * RETURN:
3314 * 0 if the message is processed, non-zero if there was an error
3316 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3318 TRACKMOUSEEVENT trackinfo;
3320 if (!(fwKeys & MK_LBUTTON))
3321 infoPtr->bLButtonDown = FALSE;
3323 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3325 LVHITTESTINFO lvHitTestInfo;
3326 NMLISTVIEW nmlv;
3328 lvHitTestInfo.pt = infoPtr->ptClickPos;
3329 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3331 ZeroMemory(&nmlv, sizeof(nmlv));
3332 nmlv.iItem = lvHitTestInfo.iItem;
3333 nmlv.ptAction = infoPtr->ptClickPos;
3335 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3337 return 0;
3339 else
3340 infoPtr->bLButtonDown = FALSE;
3342 /* see if we are supposed to be tracking mouse hovering */
3343 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3344 /* fill in the trackinfo struct */
3345 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3346 trackinfo.dwFlags = TME_QUERY;
3347 trackinfo.hwndTrack = infoPtr->hwndSelf;
3348 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3350 /* see if we are already tracking this hwnd */
3351 _TrackMouseEvent(&trackinfo);
3353 if(!(trackinfo.dwFlags & TME_HOVER)) {
3354 trackinfo.dwFlags = TME_HOVER;
3356 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3357 _TrackMouseEvent(&trackinfo);
3361 return 0;
3365 /***
3366 * Tests whether the item is assignable to a list with style lStyle
3368 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3370 if ( (lpLVItem->mask & LVIF_TEXT) &&
3371 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3372 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3374 return TRUE;
3378 /***
3379 * DESCRIPTION:
3380 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3382 * PARAMETER(S):
3383 * [I] infoPtr : valid pointer to the listview structure
3384 * [I] lpLVItem : valid pointer to new item attributes
3385 * [I] isNew : the item being set is being inserted
3386 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3387 * [O] bChanged : will be set to TRUE if the item really changed
3389 * RETURN:
3390 * SUCCESS : TRUE
3391 * FAILURE : FALSE
3393 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3395 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3396 ITEM_INFO *lpItem;
3397 NMLISTVIEW nmlv;
3398 UINT uChanged = 0;
3399 LVITEMW item;
3401 TRACE("()\n");
3403 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3405 if (lpLVItem->mask == 0) return TRUE;
3407 if (infoPtr->dwStyle & LVS_OWNERDATA)
3409 /* a virtual listview only stores selection and focus */
3410 if (lpLVItem->mask & ~LVIF_STATE)
3411 return FALSE;
3412 lpItem = NULL;
3414 else
3416 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3417 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3418 assert (lpItem);
3421 /* we need to get the lParam and state of the item */
3422 item.iItem = lpLVItem->iItem;
3423 item.iSubItem = lpLVItem->iSubItem;
3424 item.mask = LVIF_STATE | LVIF_PARAM;
3425 item.stateMask = ~0;
3426 item.state = 0;
3427 item.lParam = 0;
3428 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3430 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3431 /* determine what fields will change */
3432 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3433 uChanged |= LVIF_STATE;
3435 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3436 uChanged |= LVIF_IMAGE;
3438 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3439 uChanged |= LVIF_PARAM;
3441 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3442 uChanged |= LVIF_INDENT;
3444 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3445 uChanged |= LVIF_TEXT;
3447 TRACE("uChanged=0x%x\n", uChanged);
3448 if (!uChanged) return TRUE;
3449 *bChanged = TRUE;
3451 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3452 nmlv.iItem = lpLVItem->iItem;
3453 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3454 nmlv.uOldState = item.state;
3455 nmlv.uChanged = uChanged;
3456 nmlv.lParam = item.lParam;
3458 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3459 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3460 /* are enabled */
3461 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3463 HWND hwndSelf = infoPtr->hwndSelf;
3465 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3466 return FALSE;
3467 if (!IsWindow(hwndSelf))
3468 return FALSE;
3471 /* copy information */
3472 if (lpLVItem->mask & LVIF_TEXT)
3473 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3475 if (lpLVItem->mask & LVIF_IMAGE)
3476 lpItem->hdr.iImage = lpLVItem->iImage;
3478 if (lpLVItem->mask & LVIF_PARAM)
3479 lpItem->lParam = lpLVItem->lParam;
3481 if (lpLVItem->mask & LVIF_INDENT)
3482 lpItem->iIndent = lpLVItem->iIndent;
3484 if (uChanged & LVIF_STATE)
3486 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3488 lpItem->state &= ~lpLVItem->stateMask;
3489 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3491 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3493 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3494 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3496 else if (lpLVItem->stateMask & LVIS_SELECTED)
3497 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3499 /* if we are asked to change focus, and we manage it, do it */
3500 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3502 if (lpLVItem->state & LVIS_FOCUSED)
3504 LISTVIEW_SetItemFocus(infoPtr, -1);
3505 infoPtr->nFocusedItem = lpLVItem->iItem;
3506 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3508 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3509 infoPtr->nFocusedItem = -1;
3513 /* if we're inserting the item, we're done */
3514 if (isNew) return TRUE;
3516 /* send LVN_ITEMCHANGED notification */
3517 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3518 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3520 return TRUE;
3523 /***
3524 * DESCRIPTION:
3525 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3527 * PARAMETER(S):
3528 * [I] infoPtr : valid pointer to the listview structure
3529 * [I] lpLVItem : valid pointer to new subitem attributes
3530 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3531 * [O] bChanged : will be set to TRUE if the item really changed
3533 * RETURN:
3534 * SUCCESS : TRUE
3535 * FAILURE : FALSE
3537 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3539 HDPA hdpaSubItems;
3540 SUBITEM_INFO *lpSubItem;
3542 /* we do not support subitems for virtual listviews */
3543 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3545 /* set subitem only if column is present */
3546 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3548 /* First do some sanity checks */
3549 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3550 particularly useful. We currently do not actually do anything with
3551 the flag on subitems.
3553 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3554 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3556 /* get the subitem structure, and create it if not there */
3557 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3558 assert (hdpaSubItems);
3560 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3561 if (!lpSubItem)
3563 SUBITEM_INFO *tmpSubItem;
3564 INT i;
3566 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3567 if (!lpSubItem) return FALSE;
3568 /* we could binary search here, if need be...*/
3569 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3571 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3572 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3574 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3576 Free(lpSubItem);
3577 return FALSE;
3579 lpSubItem->iSubItem = lpLVItem->iSubItem;
3580 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3581 *bChanged = TRUE;
3584 if (lpLVItem->mask & LVIF_IMAGE)
3585 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3587 lpSubItem->hdr.iImage = lpLVItem->iImage;
3588 *bChanged = TRUE;
3591 if (lpLVItem->mask & LVIF_TEXT)
3592 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3594 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3595 *bChanged = TRUE;
3598 return TRUE;
3601 /***
3602 * DESCRIPTION:
3603 * Sets item attributes.
3605 * PARAMETER(S):
3606 * [I] infoPtr : valid pointer to the listview structure
3607 * [I] lpLVItem : new item attributes
3608 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3610 * RETURN:
3611 * SUCCESS : TRUE
3612 * FAILURE : FALSE
3614 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3616 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3617 HWND hwndSelf = infoPtr->hwndSelf;
3618 LPWSTR pszText = NULL;
3619 BOOL bResult, bChanged = FALSE;
3621 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3623 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3624 return FALSE;
3626 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3627 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3629 pszText = lpLVItem->pszText;
3630 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3633 /* actually set the fields */
3634 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3636 if (lpLVItem->iSubItem)
3637 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3638 else
3639 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3640 if (!IsWindow(hwndSelf))
3641 return FALSE;
3643 /* redraw item, if necessary */
3644 if (bChanged && !infoPtr->bIsDrawing)
3646 /* this little optimization eliminates some nasty flicker */
3647 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3648 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3649 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3650 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3651 else
3652 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3654 /* restore text */
3655 if (pszText)
3657 textfreeT(lpLVItem->pszText, isW);
3658 lpLVItem->pszText = pszText;
3661 return bResult;
3664 /***
3665 * DESCRIPTION:
3666 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3668 * PARAMETER(S):
3669 * [I] infoPtr : valid pointer to the listview structure
3671 * RETURN:
3672 * item index
3674 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3676 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3677 INT nItem = 0;
3678 SCROLLINFO scrollInfo;
3680 scrollInfo.cbSize = sizeof(SCROLLINFO);
3681 scrollInfo.fMask = SIF_POS;
3683 if (uView == LVS_LIST)
3685 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3686 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3688 else if (uView == LVS_REPORT)
3690 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3691 nItem = scrollInfo.nPos;
3693 else
3695 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3696 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3699 TRACE("nItem=%d\n", nItem);
3701 return nItem;
3705 /***
3706 * DESCRIPTION:
3707 * Erases the background of the given rectangle
3709 * PARAMETER(S):
3710 * [I] infoPtr : valid pointer to the listview structure
3711 * [I] hdc : device context handle
3712 * [I] lprcBox : clipping rectangle
3714 * RETURN:
3715 * Success: TRUE
3716 * Failure: FALSE
3718 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3720 if (!infoPtr->hBkBrush) return FALSE;
3722 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3724 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3727 /***
3728 * DESCRIPTION:
3729 * Draws an item.
3731 * PARAMETER(S):
3732 * [I] infoPtr : valid pointer to the listview structure
3733 * [I] hdc : device context handle
3734 * [I] nItem : item index
3735 * [I] nSubItem : subitem index
3736 * [I] pos : item position in client coordinates
3737 * [I] cdmode : custom draw mode
3739 * RETURN:
3740 * Success: TRUE
3741 * Failure: FALSE
3743 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3745 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3746 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3747 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3748 DWORD cdsubitemmode = CDRF_DODEFAULT;
3749 LPRECT lprcFocus;
3750 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3751 NMLVCUSTOMDRAW nmlvcd;
3752 HIMAGELIST himl;
3753 LVITEMW lvItem;
3754 HFONT hOldFont;
3756 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3758 /* get information needed for drawing the item */
3759 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3760 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3761 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3762 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3763 lvItem.iItem = nItem;
3764 lvItem.iSubItem = nSubItem;
3765 lvItem.state = 0;
3766 lvItem.lParam = 0;
3767 lvItem.cchTextMax = DISP_TEXT_SIZE;
3768 lvItem.pszText = szDispText;
3769 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3770 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3771 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3772 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3773 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3775 /* now check if we need to update the focus rectangle */
3776 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3778 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3779 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3780 OffsetRect(&rcBox, pos.x, pos.y);
3781 OffsetRect(&rcSelect, pos.x, pos.y);
3782 OffsetRect(&rcIcon, pos.x, pos.y);
3783 OffsetRect(&rcStateIcon, pos.x, pos.y);
3784 OffsetRect(&rcLabel, pos.x, pos.y);
3785 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3786 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3787 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3789 /* fill in the custom draw structure */
3790 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3792 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3793 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3794 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3795 if (cdmode & CDRF_NOTIFYITEMDRAW)
3796 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3797 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3798 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3799 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3800 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3802 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3803 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3805 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3806 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3807 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3808 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3810 /* in full row select, subitems, will just use main item's colors */
3811 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3812 nmlvcd.clrTextBk = CLR_NONE;
3814 /* state icons */
3815 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3817 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3818 if (uStateImage)
3820 TRACE("uStateImage=%d\n", uStateImage);
3821 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3822 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3826 /* small icons */
3827 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3828 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3830 TRACE("iImage=%d\n", lvItem.iImage);
3831 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3832 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3833 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3836 /* Don't bother painting item being edited */
3837 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3839 /* FIXME: temporary hack */
3840 rcSelect.left = rcLabel.left;
3842 /* draw the selection background, if we're drawing the main item */
3843 if (nSubItem == 0)
3845 /* in icon mode, the label rect is really what we want to draw the
3846 * background for */
3847 if (uView == LVS_ICON)
3848 rcSelect = rcLabel;
3850 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3851 rcSelect.right = rcBox.right;
3853 if (nmlvcd.clrTextBk != CLR_NONE)
3854 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3855 if(lprcFocus) *lprcFocus = rcSelect;
3858 /* figure out the text drawing flags */
3859 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3860 if (uView == LVS_ICON)
3861 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3862 else if (nSubItem)
3864 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3866 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3867 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3868 default: uFormat |= DT_LEFT;
3871 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3873 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3874 else rcLabel.left += LABEL_HOR_PADDING;
3876 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3878 /* for GRIDLINES reduce the bottom so the text formats correctly */
3879 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3880 rcLabel.bottom--;
3882 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3884 postpaint:
3885 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3886 notify_postpaint(infoPtr, &nmlvcd);
3887 if (cdsubitemmode & CDRF_NEWFONT)
3888 SelectObject(hdc, hOldFont);
3889 return TRUE;
3892 /***
3893 * DESCRIPTION:
3894 * Draws listview items when in owner draw mode.
3896 * PARAMETER(S):
3897 * [I] infoPtr : valid pointer to the listview structure
3898 * [I] hdc : device context handle
3900 * RETURN:
3901 * None
3903 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3905 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3906 DWORD cditemmode = CDRF_DODEFAULT;
3907 NMLVCUSTOMDRAW nmlvcd;
3908 POINT Origin, Position;
3909 DRAWITEMSTRUCT dis;
3910 LVITEMW item;
3912 TRACE("()\n");
3914 ZeroMemory(&dis, sizeof(dis));
3916 /* Get scroll info once before loop */
3917 LISTVIEW_GetOrigin(infoPtr, &Origin);
3919 /* iterate through the invalidated rows */
3920 while(iterator_next(i))
3922 item.iItem = i->nItem;
3923 item.iSubItem = 0;
3924 item.mask = LVIF_PARAM | LVIF_STATE;
3925 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3926 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3928 dis.CtlType = ODT_LISTVIEW;
3929 dis.CtlID = uID;
3930 dis.itemID = item.iItem;
3931 dis.itemAction = ODA_DRAWENTIRE;
3932 dis.itemState = 0;
3933 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3934 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3935 dis.hwndItem = infoPtr->hwndSelf;
3936 dis.hDC = hdc;
3937 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3938 dis.rcItem.left = Position.x + Origin.x;
3939 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3940 dis.rcItem.top = Position.y + Origin.y;
3941 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3942 dis.itemData = item.lParam;
3944 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3947 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3948 * structure for the rest. of the paint cycle
3950 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3951 if (cdmode & CDRF_NOTIFYITEMDRAW)
3952 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3954 if (!(cditemmode & CDRF_SKIPDEFAULT))
3956 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3957 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3960 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3961 notify_postpaint(infoPtr, &nmlvcd);
3965 /***
3966 * DESCRIPTION:
3967 * Draws listview items when in report display mode.
3969 * PARAMETER(S):
3970 * [I] infoPtr : valid pointer to the listview structure
3971 * [I] hdc : device context handle
3972 * [I] cdmode : custom draw mode
3974 * RETURN:
3975 * None
3977 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3979 INT rgntype;
3980 RECT rcClip, rcItem;
3981 POINT Origin, Position;
3982 RANGE colRange;
3983 ITERATOR j;
3985 TRACE("()\n");
3987 /* figure out what to draw */
3988 rgntype = GetClipBox(hdc, &rcClip);
3989 if (rgntype == NULLREGION) return;
3991 /* Get scroll info once before loop */
3992 LISTVIEW_GetOrigin(infoPtr, &Origin);
3994 /* narrow down the columns we need to paint */
3995 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3997 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3998 if (rcItem.right + Origin.x >= rcClip.left) break;
4000 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4002 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4003 if (rcItem.left + Origin.x < rcClip.right) break;
4005 iterator_rangeitems(&j, colRange);
4007 /* in full row select, we _have_ to draw the main item */
4008 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4009 j.nSpecial = 0;
4011 /* iterate through the invalidated rows */
4012 while(iterator_next(i))
4014 /* iterate through the invalidated columns */
4015 while(iterator_next(&j))
4017 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4018 Position.x += Origin.x;
4019 Position.y += Origin.y;
4021 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4023 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4024 rcItem.top = 0;
4025 rcItem.bottom = infoPtr->nItemHeight;
4026 OffsetRect(&rcItem, Position.x, Position.y);
4027 if (!RectVisible(hdc, &rcItem)) continue;
4030 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4033 iterator_destroy(&j);
4036 /***
4037 * DESCRIPTION:
4038 * Draws the gridlines if necessary when in report display mode.
4040 * PARAMETER(S):
4041 * [I] infoPtr : valid pointer to the listview structure
4042 * [I] hdc : device context handle
4044 * RETURN:
4045 * None
4047 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4049 INT rgntype;
4050 INT y, itemheight;
4051 HPEN hPen, hOldPen;
4052 RECT rcClip, rcItem;
4053 POINT Origin;
4054 RANGE colRange;
4055 ITERATOR j;
4057 TRACE("()\n");
4059 /* figure out what to draw */
4060 rgntype = GetClipBox(hdc, &rcClip);
4061 if (rgntype == NULLREGION) return;
4063 /* Get scroll info once before loop */
4064 LISTVIEW_GetOrigin(infoPtr, &Origin);
4066 /* narrow down the columns we need to paint */
4067 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4069 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4070 if (rcItem.right + Origin.x >= rcClip.left) break;
4072 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4074 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4075 if (rcItem.left + Origin.x < rcClip.right) break;
4077 iterator_rangeitems(&j, colRange);
4079 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4081 hOldPen = SelectObject ( hdc, hPen );
4083 /* draw the vertical lines for the columns */
4084 iterator_rangeitems(&j, colRange);
4085 while(iterator_next(&j))
4087 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4088 if (rcItem.left == 0) continue; /* skip first column */
4089 rcItem.left += Origin.x;
4090 rcItem.right += Origin.x;
4091 rcItem.top = infoPtr->rcList.top;
4092 rcItem.bottom = infoPtr->rcList.bottom;
4093 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4094 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4095 LineTo (hdc, rcItem.left, rcItem.bottom);
4097 iterator_destroy(&j);
4099 /* draw the horizontial lines for the rows */
4100 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4101 rcItem.left = infoPtr->rcList.left + Origin.x;
4102 rcItem.right = infoPtr->rcList.right + Origin.x;
4103 rcItem.bottom = rcItem.top = Origin.y - 1;
4104 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4105 LineTo(hdc, rcItem.right, rcItem.top);
4106 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4108 rcItem.bottom = rcItem.top = y;
4109 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4110 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4111 LineTo (hdc, rcItem.right, rcItem.top);
4114 SelectObject( hdc, hOldPen );
4115 DeleteObject( hPen );
4119 /***
4120 * DESCRIPTION:
4121 * Draws listview items when in list display mode.
4123 * PARAMETER(S):
4124 * [I] infoPtr : valid pointer to the listview structure
4125 * [I] hdc : device context handle
4126 * [I] cdmode : custom draw mode
4128 * RETURN:
4129 * None
4131 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4133 POINT Origin, Position;
4135 /* Get scroll info once before loop */
4136 LISTVIEW_GetOrigin(infoPtr, &Origin);
4138 while(iterator_prev(i))
4140 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4141 Position.x += Origin.x;
4142 Position.y += Origin.y;
4144 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4149 /***
4150 * DESCRIPTION:
4151 * Draws listview items.
4153 * PARAMETER(S):
4154 * [I] infoPtr : valid pointer to the listview structure
4155 * [I] hdc : device context handle
4156 * [I] prcErase : rect to be erased before refresh (may be NULL)
4158 * RETURN:
4159 * NoneX
4161 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4163 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4164 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4165 NMLVCUSTOMDRAW nmlvcd;
4166 HFONT hOldFont = 0;
4167 DWORD cdmode;
4168 INT oldBkMode = 0;
4169 RECT rcClient;
4170 ITERATOR i;
4171 HDC hdcOrig = hdc;
4172 HBITMAP hbmp = NULL;
4174 LISTVIEW_DUMP(infoPtr);
4176 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4177 TRACE("double buffering\n");
4179 hdc = CreateCompatibleDC(hdcOrig);
4180 if (!hdc) {
4181 ERR("Failed to create DC for backbuffer\n");
4182 return;
4184 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4185 infoPtr->rcList.bottom);
4186 if (!hbmp) {
4187 ERR("Failed to create bitmap for backbuffer\n");
4188 DeleteDC(hdc);
4189 return;
4192 SelectObject(hdc, hbmp);
4193 SelectObject(hdc, infoPtr->hFont);
4194 } else {
4195 /* Save dc values we're gonna trash while drawing
4196 * FIXME: Should be done in LISTVIEW_DrawItem() */
4197 hOldFont = SelectObject(hdc, infoPtr->hFont);
4198 oldBkMode = GetBkMode(hdc);
4199 oldBkColor = GetBkColor(hdc);
4200 oldTextColor = GetTextColor(hdc);
4203 infoPtr->bIsDrawing = TRUE;
4205 if (prcErase) {
4206 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4207 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4208 /* If no erasing was done (usually because RedrawWindow was called
4209 * with RDW_INVALIDATE only) we need to copy the old contents into
4210 * the backbuffer before continuing. */
4211 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4212 infoPtr->rcList.right - infoPtr->rcList.left,
4213 infoPtr->rcList.bottom - infoPtr->rcList.top,
4214 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4217 /* FIXME: Shouldn't need to do this */
4218 oldClrTextBk = infoPtr->clrTextBk;
4219 oldClrText = infoPtr->clrText;
4221 infoPtr->cditemmode = CDRF_DODEFAULT;
4223 GetClientRect(infoPtr->hwndSelf, &rcClient);
4224 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4225 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4226 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4227 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4229 /* Use these colors to draw the items */
4230 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4231 infoPtr->clrText = nmlvcd.clrText;
4233 /* nothing to draw */
4234 if(infoPtr->nItemCount == 0) goto enddraw;
4236 /* figure out what we need to draw */
4237 iterator_visibleitems(&i, infoPtr, hdc);
4239 /* send cache hint notification */
4240 if (infoPtr->dwStyle & LVS_OWNERDATA)
4242 RANGE range = iterator_range(&i);
4243 NMLVCACHEHINT nmlv;
4245 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4246 nmlv.iFrom = range.lower;
4247 nmlv.iTo = range.upper - 1;
4248 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4251 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4252 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4253 else
4255 if (uView == LVS_REPORT)
4256 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4257 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4258 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4260 /* if we have a focus rect, draw it */
4261 if (infoPtr->bFocus)
4262 DrawFocusRect(hdc, &infoPtr->rcFocus);
4264 iterator_destroy(&i);
4266 enddraw:
4267 /* For LVS_EX_GRIDLINES go and draw lines */
4268 /* This includes the case where there were *no* items */
4269 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4270 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4271 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4273 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4274 notify_postpaint(infoPtr, &nmlvcd);
4276 infoPtr->clrTextBk = oldClrTextBk;
4277 infoPtr->clrText = oldClrText;
4279 if(hbmp) {
4280 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4281 infoPtr->rcList.right - infoPtr->rcList.left,
4282 infoPtr->rcList.bottom - infoPtr->rcList.top,
4283 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4285 DeleteObject(hbmp);
4286 DeleteDC(hdc);
4287 } else {
4288 SelectObject(hdc, hOldFont);
4289 SetBkMode(hdc, oldBkMode);
4290 SetBkColor(hdc, oldBkColor);
4291 SetTextColor(hdc, oldTextColor);
4294 infoPtr->bIsDrawing = FALSE;
4298 /***
4299 * DESCRIPTION:
4300 * Calculates the approximate width and height of a given number of items.
4302 * PARAMETER(S):
4303 * [I] infoPtr : valid pointer to the listview structure
4304 * [I] nItemCount : number of items
4305 * [I] wWidth : width
4306 * [I] wHeight : height
4308 * RETURN:
4309 * Returns a DWORD. The width in the low word and the height in high word.
4311 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4312 WORD wWidth, WORD wHeight)
4314 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4315 INT nItemCountPerColumn = 1;
4316 INT nColumnCount = 0;
4317 DWORD dwViewRect = 0;
4319 if (nItemCount == -1)
4320 nItemCount = infoPtr->nItemCount;
4322 if (uView == LVS_LIST)
4324 if (wHeight == 0xFFFF)
4326 /* use current height */
4327 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4330 if (wHeight < infoPtr->nItemHeight)
4331 wHeight = infoPtr->nItemHeight;
4333 if (nItemCount > 0)
4335 if (infoPtr->nItemHeight > 0)
4337 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4338 if (nItemCountPerColumn == 0)
4339 nItemCountPerColumn = 1;
4341 if (nItemCount % nItemCountPerColumn != 0)
4342 nColumnCount = nItemCount / nItemCountPerColumn;
4343 else
4344 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4348 /* Microsoft padding magic */
4349 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4350 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4352 dwViewRect = MAKELONG(wWidth, wHeight);
4354 else if (uView == LVS_REPORT)
4356 RECT rcBox;
4358 if (infoPtr->nItemCount > 0)
4360 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4361 wWidth = rcBox.right - rcBox.left;
4362 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4364 else
4366 /* use current height and width */
4367 if (wHeight == 0xffff)
4368 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4369 if (wWidth == 0xffff)
4370 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4373 dwViewRect = MAKELONG(wWidth, wHeight);
4375 else if (uView == LVS_SMALLICON)
4376 FIXME("uView == LVS_SMALLICON: not implemented\n");
4377 else if (uView == LVS_ICON)
4378 FIXME("uView == LVS_ICON: not implemented\n");
4380 return dwViewRect;
4384 /***
4385 * DESCRIPTION:
4386 * Create a drag image list for the specified item.
4388 * PARAMETER(S):
4389 * [I] infoPtr : valid pointer to the listview structure
4390 * [I] iItem : index of item
4391 * [O] lppt : Upperr-left corner of the image
4393 * RETURN:
4394 * Returns a handle to the image list if successful, NULL otherwise.
4396 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4398 RECT rcItem;
4399 SIZE size;
4400 POINT pos;
4401 HDC hdc, hdcOrig;
4402 HBITMAP hbmp, hOldbmp;
4403 HIMAGELIST dragList = 0;
4404 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4406 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4407 return 0;
4409 rcItem.left = LVIR_BOUNDS;
4410 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4411 return 0;
4413 lppt->x = rcItem.left;
4414 lppt->y = rcItem.top;
4416 size.cx = rcItem.right - rcItem.left;
4417 size.cy = rcItem.bottom - rcItem.top;
4419 hdcOrig = GetDC(infoPtr->hwndSelf);
4420 hdc = CreateCompatibleDC(hdcOrig);
4421 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4422 hOldbmp = SelectObject(hdc, hbmp);
4424 rcItem.left = rcItem.top = 0;
4425 rcItem.right = size.cx;
4426 rcItem.bottom = size.cy;
4427 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4429 pos.x = pos.y = 0;
4430 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4432 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4433 SelectObject(hdc, hOldbmp);
4434 ImageList_Add(dragList, hbmp, 0);
4436 else
4437 SelectObject(hdc, hOldbmp);
4439 DeleteObject(hbmp);
4440 DeleteDC(hdc);
4441 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4443 TRACE("ret=%p\n", dragList);
4445 return dragList;
4449 /***
4450 * DESCRIPTION:
4451 * Removes all listview items and subitems.
4453 * PARAMETER(S):
4454 * [I] infoPtr : valid pointer to the listview structure
4456 * RETURN:
4457 * SUCCESS : TRUE
4458 * FAILURE : FALSE
4460 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4462 NMLISTVIEW nmlv;
4463 HDPA hdpaSubItems = NULL;
4464 BOOL bSuppress;
4465 ITEMHDR *hdrItem;
4466 INT i, j;
4468 TRACE("()\n");
4470 /* we do it directly, to avoid notifications */
4471 ranges_clear(infoPtr->selectionRanges);
4472 infoPtr->nSelectionMark = -1;
4473 infoPtr->nFocusedItem = -1;
4474 SetRectEmpty(&infoPtr->rcFocus);
4475 /* But we are supposed to leave nHotItem as is! */
4478 /* send LVN_DELETEALLITEMS notification */
4479 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4480 nmlv.iItem = -1;
4481 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4483 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4485 /* send LVN_DELETEITEM notification, if not suppressed */
4486 if (!bSuppress) notify_deleteitem(infoPtr, i);
4487 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4489 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4490 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4492 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4493 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4494 Free(hdrItem);
4496 DPA_Destroy(hdpaSubItems);
4497 DPA_DeletePtr(infoPtr->hdpaItems, i);
4499 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4500 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4501 infoPtr->nItemCount --;
4504 if (!destroy)
4506 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4507 LISTVIEW_UpdateScroll(infoPtr);
4509 LISTVIEW_InvalidateList(infoPtr);
4511 return TRUE;
4514 /***
4515 * DESCRIPTION:
4516 * Scrolls, and updates the columns, when a column is changing width.
4518 * PARAMETER(S):
4519 * [I] infoPtr : valid pointer to the listview structure
4520 * [I] nColumn : column to scroll
4521 * [I] dx : amount of scroll, in pixels
4523 * RETURN:
4524 * None.
4526 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4528 COLUMN_INFO *lpColumnInfo;
4529 RECT rcOld, rcCol;
4530 POINT ptOrigin;
4531 INT nCol;
4533 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4534 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4535 rcCol = lpColumnInfo->rcHeader;
4536 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4537 rcCol.left = rcCol.right;
4539 /* adjust the other columns */
4540 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4542 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4543 lpColumnInfo->rcHeader.left += dx;
4544 lpColumnInfo->rcHeader.right += dx;
4547 /* do not update screen if not in report mode */
4548 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4550 /* if we have a focus, we must first erase the focus rect */
4551 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4553 /* Need to reset the item width when inserting a new column */
4554 infoPtr->nItemWidth += dx;
4556 LISTVIEW_UpdateScroll(infoPtr);
4557 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4559 /* scroll to cover the deleted column, and invalidate for redraw */
4560 rcOld = infoPtr->rcList;
4561 rcOld.left = ptOrigin.x + rcCol.left + dx;
4562 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4564 /* we can restore focus now */
4565 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4568 /***
4569 * DESCRIPTION:
4570 * Removes a column from the listview control.
4572 * PARAMETER(S):
4573 * [I] infoPtr : valid pointer to the listview structure
4574 * [I] nColumn : column index
4576 * RETURN:
4577 * SUCCESS : TRUE
4578 * FAILURE : FALSE
4580 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4582 RECT rcCol;
4584 TRACE("nColumn=%d\n", nColumn);
4586 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4587 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4589 /* While the MSDN specifically says that column zero should not be deleted,
4590 what actually happens is that the column itself is deleted but no items or subitems
4591 are removed.
4594 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4596 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4597 return FALSE;
4599 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4600 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4602 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4604 SUBITEM_INFO *lpSubItem, *lpDelItem;
4605 HDPA hdpaSubItems;
4606 INT nItem, nSubItem, i;
4608 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4610 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4611 nSubItem = 0;
4612 lpDelItem = 0;
4613 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4615 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4616 if (lpSubItem->iSubItem == nColumn)
4618 nSubItem = i;
4619 lpDelItem = lpSubItem;
4621 else if (lpSubItem->iSubItem > nColumn)
4623 lpSubItem->iSubItem--;
4627 /* if we found our subitem, zapp it */
4628 if (nSubItem > 0)
4630 /* free string */
4631 if (is_textW(lpDelItem->hdr.pszText))
4632 Free(lpDelItem->hdr.pszText);
4634 /* free item */
4635 Free(lpDelItem);
4637 /* free dpa memory */
4638 DPA_DeletePtr(hdpaSubItems, nSubItem);
4643 /* update the other column info */
4644 LISTVIEW_UpdateItemSize(infoPtr);
4645 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4646 LISTVIEW_InvalidateList(infoPtr);
4647 else
4648 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4650 return TRUE;
4653 /***
4654 * DESCRIPTION:
4655 * Invalidates the listview after an item's insertion or deletion.
4657 * PARAMETER(S):
4658 * [I] infoPtr : valid pointer to the listview structure
4659 * [I] nItem : item index
4660 * [I] dir : -1 if deleting, 1 if inserting
4662 * RETURN:
4663 * None
4665 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4667 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4668 INT nPerCol, nItemCol, nItemRow;
4669 RECT rcScroll;
4670 POINT Origin;
4672 /* if we don't refresh, what's the point of scrolling? */
4673 if (!is_redrawing(infoPtr)) return;
4675 assert (abs(dir) == 1);
4677 /* arrange icons if autoarrange is on */
4678 if (is_autoarrange(infoPtr))
4680 BOOL arrange = TRUE;
4681 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4682 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4683 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4686 /* scrollbars need updating */
4687 LISTVIEW_UpdateScroll(infoPtr);
4689 /* figure out the item's position */
4690 if (uView == LVS_REPORT)
4691 nPerCol = infoPtr->nItemCount + 1;
4692 else if (uView == LVS_LIST)
4693 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4694 else /* LVS_ICON, or LVS_SMALLICON */
4695 return;
4697 nItemCol = nItem / nPerCol;
4698 nItemRow = nItem % nPerCol;
4699 LISTVIEW_GetOrigin(infoPtr, &Origin);
4701 /* move the items below up a slot */
4702 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4703 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4704 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4705 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4706 OffsetRect(&rcScroll, Origin.x, Origin.y);
4707 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4708 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4710 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4711 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4712 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4715 /* report has only that column, so we're done */
4716 if (uView == LVS_REPORT) return;
4718 /* now for LISTs, we have to deal with the columns to the right */
4719 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4720 rcScroll.top = 0;
4721 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4722 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4723 OffsetRect(&rcScroll, Origin.x, Origin.y);
4724 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4725 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4726 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4729 /***
4730 * DESCRIPTION:
4731 * Removes an item from the listview control.
4733 * PARAMETER(S):
4734 * [I] infoPtr : valid pointer to the listview structure
4735 * [I] nItem : item index
4737 * RETURN:
4738 * SUCCESS : TRUE
4739 * FAILURE : FALSE
4741 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4743 LVITEMW item;
4744 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4745 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4747 TRACE("(nItem=%d)\n", nItem);
4749 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4751 /* remove selection, and focus */
4752 item.state = 0;
4753 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4754 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4756 /* send LVN_DELETEITEM notification. */
4757 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4759 /* we need to do this here, because we'll be deleting stuff */
4760 if (is_icon)
4761 LISTVIEW_InvalidateItem(infoPtr, nItem);
4763 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4765 HDPA hdpaSubItems;
4766 ITEMHDR *hdrItem;
4767 INT i;
4769 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4770 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4772 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4773 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4774 Free(hdrItem);
4776 DPA_Destroy(hdpaSubItems);
4779 if (is_icon)
4781 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4782 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4785 infoPtr->nItemCount--;
4786 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4788 /* now is the invalidation fun */
4789 if (!is_icon)
4790 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4791 return TRUE;
4795 /***
4796 * DESCRIPTION:
4797 * Callback implementation for editlabel control
4799 * PARAMETER(S):
4800 * [I] infoPtr : valid pointer to the listview structure
4801 * [I] pszText : modified text
4802 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4804 * RETURN:
4805 * SUCCESS : TRUE
4806 * FAILURE : FALSE
4808 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4810 HWND hwndSelf = infoPtr->hwndSelf;
4811 NMLVDISPINFOW dispInfo;
4813 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4815 ZeroMemory(&dispInfo, sizeof(dispInfo));
4816 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4817 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4818 dispInfo.item.iSubItem = 0;
4819 dispInfo.item.stateMask = ~0;
4820 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4821 /* add the text from the edit in */
4822 dispInfo.item.mask |= LVIF_TEXT;
4823 dispInfo.item.pszText = pszText;
4824 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4826 /* Do we need to update the Item Text */
4827 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4828 if (!IsWindow(hwndSelf))
4829 return FALSE;
4830 if (!pszText) return TRUE;
4832 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4834 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4835 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4836 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4838 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4839 return TRUE;
4843 ZeroMemory(&dispInfo, sizeof(dispInfo));
4844 dispInfo.item.mask = LVIF_TEXT;
4845 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4846 dispInfo.item.iSubItem = 0;
4847 dispInfo.item.pszText = pszText;
4848 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4849 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4852 /***
4853 * DESCRIPTION:
4854 * Begin in place editing of specified list view item
4856 * PARAMETER(S):
4857 * [I] infoPtr : valid pointer to the listview structure
4858 * [I] nItem : item index
4859 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4861 * RETURN:
4862 * SUCCESS : TRUE
4863 * FAILURE : FALSE
4865 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4867 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4868 NMLVDISPINFOW dispInfo;
4869 RECT rect;
4870 HWND hwndSelf = infoPtr->hwndSelf;
4872 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4874 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4875 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4877 infoPtr->nEditLabelItem = nItem;
4879 /* Is the EditBox still there, if so remove it */
4880 if(infoPtr->hwndEdit != 0)
4882 SetFocus(infoPtr->hwndSelf);
4883 infoPtr->hwndEdit = 0;
4886 LISTVIEW_SetSelection(infoPtr, nItem);
4887 LISTVIEW_SetItemFocus(infoPtr, nItem);
4888 LISTVIEW_InvalidateItem(infoPtr, nItem);
4890 rect.left = LVIR_LABEL;
4891 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4893 ZeroMemory(&dispInfo, sizeof(dispInfo));
4894 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4895 dispInfo.item.iItem = nItem;
4896 dispInfo.item.iSubItem = 0;
4897 dispInfo.item.stateMask = ~0;
4898 dispInfo.item.pszText = szDispText;
4899 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4900 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4902 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4903 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4904 if (!infoPtr->hwndEdit) return 0;
4906 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4908 if (!IsWindow(hwndSelf))
4909 return 0;
4910 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4911 infoPtr->hwndEdit = 0;
4912 return 0;
4915 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4916 SetFocus(infoPtr->hwndEdit);
4917 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4918 return infoPtr->hwndEdit;
4922 /***
4923 * DESCRIPTION:
4924 * Ensures the specified item is visible, scrolling into view if necessary.
4926 * PARAMETER(S):
4927 * [I] infoPtr : valid pointer to the listview structure
4928 * [I] nItem : item index
4929 * [I] bPartial : partially or entirely visible
4931 * RETURN:
4932 * SUCCESS : TRUE
4933 * FAILURE : FALSE
4935 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4937 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4938 INT nScrollPosHeight = 0;
4939 INT nScrollPosWidth = 0;
4940 INT nHorzAdjust = 0;
4941 INT nVertAdjust = 0;
4942 INT nHorzDiff = 0;
4943 INT nVertDiff = 0;
4944 RECT rcItem, rcTemp;
4946 rcItem.left = LVIR_BOUNDS;
4947 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4949 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4951 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4953 /* scroll left/right, but in LVS_REPORT mode */
4954 if (uView == LVS_LIST)
4955 nScrollPosWidth = infoPtr->nItemWidth;
4956 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4957 nScrollPosWidth = 1;
4959 if (rcItem.left < infoPtr->rcList.left)
4961 nHorzAdjust = -1;
4962 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4964 else
4966 nHorzAdjust = 1;
4967 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4971 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4973 /* scroll up/down, but not in LVS_LIST mode */
4974 if (uView == LVS_REPORT)
4975 nScrollPosHeight = infoPtr->nItemHeight;
4976 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4977 nScrollPosHeight = 1;
4979 if (rcItem.top < infoPtr->rcList.top)
4981 nVertAdjust = -1;
4982 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4984 else
4986 nVertAdjust = 1;
4987 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4991 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4993 if (nScrollPosWidth)
4995 INT diff = nHorzDiff / nScrollPosWidth;
4996 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4997 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5000 if (nScrollPosHeight)
5002 INT diff = nVertDiff / nScrollPosHeight;
5003 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5004 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5007 return TRUE;
5010 /***
5011 * DESCRIPTION:
5012 * Searches for an item with specific characteristics.
5014 * PARAMETER(S):
5015 * [I] hwnd : window handle
5016 * [I] nStart : base item index
5017 * [I] lpFindInfo : item information to look for
5019 * RETURN:
5020 * SUCCESS : index of item
5021 * FAILURE : -1
5023 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5024 const LVFINDINFOW *lpFindInfo)
5026 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5027 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5028 BOOL bWrap = FALSE, bNearest = FALSE;
5029 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5030 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5031 POINT Position, Destination;
5032 LVITEMW lvItem;
5034 if (!lpFindInfo || nItem < 0) return -1;
5036 lvItem.mask = 0;
5037 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5039 lvItem.mask |= LVIF_TEXT;
5040 lvItem.pszText = szDispText;
5041 lvItem.cchTextMax = DISP_TEXT_SIZE;
5044 if (lpFindInfo->flags & LVFI_WRAP)
5045 bWrap = TRUE;
5047 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5048 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5050 POINT Origin;
5051 RECT rcArea;
5053 LISTVIEW_GetOrigin(infoPtr, &Origin);
5054 Destination.x = lpFindInfo->pt.x - Origin.x;
5055 Destination.y = lpFindInfo->pt.y - Origin.y;
5056 switch(lpFindInfo->vkDirection)
5058 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5059 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5060 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5061 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5062 case VK_HOME: Destination.x = Destination.y = 0; break;
5063 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5064 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5065 case VK_END:
5066 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5067 Destination.x = rcArea.right;
5068 Destination.y = rcArea.bottom;
5069 break;
5070 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5072 bNearest = TRUE;
5074 else Destination.x = Destination.y = 0;
5076 /* if LVFI_PARAM is specified, all other flags are ignored */
5077 if (lpFindInfo->flags & LVFI_PARAM)
5079 lvItem.mask |= LVIF_PARAM;
5080 bNearest = FALSE;
5081 lvItem.mask &= ~LVIF_TEXT;
5084 again:
5085 for (; nItem < nLast; nItem++)
5087 lvItem.iItem = nItem;
5088 lvItem.iSubItem = 0;
5089 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5091 if (lvItem.mask & LVIF_PARAM)
5093 if (lpFindInfo->lParam == lvItem.lParam)
5094 return nItem;
5095 else
5096 continue;
5099 if (lvItem.mask & LVIF_TEXT)
5101 if (lpFindInfo->flags & LVFI_PARTIAL)
5103 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5105 else
5107 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5111 if (!bNearest) return nItem;
5113 /* This is very inefficient. To do a good job here,
5114 * we need a sorted array of (x,y) item positions */
5115 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5117 /* compute the distance^2 to the destination */
5118 xdist = Destination.x - Position.x;
5119 ydist = Destination.y - Position.y;
5120 dist = xdist * xdist + ydist * ydist;
5122 /* remember the distance, and item if it's closer */
5123 if (dist < mindist)
5125 mindist = dist;
5126 nNearestItem = nItem;
5130 if (bWrap)
5132 nItem = 0;
5133 nLast = min(nStart + 1, infoPtr->nItemCount);
5134 bWrap = FALSE;
5135 goto again;
5138 return nNearestItem;
5141 /***
5142 * DESCRIPTION:
5143 * Searches for an item with specific characteristics.
5145 * PARAMETER(S):
5146 * [I] hwnd : window handle
5147 * [I] nStart : base item index
5148 * [I] lpFindInfo : item information to look for
5150 * RETURN:
5151 * SUCCESS : index of item
5152 * FAILURE : -1
5154 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5155 const LVFINDINFOA *lpFindInfo)
5157 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5158 LVFINDINFOW fiw;
5159 INT res;
5160 LPWSTR strW = NULL;
5162 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5163 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5164 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5165 textfreeT(strW, FALSE);
5166 return res;
5169 /***
5170 * DESCRIPTION:
5171 * Retrieves the background image of the listview control.
5173 * PARAMETER(S):
5174 * [I] infoPtr : valid pointer to the listview structure
5175 * [O] lpBkImage : background image attributes
5177 * RETURN:
5178 * SUCCESS : TRUE
5179 * FAILURE : FALSE
5181 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5182 /* { */
5183 /* FIXME (listview, "empty stub!\n"); */
5184 /* return FALSE; */
5185 /* } */
5187 /***
5188 * DESCRIPTION:
5189 * Retrieves column attributes.
5191 * PARAMETER(S):
5192 * [I] infoPtr : valid pointer to the listview structure
5193 * [I] nColumn : column index
5194 * [IO] lpColumn : column information
5195 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5196 * otherwise it is in fact a LPLVCOLUMNA
5198 * RETURN:
5199 * SUCCESS : TRUE
5200 * FAILURE : FALSE
5202 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5204 COLUMN_INFO *lpColumnInfo;
5205 HDITEMW hdi;
5207 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5208 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5210 /* initialize memory */
5211 ZeroMemory(&hdi, sizeof(hdi));
5213 if (lpColumn->mask & LVCF_TEXT)
5215 hdi.mask |= HDI_TEXT;
5216 hdi.pszText = lpColumn->pszText;
5217 hdi.cchTextMax = lpColumn->cchTextMax;
5220 if (lpColumn->mask & LVCF_IMAGE)
5221 hdi.mask |= HDI_IMAGE;
5223 if (lpColumn->mask & LVCF_ORDER)
5224 hdi.mask |= HDI_ORDER;
5226 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5228 if (lpColumn->mask & LVCF_FMT)
5229 lpColumn->fmt = lpColumnInfo->fmt;
5231 if (lpColumn->mask & LVCF_WIDTH)
5232 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5234 if (lpColumn->mask & LVCF_IMAGE)
5235 lpColumn->iImage = hdi.iImage;
5237 if (lpColumn->mask & LVCF_ORDER)
5238 lpColumn->iOrder = hdi.iOrder;
5240 return TRUE;
5244 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5246 INT i;
5248 if (!lpiArray)
5249 return FALSE;
5251 /* FIXME: little hack */
5252 for (i = 0; i < iCount; i++)
5253 lpiArray[i] = i;
5255 return TRUE;
5258 /***
5259 * DESCRIPTION:
5260 * Retrieves the column width.
5262 * PARAMETER(S):
5263 * [I] infoPtr : valid pointer to the listview structure
5264 * [I] int : column index
5266 * RETURN:
5267 * SUCCESS : column width
5268 * FAILURE : zero
5270 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5272 INT nColumnWidth = 0;
5273 HDITEMW hdItem;
5275 TRACE("nColumn=%d\n", nColumn);
5277 /* we have a 'column' in LIST and REPORT mode only */
5278 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5280 case LVS_LIST:
5281 nColumnWidth = infoPtr->nItemWidth;
5282 break;
5283 case LVS_REPORT:
5284 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5285 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5286 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5288 * TODO: should we do the same in LVM_GETCOLUMN?
5290 hdItem.mask = HDI_WIDTH;
5291 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5293 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5294 return 0;
5296 nColumnWidth = hdItem.cxy;
5297 break;
5300 TRACE("nColumnWidth=%d\n", nColumnWidth);
5301 return nColumnWidth;
5304 /***
5305 * DESCRIPTION:
5306 * In list or report display mode, retrieves the number of items that can fit
5307 * vertically in the visible area. In icon or small icon display mode,
5308 * retrieves the total number of visible items.
5310 * PARAMETER(S):
5311 * [I] infoPtr : valid pointer to the listview structure
5313 * RETURN:
5314 * Number of fully visible items.
5316 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5318 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5320 case LVS_ICON:
5321 case LVS_SMALLICON:
5322 return infoPtr->nItemCount;
5323 case LVS_REPORT:
5324 return LISTVIEW_GetCountPerColumn(infoPtr);
5325 case LVS_LIST:
5326 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5328 assert(FALSE);
5329 return 0;
5332 /***
5333 * DESCRIPTION:
5334 * Retrieves an image list handle.
5336 * PARAMETER(S):
5337 * [I] infoPtr : valid pointer to the listview structure
5338 * [I] nImageList : image list identifier
5340 * RETURN:
5341 * SUCCESS : image list handle
5342 * FAILURE : NULL
5344 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5346 switch (nImageList)
5348 case LVSIL_NORMAL: return infoPtr->himlNormal;
5349 case LVSIL_SMALL: return infoPtr->himlSmall;
5350 case LVSIL_STATE: return infoPtr->himlState;
5352 return NULL;
5355 /* LISTVIEW_GetISearchString */
5357 /***
5358 * DESCRIPTION:
5359 * Retrieves item attributes.
5361 * PARAMETER(S):
5362 * [I] hwnd : window handle
5363 * [IO] lpLVItem : item info
5364 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5365 * if FALSE, then lpLVItem is a LPLVITEMA.
5367 * NOTE:
5368 * This is the internal 'GetItem' interface -- it tries to
5369 * be smart and avoid text copies, if possible, by modifying
5370 * lpLVItem->pszText to point to the text string. Please note
5371 * that this is not always possible (e.g. OWNERDATA), so on
5372 * entry you *must* supply valid values for pszText, and cchTextMax.
5373 * The only difference to the documented interface is that upon
5374 * return, you should use *only* the lpLVItem->pszText, rather than
5375 * the buffer pointer you provided on input. Most code already does
5376 * that, so it's not a problem.
5377 * For the two cases when the text must be copied (that is,
5378 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5380 * RETURN:
5381 * SUCCESS : TRUE
5382 * FAILURE : FALSE
5384 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5386 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5387 NMLVDISPINFOW dispInfo;
5388 ITEM_INFO *lpItem;
5389 ITEMHDR* pItemHdr;
5390 HDPA hdpaSubItems;
5391 INT isubitem;
5393 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5395 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5396 return FALSE;
5398 if (lpLVItem->mask == 0) return TRUE;
5400 /* make a local copy */
5401 isubitem = lpLVItem->iSubItem;
5403 /* a quick optimization if all we're asked is the focus state
5404 * these queries are worth optimising since they are common,
5405 * and can be answered in constant time, without the heavy accesses */
5406 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5407 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5409 lpLVItem->state = 0;
5410 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5411 lpLVItem->state |= LVIS_FOCUSED;
5412 return TRUE;
5415 ZeroMemory(&dispInfo, sizeof(dispInfo));
5417 /* if the app stores all the data, handle it separately */
5418 if (infoPtr->dwStyle & LVS_OWNERDATA)
5420 dispInfo.item.state = 0;
5422 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5423 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5425 /* NOTE: copy only fields which we _know_ are initialized, some apps
5426 * depend on the uninitialized fields being 0 */
5427 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5428 dispInfo.item.iItem = lpLVItem->iItem;
5429 dispInfo.item.iSubItem = isubitem;
5430 if (lpLVItem->mask & LVIF_TEXT)
5432 dispInfo.item.pszText = lpLVItem->pszText;
5433 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5435 if (lpLVItem->mask & LVIF_STATE)
5436 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5437 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5438 dispInfo.item.stateMask = lpLVItem->stateMask;
5439 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5441 /* full size structure expected - _WIN32IE >= 0x560 */
5442 *lpLVItem = dispInfo.item;
5444 else if (lpLVItem->mask & LVIF_INDENT)
5446 /* indent member expected - _WIN32IE >= 0x300 */
5447 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5449 else
5451 /* minimal structure expected */
5452 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5454 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5457 /* make sure lParam is zeroed out */
5458 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5460 /* we store only a little state, so if we're not asked, we're done */
5461 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5463 /* if focus is handled by us, report it */
5464 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5466 lpLVItem->state &= ~LVIS_FOCUSED;
5467 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5468 lpLVItem->state |= LVIS_FOCUSED;
5471 /* and do the same for selection, if we handle it */
5472 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5474 lpLVItem->state &= ~LVIS_SELECTED;
5475 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5476 lpLVItem->state |= LVIS_SELECTED;
5479 return TRUE;
5482 /* find the item and subitem structures before we proceed */
5483 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5484 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5485 assert (lpItem);
5487 if (isubitem)
5489 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5490 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5491 if (!lpSubItem)
5493 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5494 isubitem = 0;
5497 else
5498 pItemHdr = &lpItem->hdr;
5500 /* Do we need to query the state from the app? */
5501 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5503 dispInfo.item.mask |= LVIF_STATE;
5504 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5507 /* Do we need to enquire about the image? */
5508 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5509 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5511 dispInfo.item.mask |= LVIF_IMAGE;
5512 dispInfo.item.iImage = I_IMAGECALLBACK;
5515 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5516 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5518 dispInfo.item.mask |= LVIF_TEXT;
5519 dispInfo.item.pszText = lpLVItem->pszText;
5520 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5521 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5522 *dispInfo.item.pszText = '\0';
5525 /* If we don't have all the requested info, query the application */
5526 if (dispInfo.item.mask != 0)
5528 dispInfo.item.iItem = lpLVItem->iItem;
5529 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5530 dispInfo.item.lParam = lpItem->lParam;
5531 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5532 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5535 /* we should not store values for subitems */
5536 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5538 /* Now, handle the iImage field */
5539 if (dispInfo.item.mask & LVIF_IMAGE)
5541 lpLVItem->iImage = dispInfo.item.iImage;
5542 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5543 pItemHdr->iImage = dispInfo.item.iImage;
5545 else if (lpLVItem->mask & LVIF_IMAGE)
5547 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5548 lpLVItem->iImage = pItemHdr->iImage;
5549 else
5550 lpLVItem->iImage = 0;
5553 /* The pszText field */
5554 if (dispInfo.item.mask & LVIF_TEXT)
5556 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5557 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5559 lpLVItem->pszText = dispInfo.item.pszText;
5561 else if (lpLVItem->mask & LVIF_TEXT)
5563 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5564 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5567 /* Next is the lParam field */
5568 if (dispInfo.item.mask & LVIF_PARAM)
5570 lpLVItem->lParam = dispInfo.item.lParam;
5571 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5572 lpItem->lParam = dispInfo.item.lParam;
5574 else if (lpLVItem->mask & LVIF_PARAM)
5575 lpLVItem->lParam = lpItem->lParam;
5577 /* if this is a subitem, we're done */
5578 if (isubitem) return TRUE;
5580 /* ... the state field (this one is different due to uCallbackmask) */
5581 if (lpLVItem->mask & LVIF_STATE)
5583 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5584 if (dispInfo.item.mask & LVIF_STATE)
5586 lpLVItem->state &= ~dispInfo.item.stateMask;
5587 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5589 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5591 lpLVItem->state &= ~LVIS_FOCUSED;
5592 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5593 lpLVItem->state |= LVIS_FOCUSED;
5595 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5597 lpLVItem->state &= ~LVIS_SELECTED;
5598 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5599 lpLVItem->state |= LVIS_SELECTED;
5603 /* and last, but not least, the indent field */
5604 if (lpLVItem->mask & LVIF_INDENT)
5605 lpLVItem->iIndent = lpItem->iIndent;
5607 return TRUE;
5610 /***
5611 * DESCRIPTION:
5612 * Retrieves item attributes.
5614 * PARAMETER(S):
5615 * [I] hwnd : window handle
5616 * [IO] lpLVItem : item info
5617 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5618 * if FALSE, then lpLVItem is a LPLVITEMA.
5620 * NOTE:
5621 * This is the external 'GetItem' interface -- it properly copies
5622 * the text in the provided buffer.
5624 * RETURN:
5625 * SUCCESS : TRUE
5626 * FAILURE : FALSE
5628 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5630 LPWSTR pszText;
5631 BOOL bResult;
5633 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5634 return FALSE;
5636 pszText = lpLVItem->pszText;
5637 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5638 if (bResult && lpLVItem->pszText != pszText)
5639 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5640 lpLVItem->pszText = pszText;
5642 return bResult;
5646 /***
5647 * DESCRIPTION:
5648 * Retrieves the position (upper-left) of the listview control item.
5649 * Note that for LVS_ICON style, the upper-left is that of the icon
5650 * and not the bounding box.
5652 * PARAMETER(S):
5653 * [I] infoPtr : valid pointer to the listview structure
5654 * [I] nItem : item index
5655 * [O] lpptPosition : coordinate information
5657 * RETURN:
5658 * SUCCESS : TRUE
5659 * FAILURE : FALSE
5661 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5663 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5664 POINT Origin;
5666 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5668 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5670 LISTVIEW_GetOrigin(infoPtr, &Origin);
5671 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5673 if (uView == LVS_ICON)
5675 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5676 lpptPosition->y += ICON_TOP_PADDING;
5678 lpptPosition->x += Origin.x;
5679 lpptPosition->y += Origin.y;
5681 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5682 return TRUE;
5686 /***
5687 * DESCRIPTION:
5688 * Retrieves the bounding rectangle for a listview control item.
5690 * PARAMETER(S):
5691 * [I] infoPtr : valid pointer to the listview structure
5692 * [I] nItem : item index
5693 * [IO] lprc : bounding rectangle coordinates
5694 * lprc->left specifies the portion of the item for which the bounding
5695 * rectangle will be retrieved.
5697 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5698 * including the icon and label.
5700 * * For LVS_ICON
5701 * * Experiment shows that native control returns:
5702 * * width = min (48, length of text line)
5703 * * .left = position.x - (width - iconsize.cx)/2
5704 * * .right = .left + width
5705 * * height = #lines of text * ntmHeight + icon height + 8
5706 * * .top = position.y - 2
5707 * * .bottom = .top + height
5708 * * separation between items .y = itemSpacing.cy - height
5709 * * .x = itemSpacing.cx - width
5710 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5712 * * For LVS_ICON
5713 * * Experiment shows that native control returns:
5714 * * width = iconSize.cx + 16
5715 * * .left = position.x - (width - iconsize.cx)/2
5716 * * .right = .left + width
5717 * * height = iconSize.cy + 4
5718 * * .top = position.y - 2
5719 * * .bottom = .top + height
5720 * * separation between items .y = itemSpacing.cy - height
5721 * * .x = itemSpacing.cx - width
5722 * LVIR_LABEL Returns the bounding rectangle of the item text.
5724 * * For LVS_ICON
5725 * * Experiment shows that native control returns:
5726 * * width = text length
5727 * * .left = position.x - width/2
5728 * * .right = .left + width
5729 * * height = ntmH * linecount + 2
5730 * * .top = position.y + iconSize.cy + 6
5731 * * .bottom = .top + height
5732 * * separation between items .y = itemSpacing.cy - height
5733 * * .x = itemSpacing.cx - width
5734 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5735 * rectangles, but excludes columns in report view.
5737 * RETURN:
5738 * SUCCESS : TRUE
5739 * FAILURE : FALSE
5741 * NOTES
5742 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5743 * upon whether the window has the focus currently and on whether the item
5744 * is the one with the focus. Ensure that the control's record of which
5745 * item has the focus agrees with the items' records.
5747 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5749 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5750 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5751 BOOL doLabel = TRUE, oversizedBox = FALSE;
5752 POINT Position, Origin;
5753 LVITEMW lvItem;
5755 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5757 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5759 LISTVIEW_GetOrigin(infoPtr, &Origin);
5760 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5762 /* Be smart and try to figure out the minimum we have to do */
5763 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5764 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5765 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5766 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5767 oversizedBox = TRUE;
5769 /* get what we need from the item before hand, so we make
5770 * only one request. This can speed up things, if data
5771 * is stored on the app side */
5772 lvItem.mask = 0;
5773 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5774 if (doLabel) lvItem.mask |= LVIF_TEXT;
5775 lvItem.iItem = nItem;
5776 lvItem.iSubItem = 0;
5777 lvItem.pszText = szDispText;
5778 lvItem.cchTextMax = DISP_TEXT_SIZE;
5779 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5780 /* we got the state already up, simulate it here, to avoid a reget */
5781 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5783 lvItem.mask |= LVIF_STATE;
5784 lvItem.stateMask = LVIS_FOCUSED;
5785 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5788 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5789 lprc->left = LVIR_BOUNDS;
5790 switch(lprc->left)
5792 case LVIR_ICON:
5793 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5794 break;
5796 case LVIR_LABEL:
5797 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5798 break;
5800 case LVIR_BOUNDS:
5801 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5802 break;
5804 case LVIR_SELECTBOUNDS:
5805 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5806 break;
5808 default:
5809 WARN("Unknown value: %d\n", lprc->left);
5810 return FALSE;
5813 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5815 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5817 return TRUE;
5820 /***
5821 * DESCRIPTION:
5822 * Retrieves the spacing between listview control items.
5824 * PARAMETER(S):
5825 * [I] infoPtr : valid pointer to the listview structure
5826 * [IO] lprc : rectangle to receive the output
5827 * on input, lprc->top = nSubItem
5828 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5830 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5831 * not only those of the first column.
5832 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5834 * RETURN:
5835 * TRUE: success
5836 * FALSE: failure
5838 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5840 POINT Position;
5841 LVITEMW lvItem;
5842 INT nColumn;
5844 if (!lprc) return FALSE;
5846 nColumn = lprc->top;
5848 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5849 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5850 if (lprc->top == 0)
5851 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5853 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5855 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5857 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5859 lvItem.mask = 0;
5860 lvItem.iItem = nItem;
5861 lvItem.iSubItem = nColumn;
5863 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5864 switch(lprc->left)
5866 case LVIR_ICON:
5867 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5868 break;
5870 case LVIR_LABEL:
5871 case LVIR_BOUNDS:
5872 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5873 break;
5875 default:
5876 ERR("Unknown bounds=%d\n", lprc->left);
5877 return FALSE;
5880 OffsetRect(lprc, Position.x, Position.y);
5881 return TRUE;
5885 /***
5886 * DESCRIPTION:
5887 * Retrieves the width of a label.
5889 * PARAMETER(S):
5890 * [I] infoPtr : valid pointer to the listview structure
5892 * RETURN:
5893 * SUCCESS : string width (in pixels)
5894 * FAILURE : zero
5896 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5898 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5899 LVITEMW lvItem;
5901 TRACE("(nItem=%d)\n", nItem);
5903 lvItem.mask = LVIF_TEXT;
5904 lvItem.iItem = nItem;
5905 lvItem.iSubItem = 0;
5906 lvItem.pszText = szDispText;
5907 lvItem.cchTextMax = DISP_TEXT_SIZE;
5908 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5910 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5913 /***
5914 * DESCRIPTION:
5915 * Retrieves the spacing between listview control items.
5917 * PARAMETER(S):
5918 * [I] infoPtr : valid pointer to the listview structure
5919 * [I] bSmall : flag for small or large icon
5921 * RETURN:
5922 * Horizontal + vertical spacing
5924 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5926 LONG lResult;
5928 if (!bSmall)
5930 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5932 else
5934 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5935 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5936 else
5937 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5939 return lResult;
5942 /***
5943 * DESCRIPTION:
5944 * Retrieves the state of a listview control item.
5946 * PARAMETER(S):
5947 * [I] infoPtr : valid pointer to the listview structure
5948 * [I] nItem : item index
5949 * [I] uMask : state mask
5951 * RETURN:
5952 * State specified by the mask.
5954 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5956 LVITEMW lvItem;
5958 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5960 lvItem.iItem = nItem;
5961 lvItem.iSubItem = 0;
5962 lvItem.mask = LVIF_STATE;
5963 lvItem.stateMask = uMask;
5964 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5966 return lvItem.state & uMask;
5969 /***
5970 * DESCRIPTION:
5971 * Retrieves the text of a listview control item or subitem.
5973 * PARAMETER(S):
5974 * [I] hwnd : window handle
5975 * [I] nItem : item index
5976 * [IO] lpLVItem : item information
5977 * [I] isW : TRUE if lpLVItem is Unicode
5979 * RETURN:
5980 * SUCCESS : string length
5981 * FAILURE : 0
5983 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5985 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5987 lpLVItem->mask = LVIF_TEXT;
5988 lpLVItem->iItem = nItem;
5989 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5991 return textlenT(lpLVItem->pszText, isW);
5994 /***
5995 * DESCRIPTION:
5996 * Searches for an item based on properties + relationships.
5998 * PARAMETER(S):
5999 * [I] infoPtr : valid pointer to the listview structure
6000 * [I] nItem : item index
6001 * [I] uFlags : relationship flag
6003 * RETURN:
6004 * SUCCESS : item index
6005 * FAILURE : -1
6007 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6009 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6010 UINT uMask = 0;
6011 LVFINDINFOW lvFindInfo;
6012 INT nCountPerColumn;
6013 INT nCountPerRow;
6014 INT i;
6016 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6017 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6019 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6021 if (uFlags & LVNI_CUT)
6022 uMask |= LVIS_CUT;
6024 if (uFlags & LVNI_DROPHILITED)
6025 uMask |= LVIS_DROPHILITED;
6027 if (uFlags & LVNI_FOCUSED)
6028 uMask |= LVIS_FOCUSED;
6030 if (uFlags & LVNI_SELECTED)
6031 uMask |= LVIS_SELECTED;
6033 /* if we're asked for the focused item, that's only one,
6034 * so it's worth optimizing */
6035 if (uFlags & LVNI_FOCUSED)
6037 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6038 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6041 if (uFlags & LVNI_ABOVE)
6043 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6045 while (nItem >= 0)
6047 nItem--;
6048 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6049 return nItem;
6052 else
6054 /* Special case for autoarrange - move 'til the top of a list */
6055 if (is_autoarrange(infoPtr))
6057 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6058 while (nItem - nCountPerRow >= 0)
6060 nItem -= nCountPerRow;
6061 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6062 return nItem;
6064 return -1;
6066 lvFindInfo.flags = LVFI_NEARESTXY;
6067 lvFindInfo.vkDirection = VK_UP;
6068 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6069 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6071 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6072 return nItem;
6076 else if (uFlags & LVNI_BELOW)
6078 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6080 while (nItem < infoPtr->nItemCount)
6082 nItem++;
6083 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6084 return nItem;
6087 else
6089 /* Special case for autoarrange - move 'til the bottom of a list */
6090 if (is_autoarrange(infoPtr))
6092 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6093 while (nItem + nCountPerRow < infoPtr->nItemCount )
6095 nItem += nCountPerRow;
6096 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6097 return nItem;
6099 return -1;
6101 lvFindInfo.flags = LVFI_NEARESTXY;
6102 lvFindInfo.vkDirection = VK_DOWN;
6103 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6104 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6106 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6107 return nItem;
6111 else if (uFlags & LVNI_TOLEFT)
6113 if (uView == LVS_LIST)
6115 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6116 while (nItem - nCountPerColumn >= 0)
6118 nItem -= nCountPerColumn;
6119 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6120 return nItem;
6123 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6125 /* Special case for autoarrange - move 'ti the beginning of a row */
6126 if (is_autoarrange(infoPtr))
6128 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6129 while (nItem % nCountPerRow > 0)
6131 nItem --;
6132 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6133 return nItem;
6135 return -1;
6137 lvFindInfo.flags = LVFI_NEARESTXY;
6138 lvFindInfo.vkDirection = VK_LEFT;
6139 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6140 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6142 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6143 return nItem;
6147 else if (uFlags & LVNI_TORIGHT)
6149 if (uView == LVS_LIST)
6151 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6152 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6154 nItem += nCountPerColumn;
6155 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6156 return nItem;
6159 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6161 /* Special case for autoarrange - move 'til the end of a row */
6162 if (is_autoarrange(infoPtr))
6164 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6165 while (nItem % nCountPerRow < nCountPerRow - 1 )
6167 nItem ++;
6168 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6169 return nItem;
6171 return -1;
6173 lvFindInfo.flags = LVFI_NEARESTXY;
6174 lvFindInfo.vkDirection = VK_RIGHT;
6175 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6176 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6178 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6179 return nItem;
6183 else
6185 nItem++;
6187 /* search by index */
6188 for (i = nItem; i < infoPtr->nItemCount; i++)
6190 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6191 return i;
6195 return -1;
6198 /* LISTVIEW_GetNumberOfWorkAreas */
6200 /***
6201 * DESCRIPTION:
6202 * Retrieves the origin coordinates when in icon or small icon display mode.
6204 * PARAMETER(S):
6205 * [I] infoPtr : valid pointer to the listview structure
6206 * [O] lpptOrigin : coordinate information
6208 * RETURN:
6209 * None.
6211 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6213 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6214 INT nHorzPos = 0, nVertPos = 0;
6215 SCROLLINFO scrollInfo;
6217 scrollInfo.cbSize = sizeof(SCROLLINFO);
6218 scrollInfo.fMask = SIF_POS;
6220 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6221 nHorzPos = scrollInfo.nPos;
6222 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6223 nVertPos = scrollInfo.nPos;
6225 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6227 lpptOrigin->x = infoPtr->rcList.left;
6228 lpptOrigin->y = infoPtr->rcList.top;
6229 if (uView == LVS_LIST)
6230 nHorzPos *= infoPtr->nItemWidth;
6231 else if (uView == LVS_REPORT)
6232 nVertPos *= infoPtr->nItemHeight;
6234 lpptOrigin->x -= nHorzPos;
6235 lpptOrigin->y -= nVertPos;
6237 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6240 /***
6241 * DESCRIPTION:
6242 * Retrieves the width of a string.
6244 * PARAMETER(S):
6245 * [I] hwnd : window handle
6246 * [I] lpszText : text string to process
6247 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6249 * RETURN:
6250 * SUCCESS : string width (in pixels)
6251 * FAILURE : zero
6253 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6255 SIZE stringSize;
6257 stringSize.cx = 0;
6258 if (is_textT(lpszText, isW))
6260 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6261 HDC hdc = GetDC(infoPtr->hwndSelf);
6262 HFONT hOldFont = SelectObject(hdc, hFont);
6264 if (isW)
6265 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6266 else
6267 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6268 SelectObject(hdc, hOldFont);
6269 ReleaseDC(infoPtr->hwndSelf, hdc);
6271 return stringSize.cx;
6274 /***
6275 * DESCRIPTION:
6276 * Determines which listview item is located at the specified position.
6278 * PARAMETER(S):
6279 * [I] infoPtr : valid pointer to the listview structure
6280 * [IO] lpht : hit test information
6281 * [I] subitem : fill out iSubItem.
6282 * [I] select : return the index only if the hit selects the item
6284 * NOTE:
6285 * (mm 20001022): We must not allow iSubItem to be touched, for
6286 * an app might pass only a structure with space up to iItem!
6287 * (MS Office 97 does that for instance in the file open dialog)
6289 * RETURN:
6290 * SUCCESS : item index
6291 * FAILURE : -1
6293 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6295 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6296 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6297 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6298 POINT Origin, Position, opt;
6299 LVITEMW lvItem;
6300 ITERATOR i;
6301 INT iItem;
6303 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6305 lpht->flags = 0;
6306 lpht->iItem = -1;
6307 if (subitem) lpht->iSubItem = 0;
6309 if (infoPtr->rcList.left > lpht->pt.x)
6310 lpht->flags |= LVHT_TOLEFT;
6311 else if (infoPtr->rcList.right < lpht->pt.x)
6312 lpht->flags |= LVHT_TORIGHT;
6314 if (infoPtr->rcList.top > lpht->pt.y)
6315 lpht->flags |= LVHT_ABOVE;
6316 else if (infoPtr->rcList.bottom < lpht->pt.y)
6317 lpht->flags |= LVHT_BELOW;
6319 TRACE("lpht->flags=0x%x\n", lpht->flags);
6320 if (lpht->flags) return -1;
6322 lpht->flags |= LVHT_NOWHERE;
6324 LISTVIEW_GetOrigin(infoPtr, &Origin);
6326 /* first deal with the large items */
6327 rcSearch.left = lpht->pt.x;
6328 rcSearch.top = lpht->pt.y;
6329 rcSearch.right = rcSearch.left + 1;
6330 rcSearch.bottom = rcSearch.top + 1;
6332 iterator_frameditems(&i, infoPtr, &rcSearch);
6333 iterator_next(&i); /* go to first item in the sequence */
6334 iItem = i.nItem;
6335 iterator_destroy(&i);
6337 TRACE("lpht->iItem=%d\n", iItem);
6338 if (iItem == -1) return -1;
6340 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6341 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6342 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6343 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6344 lvItem.iItem = iItem;
6345 lvItem.iSubItem = 0;
6346 lvItem.pszText = szDispText;
6347 lvItem.cchTextMax = DISP_TEXT_SIZE;
6348 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6349 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6351 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6352 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6353 opt.x = lpht->pt.x - Position.x - Origin.x;
6354 opt.y = lpht->pt.y - Position.y - Origin.y;
6356 if (uView == LVS_REPORT)
6357 rcBounds = rcBox;
6358 else
6360 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6361 UnionRect(&rcBounds, &rcBounds, &rcState);
6363 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6364 if (!PtInRect(&rcBounds, opt)) return -1;
6366 if (PtInRect(&rcIcon, opt))
6367 lpht->flags |= LVHT_ONITEMICON;
6368 else if (PtInRect(&rcLabel, opt))
6369 lpht->flags |= LVHT_ONITEMLABEL;
6370 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6371 lpht->flags |= LVHT_ONITEMSTATEICON;
6372 if (lpht->flags & LVHT_ONITEM)
6373 lpht->flags &= ~LVHT_NOWHERE;
6375 TRACE("lpht->flags=0x%x\n", lpht->flags);
6376 if (uView == LVS_REPORT && subitem)
6378 INT j;
6380 rcBounds.right = rcBounds.left;
6381 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6383 rcBounds.left = rcBounds.right;
6384 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6385 if (PtInRect(&rcBounds, opt))
6387 lpht->iSubItem = j;
6388 break;
6393 if (select && !(uView == LVS_REPORT &&
6394 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6395 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6397 if (uView == LVS_REPORT)
6399 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6400 UnionRect(&rcBounds, &rcBounds, &rcState);
6402 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6404 return lpht->iItem = iItem;
6408 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6409 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6410 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6411 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6412 their own sort proc. when sending LVM_SORTITEMS.
6414 /* Platform SDK:
6415 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6417 LVS_SORTXXX must be specified,
6418 LVS_OWNERDRAW is not set,
6419 <item>.pszText is not LPSTR_TEXTCALLBACK.
6421 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6422 are sorted based on item text..."
6424 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6426 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6427 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6428 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6430 /* if we're sorting descending, negate the return value */
6431 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6434 /***
6435 * DESCRIPTION:
6436 * Inserts a new item in the listview control.
6438 * PARAMETER(S):
6439 * [I] infoPtr : valid pointer to the listview structure
6440 * [I] lpLVItem : item information
6441 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6443 * RETURN:
6444 * SUCCESS : new item index
6445 * FAILURE : -1
6447 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6449 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6450 INT nItem;
6451 HDPA hdpaSubItems;
6452 NMLISTVIEW nmlv;
6453 ITEM_INFO *lpItem;
6454 BOOL is_sorted, has_changed;
6455 LVITEMW item;
6456 HWND hwndSelf = infoPtr->hwndSelf;
6458 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6460 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6462 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6463 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6465 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6467 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6469 /* insert item in listview control data structure */
6470 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6471 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6473 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6474 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6476 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6478 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6479 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6480 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6481 if (nItem == -1) goto fail;
6482 infoPtr->nItemCount++;
6484 /* shift indices first so they don't get tangled */
6485 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6487 /* set the item attributes */
6488 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6490 /* full size structure expected - _WIN32IE >= 0x560 */
6491 item = *lpLVItem;
6493 else if (lpLVItem->mask & LVIF_INDENT)
6495 /* indent member expected - _WIN32IE >= 0x300 */
6496 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6498 else
6500 /* minimal structure expected */
6501 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6503 item.iItem = nItem;
6504 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6506 item.mask |= LVIF_STATE;
6507 item.stateMask |= LVIS_STATEIMAGEMASK;
6508 item.state &= ~LVIS_STATEIMAGEMASK;
6509 item.state |= INDEXTOSTATEIMAGEMASK(1);
6511 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6513 /* if we're sorted, sort the list, and update the index */
6514 if (is_sorted)
6516 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6517 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6518 assert(nItem != -1);
6521 /* make room for the position, if we are in the right mode */
6522 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6524 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6525 goto undo;
6526 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6528 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6529 goto undo;
6533 /* send LVN_INSERTITEM notification */
6534 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6535 nmlv.iItem = nItem;
6536 nmlv.lParam = lpItem->lParam;
6537 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6538 if (!IsWindow(hwndSelf))
6539 return -1;
6541 /* align items (set position of each item) */
6542 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6544 POINT pt;
6546 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6547 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6548 else
6549 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6551 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6554 /* now is the invalidation fun */
6555 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6556 return nItem;
6558 undo:
6559 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6560 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6561 infoPtr->nItemCount--;
6562 fail:
6563 DPA_DeletePtr(hdpaSubItems, 0);
6564 DPA_Destroy (hdpaSubItems);
6565 Free (lpItem);
6566 return -1;
6569 /***
6570 * DESCRIPTION:
6571 * Redraws a range of items.
6573 * PARAMETER(S):
6574 * [I] infoPtr : valid pointer to the listview structure
6575 * [I] nFirst : first item
6576 * [I] nLast : last item
6578 * RETURN:
6579 * SUCCESS : TRUE
6580 * FAILURE : FALSE
6582 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6584 INT i;
6586 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6587 max(nFirst, nLast) >= infoPtr->nItemCount)
6588 return FALSE;
6590 for (i = nFirst; i <= nLast; i++)
6591 LISTVIEW_InvalidateItem(infoPtr, i);
6593 return TRUE;
6596 /***
6597 * DESCRIPTION:
6598 * Scroll the content of a listview.
6600 * PARAMETER(S):
6601 * [I] infoPtr : valid pointer to the listview structure
6602 * [I] dx : horizontal scroll amount in pixels
6603 * [I] dy : vertical scroll amount in pixels
6605 * RETURN:
6606 * SUCCESS : TRUE
6607 * FAILURE : FALSE
6609 * COMMENTS:
6610 * If the control is in report mode (LVS_REPORT) the control can
6611 * be scrolled only in line increments. "dy" will be rounded to the
6612 * nearest number of pixels that are a whole line. Ex: if line height
6613 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6614 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6616 * For: (per experimentation with native control and CSpy ListView)
6617 * LVS_ICON dy=1 = 1 pixel (vertical only)
6618 * dx ignored
6619 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6620 * dx ignored
6621 * LVS_LIST dx=1 = 1 column (horizontal only)
6622 * but will only scroll 1 column per message
6623 * no matter what the value.
6624 * dy must be 0 or FALSE returned.
6625 * LVS_REPORT dx=1 = 1 pixel
6626 * dy= see above
6629 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6631 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6632 case LVS_REPORT:
6633 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6634 dy /= infoPtr->nItemHeight;
6635 break;
6636 case LVS_LIST:
6637 if (dy != 0) return FALSE;
6638 break;
6639 default: /* icon */
6640 dx = 0;
6641 break;
6644 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6645 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6647 return TRUE;
6650 /***
6651 * DESCRIPTION:
6652 * Sets the background color.
6654 * PARAMETER(S):
6655 * [I] infoPtr : valid pointer to the listview structure
6656 * [I] clrBk : background color
6658 * RETURN:
6659 * SUCCESS : TRUE
6660 * FAILURE : FALSE
6662 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6664 TRACE("(clrBk=%x)\n", clrBk);
6666 if(infoPtr->clrBk != clrBk) {
6667 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6668 infoPtr->clrBk = clrBk;
6669 if (clrBk == CLR_NONE)
6670 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6671 else
6672 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6673 LISTVIEW_InvalidateList(infoPtr);
6676 return TRUE;
6679 /* LISTVIEW_SetBkImage */
6681 /*** Helper for {Insert,Set}ColumnT *only* */
6682 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6683 const LVCOLUMNW *lpColumn, BOOL isW)
6685 if (lpColumn->mask & LVCF_FMT)
6687 /* format member is valid */
6688 lphdi->mask |= HDI_FORMAT;
6690 /* set text alignment (leftmost column must be left-aligned) */
6691 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6692 lphdi->fmt |= HDF_LEFT;
6693 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6694 lphdi->fmt |= HDF_RIGHT;
6695 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6696 lphdi->fmt |= HDF_CENTER;
6698 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6699 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6701 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6703 lphdi->fmt |= HDF_IMAGE;
6704 lphdi->iImage = I_IMAGECALLBACK;
6708 if (lpColumn->mask & LVCF_WIDTH)
6710 lphdi->mask |= HDI_WIDTH;
6711 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6713 /* make it fill the remainder of the controls width */
6714 RECT rcHeader;
6715 INT item_index;
6717 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6719 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6720 lphdi->cxy += rcHeader.right - rcHeader.left;
6723 /* retrieve the layout of the header */
6724 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6725 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6727 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6729 else
6730 lphdi->cxy = lpColumn->cx;
6733 if (lpColumn->mask & LVCF_TEXT)
6735 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6736 lphdi->fmt |= HDF_STRING;
6737 lphdi->pszText = lpColumn->pszText;
6738 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6741 if (lpColumn->mask & LVCF_IMAGE)
6743 lphdi->mask |= HDI_IMAGE;
6744 lphdi->iImage = lpColumn->iImage;
6747 if (lpColumn->mask & LVCF_ORDER)
6749 lphdi->mask |= HDI_ORDER;
6750 lphdi->iOrder = lpColumn->iOrder;
6755 /***
6756 * DESCRIPTION:
6757 * Inserts a new column.
6759 * PARAMETER(S):
6760 * [I] infoPtr : valid pointer to the listview structure
6761 * [I] nColumn : column index
6762 * [I] lpColumn : column information
6763 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6765 * RETURN:
6766 * SUCCESS : new column index
6767 * FAILURE : -1
6769 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6770 const LVCOLUMNW *lpColumn, BOOL isW)
6772 COLUMN_INFO *lpColumnInfo;
6773 INT nNewColumn;
6774 HDITEMW hdi;
6776 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6778 if (!lpColumn || nColumn < 0) return -1;
6779 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6781 ZeroMemory(&hdi, sizeof(HDITEMW));
6782 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6785 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6786 * (can be seen in SPY) otherwise column never gets added.
6788 if (!(lpColumn->mask & LVCF_WIDTH)) {
6789 hdi.mask |= HDI_WIDTH;
6790 hdi.cxy = 10;
6794 * when the iSubItem is available Windows copies it to the header lParam. It seems
6795 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6797 if (lpColumn->mask & LVCF_SUBITEM)
6799 hdi.mask |= HDI_LPARAM;
6800 hdi.lParam = lpColumn->iSubItem;
6803 /* insert item in header control */
6804 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6805 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6806 (WPARAM)nColumn, (LPARAM)&hdi);
6807 if (nNewColumn == -1) return -1;
6808 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6810 /* create our own column info */
6811 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6812 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6814 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6815 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6817 /* now we have to actually adjust the data */
6818 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6820 SUBITEM_INFO *lpSubItem;
6821 HDPA hdpaSubItems;
6822 INT nItem, i;
6824 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6826 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6827 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6829 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6830 if (lpSubItem->iSubItem >= nNewColumn)
6831 lpSubItem->iSubItem++;
6836 /* make space for the new column */
6837 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6838 LISTVIEW_UpdateItemSize(infoPtr);
6840 return nNewColumn;
6842 fail:
6843 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6844 if (lpColumnInfo)
6846 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6847 Free(lpColumnInfo);
6849 return -1;
6852 /***
6853 * DESCRIPTION:
6854 * Sets the attributes of a header item.
6856 * PARAMETER(S):
6857 * [I] infoPtr : valid pointer to the listview structure
6858 * [I] nColumn : column index
6859 * [I] lpColumn : column attributes
6860 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6862 * RETURN:
6863 * SUCCESS : TRUE
6864 * FAILURE : FALSE
6866 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6867 const LVCOLUMNW *lpColumn, BOOL isW)
6869 HDITEMW hdi, hdiget;
6870 BOOL bResult;
6872 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6874 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6876 ZeroMemory(&hdi, sizeof(HDITEMW));
6877 if (lpColumn->mask & LVCF_FMT)
6879 hdi.mask |= HDI_FORMAT;
6880 hdiget.mask = HDI_FORMAT;
6881 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6882 hdi.fmt = hdiget.fmt & HDF_STRING;
6884 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6886 /* set header item attributes */
6887 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6888 if (!bResult) return FALSE;
6890 if (lpColumn->mask & LVCF_FMT)
6892 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6893 int oldFmt = lpColumnInfo->fmt;
6895 lpColumnInfo->fmt = lpColumn->fmt;
6896 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6898 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6899 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6903 return TRUE;
6906 /***
6907 * DESCRIPTION:
6908 * Sets the column order array
6910 * PARAMETERS:
6911 * [I] infoPtr : valid pointer to the listview structure
6912 * [I] iCount : number of elements in column order array
6913 * [I] lpiArray : pointer to column order array
6915 * RETURN:
6916 * SUCCESS : TRUE
6917 * FAILURE : FALSE
6919 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6921 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6923 if (!lpiArray)
6924 return FALSE;
6926 return TRUE;
6930 /***
6931 * DESCRIPTION:
6932 * Sets the width of a column
6934 * PARAMETERS:
6935 * [I] infoPtr : valid pointer to the listview structure
6936 * [I] nColumn : column index
6937 * [I] cx : column width
6939 * RETURN:
6940 * SUCCESS : TRUE
6941 * FAILURE : FALSE
6943 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6945 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6946 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6947 INT max_cx = 0;
6948 HDITEMW hdi;
6950 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6952 /* set column width only if in report or list mode */
6953 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6955 /* take care of invalid cx values */
6956 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6957 else if (uView == LVS_LIST && cx < 1) return FALSE;
6959 /* resize all columns if in LVS_LIST mode */
6960 if(uView == LVS_LIST)
6962 infoPtr->nItemWidth = cx;
6963 LISTVIEW_InvalidateList(infoPtr);
6964 return TRUE;
6967 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6969 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6971 INT nLabelWidth;
6972 LVITEMW lvItem;
6974 lvItem.mask = LVIF_TEXT;
6975 lvItem.iItem = 0;
6976 lvItem.iSubItem = nColumn;
6977 lvItem.pszText = szDispText;
6978 lvItem.cchTextMax = DISP_TEXT_SIZE;
6979 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6981 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6982 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6983 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6985 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6986 max_cx += infoPtr->iconSize.cx;
6987 max_cx += TRAILING_LABEL_PADDING;
6990 /* autosize based on listview items width */
6991 if(cx == LVSCW_AUTOSIZE)
6992 cx = max_cx;
6993 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6995 /* if iCol is the last column make it fill the remainder of the controls width */
6996 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6998 RECT rcHeader;
6999 POINT Origin;
7001 LISTVIEW_GetOrigin(infoPtr, &Origin);
7002 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7004 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7006 else
7008 /* Despite what the MS docs say, if this is not the last
7009 column, then MS resizes the column to the width of the
7010 largest text string in the column, including headers
7011 and items. This is different from LVSCW_AUTOSIZE in that
7012 LVSCW_AUTOSIZE ignores the header string length. */
7013 cx = 0;
7015 /* retrieve header text */
7016 hdi.mask = HDI_TEXT;
7017 hdi.cchTextMax = DISP_TEXT_SIZE;
7018 hdi.pszText = szDispText;
7019 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7021 HDC hdc = GetDC(infoPtr->hwndSelf);
7022 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7023 SIZE size;
7025 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7026 cx = size.cx + TRAILING_HEADER_PADDING;
7027 /* FIXME: Take into account the header image, if one is present */
7028 SelectObject(hdc, old_font);
7029 ReleaseDC(infoPtr->hwndSelf, hdc);
7031 cx = max (cx, max_cx);
7035 if (cx < 0) return FALSE;
7037 /* call header to update the column change */
7038 hdi.mask = HDI_WIDTH;
7039 hdi.cxy = cx;
7040 TRACE("hdi.cxy=%d\n", hdi.cxy);
7041 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7044 /***
7045 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7048 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7050 HDC hdc_wnd, hdc;
7051 HBITMAP hbm_im, hbm_mask, hbm_orig;
7052 RECT rc;
7053 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7054 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7055 HIMAGELIST himl;
7057 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7058 ILC_COLOR | ILC_MASK, 2, 2);
7059 hdc_wnd = GetDC(infoPtr->hwndSelf);
7060 hdc = CreateCompatibleDC(hdc_wnd);
7061 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7062 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7063 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7065 rc.left = rc.top = 0;
7066 rc.right = GetSystemMetrics(SM_CXSMICON);
7067 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7069 hbm_orig = SelectObject(hdc, hbm_mask);
7070 FillRect(hdc, &rc, hbr_white);
7071 InflateRect(&rc, -3, -3);
7072 FillRect(hdc, &rc, hbr_black);
7074 SelectObject(hdc, hbm_im);
7075 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7076 SelectObject(hdc, hbm_orig);
7077 ImageList_Add(himl, hbm_im, hbm_mask);
7079 SelectObject(hdc, hbm_im);
7080 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7081 SelectObject(hdc, hbm_orig);
7082 ImageList_Add(himl, hbm_im, hbm_mask);
7084 DeleteObject(hbm_mask);
7085 DeleteObject(hbm_im);
7086 DeleteDC(hdc);
7088 return himl;
7091 /***
7092 * DESCRIPTION:
7093 * Sets the extended listview style.
7095 * PARAMETERS:
7096 * [I] infoPtr : valid pointer to the listview structure
7097 * [I] dwMask : mask
7098 * [I] dwStyle : style
7100 * RETURN:
7101 * SUCCESS : previous style
7102 * FAILURE : 0
7104 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7106 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7108 /* set new style */
7109 if (dwMask)
7110 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7111 else
7112 infoPtr->dwLvExStyle = dwExStyle;
7114 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7116 HIMAGELIST himl = 0;
7117 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7119 LVITEMW item;
7120 item.mask = LVIF_STATE;
7121 item.stateMask = LVIS_STATEIMAGEMASK;
7122 item.state = INDEXTOSTATEIMAGEMASK(1);
7123 LISTVIEW_SetItemState(infoPtr, -1, &item);
7125 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7127 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7130 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7132 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7133 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7134 dwStyle |= HDS_DRAGDROP;
7135 else
7136 dwStyle &= ~HDS_DRAGDROP;
7137 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7140 /* GRIDLINES adds decoration at top so changes sizes */
7141 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7143 LISTVIEW_UpdateSize(infoPtr);
7147 LISTVIEW_InvalidateList(infoPtr);
7148 return dwOldExStyle;
7151 /***
7152 * DESCRIPTION:
7153 * Sets the new hot cursor used during hot tracking and hover selection.
7155 * PARAMETER(S):
7156 * [I] infoPtr : valid pointer to the listview structure
7157 * [I] hCursor : the new hot cursor handle
7159 * RETURN:
7160 * Returns the previous hot cursor
7162 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7164 HCURSOR oldCursor = infoPtr->hHotCursor;
7166 infoPtr->hHotCursor = hCursor;
7168 return oldCursor;
7172 /***
7173 * DESCRIPTION:
7174 * Sets the hot item index.
7176 * PARAMETERS:
7177 * [I] infoPtr : valid pointer to the listview structure
7178 * [I] iIndex : index
7180 * RETURN:
7181 * SUCCESS : previous hot item index
7182 * FAILURE : -1 (no hot item)
7184 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7186 INT iOldIndex = infoPtr->nHotItem;
7188 infoPtr->nHotItem = iIndex;
7190 return iOldIndex;
7194 /***
7195 * DESCRIPTION:
7196 * Sets the amount of time the cursor must hover over an item before it is selected.
7198 * PARAMETER(S):
7199 * [I] infoPtr : valid pointer to the listview structure
7200 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7202 * RETURN:
7203 * Returns the previous hover time
7205 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7207 DWORD oldHoverTime = infoPtr->dwHoverTime;
7209 infoPtr->dwHoverTime = dwHoverTime;
7211 return oldHoverTime;
7214 /***
7215 * DESCRIPTION:
7216 * Sets spacing for icons of LVS_ICON style.
7218 * PARAMETER(S):
7219 * [I] infoPtr : valid pointer to the listview structure
7220 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7221 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7223 * RETURN:
7224 * MAKELONG(oldcx, oldcy)
7226 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7228 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7229 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7231 TRACE("requested=(%d,%d)\n", cx, cy);
7233 /* this is supported only for LVS_ICON style */
7234 if (uView != LVS_ICON) return oldspacing;
7236 /* set to defaults, if instructed to */
7237 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7238 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7240 /* if 0 then compute width
7241 * FIXME: Should scan each item and determine max width of
7242 * icon or label, then make that the width */
7243 if (cx == 0)
7244 cx = infoPtr->iconSpacing.cx;
7246 /* if 0 then compute height */
7247 if (cy == 0)
7248 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7249 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7252 infoPtr->iconSpacing.cx = cx;
7253 infoPtr->iconSpacing.cy = cy;
7255 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7256 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7257 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7258 infoPtr->ntmHeight);
7260 /* these depend on the iconSpacing */
7261 LISTVIEW_UpdateItemSize(infoPtr);
7263 return oldspacing;
7266 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7268 INT cx, cy;
7270 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7272 size->cx = cx;
7273 size->cy = cy;
7275 else
7277 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7278 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7282 /***
7283 * DESCRIPTION:
7284 * Sets image lists.
7286 * PARAMETER(S):
7287 * [I] infoPtr : valid pointer to the listview structure
7288 * [I] nType : image list type
7289 * [I] himl : image list handle
7291 * RETURN:
7292 * SUCCESS : old image list
7293 * FAILURE : NULL
7295 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7297 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7298 INT oldHeight = infoPtr->nItemHeight;
7299 HIMAGELIST himlOld = 0;
7301 TRACE("(nType=%d, himl=%p\n", nType, himl);
7303 switch (nType)
7305 case LVSIL_NORMAL:
7306 himlOld = infoPtr->himlNormal;
7307 infoPtr->himlNormal = himl;
7308 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7309 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7310 break;
7312 case LVSIL_SMALL:
7313 himlOld = infoPtr->himlSmall;
7314 infoPtr->himlSmall = himl;
7315 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7316 break;
7318 case LVSIL_STATE:
7319 himlOld = infoPtr->himlState;
7320 infoPtr->himlState = himl;
7321 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7322 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7323 break;
7325 default:
7326 ERR("Unknown icon type=%d\n", nType);
7327 return NULL;
7330 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7331 if (infoPtr->nItemHeight != oldHeight)
7332 LISTVIEW_UpdateScroll(infoPtr);
7334 return himlOld;
7337 /***
7338 * DESCRIPTION:
7339 * Preallocates memory (does *not* set the actual count of items !)
7341 * PARAMETER(S):
7342 * [I] infoPtr : valid pointer to the listview structure
7343 * [I] nItems : item count (projected number of items to allocate)
7344 * [I] dwFlags : update flags
7346 * RETURN:
7347 * SUCCESS : TRUE
7348 * FAILURE : FALSE
7350 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7352 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7354 if (infoPtr->dwStyle & LVS_OWNERDATA)
7356 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7357 INT nOldCount = infoPtr->nItemCount;
7359 if (nItems < nOldCount)
7361 RANGE range = { nItems, nOldCount };
7362 ranges_del(infoPtr->selectionRanges, range);
7363 if (infoPtr->nFocusedItem >= nItems)
7365 infoPtr->nFocusedItem = -1;
7366 SetRectEmpty(&infoPtr->rcFocus);
7370 infoPtr->nItemCount = nItems;
7371 LISTVIEW_UpdateScroll(infoPtr);
7373 /* the flags are valid only in ownerdata report and list modes */
7374 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7376 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7377 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7379 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7380 LISTVIEW_InvalidateList(infoPtr);
7381 else
7383 INT nFrom, nTo;
7384 POINT Origin;
7385 RECT rcErase;
7387 LISTVIEW_GetOrigin(infoPtr, &Origin);
7388 nFrom = min(nOldCount, nItems);
7389 nTo = max(nOldCount, nItems);
7391 if (uView == LVS_REPORT)
7393 rcErase.left = 0;
7394 rcErase.top = nFrom * infoPtr->nItemHeight;
7395 rcErase.right = infoPtr->nItemWidth;
7396 rcErase.bottom = nTo * infoPtr->nItemHeight;
7397 OffsetRect(&rcErase, Origin.x, Origin.y);
7398 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7399 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7401 else /* LVS_LIST */
7403 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7405 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7406 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7407 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7408 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7409 OffsetRect(&rcErase, Origin.x, Origin.y);
7410 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7411 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7413 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7414 rcErase.top = 0;
7415 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7416 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7417 OffsetRect(&rcErase, Origin.x, Origin.y);
7418 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7419 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7423 else
7425 /* According to MSDN for non-LVS_OWNERDATA this is just
7426 * a performance issue. The control allocates its internal
7427 * data structures for the number of items specified. It
7428 * cuts down on the number of memory allocations. Therefore
7429 * we will just issue a WARN here
7431 WARN("for non-ownerdata performance option not implemented.\n");
7434 return TRUE;
7437 /***
7438 * DESCRIPTION:
7439 * Sets the position of an item.
7441 * PARAMETER(S):
7442 * [I] infoPtr : valid pointer to the listview structure
7443 * [I] nItem : item index
7444 * [I] pt : coordinate
7446 * RETURN:
7447 * SUCCESS : TRUE
7448 * FAILURE : FALSE
7450 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7452 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7453 POINT Origin;
7455 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7457 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7458 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7460 LISTVIEW_GetOrigin(infoPtr, &Origin);
7462 /* This point value seems to be an undocumented feature.
7463 * The best guess is that it means either at the origin,
7464 * or at true beginning of the list. I will assume the origin. */
7465 if ((pt.x == -1) && (pt.y == -1))
7466 pt = Origin;
7468 if (uView == LVS_ICON)
7470 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7471 pt.y -= ICON_TOP_PADDING;
7473 pt.x -= Origin.x;
7474 pt.y -= Origin.y;
7476 infoPtr->bAutoarrange = FALSE;
7478 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7481 /***
7482 * DESCRIPTION:
7483 * Sets the state of one or many items.
7485 * PARAMETER(S):
7486 * [I] infoPtr : valid pointer to the listview structure
7487 * [I] nItem : item index
7488 * [I] lpLVItem : item or subitem info
7490 * RETURN:
7491 * SUCCESS : TRUE
7492 * FAILURE : FALSE
7494 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7496 BOOL bResult = TRUE;
7497 LVITEMW lvItem;
7499 lvItem.iItem = nItem;
7500 lvItem.iSubItem = 0;
7501 lvItem.mask = LVIF_STATE;
7502 lvItem.state = lpLVItem->state;
7503 lvItem.stateMask = lpLVItem->stateMask;
7504 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7506 if (nItem == -1)
7508 /* apply to all items */
7509 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7510 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7512 else
7513 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7516 * Update selection mark
7518 * Investigation on windows 2k showed that selection mark was updated
7519 * whenever a new selection was made, but if the selected item was
7520 * unselected it was not updated.
7522 * we are probably still not 100% accurate, but this at least sets the
7523 * proper selection mark when it is needed
7526 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7527 (infoPtr->nSelectionMark == -1))
7529 int i;
7530 for (i = 0; i < infoPtr->nItemCount; i++)
7532 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7534 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7536 infoPtr->nSelectionMark = i;
7537 break;
7540 else if (ranges_contain(infoPtr->selectionRanges, i))
7542 infoPtr->nSelectionMark = i;
7543 break;
7548 return bResult;
7551 /***
7552 * DESCRIPTION:
7553 * Sets the text of an item or subitem.
7555 * PARAMETER(S):
7556 * [I] hwnd : window handle
7557 * [I] nItem : item index
7558 * [I] lpLVItem : item or subitem info
7559 * [I] isW : TRUE if input is Unicode
7561 * RETURN:
7562 * SUCCESS : TRUE
7563 * FAILURE : FALSE
7565 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7567 LVITEMW lvItem;
7569 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7571 lvItem.iItem = nItem;
7572 lvItem.iSubItem = lpLVItem->iSubItem;
7573 lvItem.mask = LVIF_TEXT;
7574 lvItem.pszText = lpLVItem->pszText;
7575 lvItem.cchTextMax = lpLVItem->cchTextMax;
7577 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7579 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7582 /***
7583 * DESCRIPTION:
7584 * Set item index that marks the start of a multiple selection.
7586 * PARAMETER(S):
7587 * [I] infoPtr : valid pointer to the listview structure
7588 * [I] nIndex : index
7590 * RETURN:
7591 * Index number or -1 if there is no selection mark.
7593 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7595 INT nOldIndex = infoPtr->nSelectionMark;
7597 TRACE("(nIndex=%d)\n", nIndex);
7599 infoPtr->nSelectionMark = nIndex;
7601 return nOldIndex;
7604 /***
7605 * DESCRIPTION:
7606 * Sets the text background color.
7608 * PARAMETER(S):
7609 * [I] infoPtr : valid pointer to the listview structure
7610 * [I] clrTextBk : text background color
7612 * RETURN:
7613 * SUCCESS : TRUE
7614 * FAILURE : FALSE
7616 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7618 TRACE("(clrTextBk=%x)\n", clrTextBk);
7620 if (infoPtr->clrTextBk != clrTextBk)
7622 infoPtr->clrTextBk = clrTextBk;
7623 LISTVIEW_InvalidateList(infoPtr);
7626 return TRUE;
7629 /***
7630 * DESCRIPTION:
7631 * Sets the text foreground color.
7633 * PARAMETER(S):
7634 * [I] infoPtr : valid pointer to the listview structure
7635 * [I] clrText : text color
7637 * RETURN:
7638 * SUCCESS : TRUE
7639 * FAILURE : FALSE
7641 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7643 TRACE("(clrText=%x)\n", clrText);
7645 if (infoPtr->clrText != clrText)
7647 infoPtr->clrText = clrText;
7648 LISTVIEW_InvalidateList(infoPtr);
7651 return TRUE;
7654 /***
7655 * DESCRIPTION:
7656 * Determines which listview item is located at the specified position.
7658 * PARAMETER(S):
7659 * [I] infoPtr : valid pointer to the listview structure
7660 * [I] hwndNewToolTip : handle to new ToolTip
7662 * RETURN:
7663 * old tool tip
7665 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7667 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7668 infoPtr->hwndToolTip = hwndNewToolTip;
7669 return hwndOldToolTip;
7673 * DESCRIPTION:
7674 * sets the Unicode character format flag for the control
7675 * PARAMETER(S):
7676 * [I] infoPtr :valid pointer to the listview structure
7677 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7679 * RETURN:
7680 * Old Unicode Format
7682 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7684 BOOL rc = infoPtr->notifyFormat;
7685 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7686 return rc;
7689 /* LISTVIEW_SetWorkAreas */
7691 /***
7692 * DESCRIPTION:
7693 * Callback internally used by LISTVIEW_SortItems()
7695 * PARAMETER(S):
7696 * [I] first : pointer to first ITEM_INFO to compare
7697 * [I] second : pointer to second ITEM_INFO to compare
7698 * [I] lParam : HWND of control
7700 * RETURN:
7701 * if first comes before second : negative
7702 * if first comes after second : positive
7703 * if first and second are equivalent : zero
7705 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7707 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7708 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7709 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7711 /* Forward the call to the client defined callback */
7712 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7715 /***
7716 * DESCRIPTION:
7717 * Sorts the listview items.
7719 * PARAMETER(S):
7720 * [I] infoPtr : valid pointer to the listview structure
7721 * [I] pfnCompare : application-defined value
7722 * [I] lParamSort : pointer to comparison callback
7724 * RETURN:
7725 * SUCCESS : TRUE
7726 * FAILURE : FALSE
7728 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7730 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7731 HDPA hdpaSubItems;
7732 ITEM_INFO *lpItem;
7733 LPVOID selectionMarkItem;
7734 LVITEMW item;
7735 int i;
7737 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7739 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7741 if (!pfnCompare) return FALSE;
7742 if (!infoPtr->hdpaItems) return FALSE;
7744 /* if there are 0 or 1 items, there is no need to sort */
7745 if (infoPtr->nItemCount < 2) return TRUE;
7747 if (infoPtr->nFocusedItem >= 0)
7749 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7750 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7751 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7753 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7754 /* clear the lpItem->state for non-selected ones */
7755 /* remove the selection ranges */
7757 infoPtr->pfnCompare = pfnCompare;
7758 infoPtr->lParamSort = lParamSort;
7759 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7761 /* Adjust selections and indices so that they are the way they should
7762 * be after the sort (otherwise, the list items move around, but
7763 * whatever is at the item's previous original position will be
7764 * selected instead)
7766 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7767 for (i=0; i < infoPtr->nItemCount; i++)
7769 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7770 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7772 if (lpItem->state & LVIS_SELECTED)
7774 item.state = LVIS_SELECTED;
7775 item.stateMask = LVIS_SELECTED;
7776 LISTVIEW_SetItemState(infoPtr, i, &item);
7778 if (lpItem->state & LVIS_FOCUSED)
7780 infoPtr->nFocusedItem = i;
7781 lpItem->state &= ~LVIS_FOCUSED;
7784 if (selectionMarkItem != NULL)
7785 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7786 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7788 /* refresh the display */
7789 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7790 LISTVIEW_InvalidateList(infoPtr);
7792 return TRUE;
7795 /***
7796 * DESCRIPTION:
7797 * Update theme handle after a theme change.
7799 * PARAMETER(S):
7800 * [I] infoPtr : valid pointer to the listview structure
7802 * RETURN:
7803 * SUCCESS : 0
7804 * FAILURE : something else
7806 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7808 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7809 CloseThemeData(theme);
7810 OpenThemeData(infoPtr->hwndSelf, themeClass);
7811 return 0;
7814 /***
7815 * DESCRIPTION:
7816 * Updates an items or rearranges the listview control.
7818 * PARAMETER(S):
7819 * [I] infoPtr : valid pointer to the listview structure
7820 * [I] nItem : item index
7822 * RETURN:
7823 * SUCCESS : TRUE
7824 * FAILURE : FALSE
7826 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7828 TRACE("(nItem=%d)\n", nItem);
7830 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7832 /* rearrange with default alignment style */
7833 if (is_autoarrange(infoPtr))
7834 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7835 else
7836 LISTVIEW_InvalidateItem(infoPtr, nItem);
7838 return TRUE;
7841 /***
7842 * DESCRIPTION:
7843 * Draw the track line at the place defined in the infoPtr structure.
7844 * The line is drawn with a XOR pen so drawing the line for the second time
7845 * in the same place erases the line.
7847 * PARAMETER(S):
7848 * [I] infoPtr : valid pointer to the listview structure
7850 * RETURN:
7851 * SUCCESS : TRUE
7852 * FAILURE : FALSE
7854 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7856 HPEN hOldPen;
7857 HDC hdc;
7858 INT oldROP;
7860 if (infoPtr->xTrackLine == -1)
7861 return FALSE;
7863 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7864 return FALSE;
7865 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7866 oldROP = SetROP2(hdc, R2_XORPEN);
7867 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7868 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7869 SetROP2(hdc, oldROP);
7870 SelectObject(hdc, hOldPen);
7871 ReleaseDC(infoPtr->hwndSelf, hdc);
7872 return TRUE;
7875 /***
7876 * DESCRIPTION:
7877 * Called when an edit control should be displayed. This function is called after
7878 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7880 * PARAMETER(S):
7881 * [I] hwnd : Handle to the listview
7882 * [I] uMsg : WM_TIMER (ignored)
7883 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7884 * [I] dwTimer : The elapsed time (ignored)
7886 * RETURN:
7887 * None.
7889 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7891 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7892 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7894 KillTimer(hwnd, idEvent);
7895 editItem->fEnabled = FALSE;
7896 /* check if the item is still selected */
7897 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7898 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7901 /***
7902 * DESCRIPTION:
7903 * Creates the listview control - the WM_NCCREATE phase.
7905 * PARAMETER(S):
7906 * [I] hwnd : window handle
7907 * [I] lpcs : the create parameters
7909 * RETURN:
7910 * Success: TRUE
7911 * Failure: FALSE
7913 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7915 LISTVIEW_INFO *infoPtr;
7916 LOGFONTW logFont;
7918 TRACE("(lpcs=%p)\n", lpcs);
7920 /* initialize info pointer */
7921 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7922 if (!infoPtr) return FALSE;
7924 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7926 infoPtr->hwndSelf = hwnd;
7927 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7928 /* determine the type of structures to use */
7929 infoPtr->hwndNotify = lpcs->hwndParent;
7930 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7932 /* initialize color information */
7933 infoPtr->clrBk = CLR_NONE;
7934 infoPtr->clrText = CLR_DEFAULT;
7935 infoPtr->clrTextBk = CLR_DEFAULT;
7936 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7938 /* set default values */
7939 infoPtr->nFocusedItem = -1;
7940 infoPtr->nSelectionMark = -1;
7941 infoPtr->nHotItem = -1;
7942 infoPtr->bRedraw = TRUE;
7943 infoPtr->bNoItemMetrics = TRUE;
7944 infoPtr->bDoChangeNotify = TRUE;
7945 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7946 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7947 infoPtr->nEditLabelItem = -1;
7948 infoPtr->dwHoverTime = -1; /* default system hover time */
7949 infoPtr->nMeasureItemHeight = 0;
7950 infoPtr->xTrackLine = -1; /* no track line */
7951 infoPtr->itemEdit.fEnabled = FALSE;
7953 /* get default font (icon title) */
7954 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7955 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7956 infoPtr->hFont = infoPtr->hDefaultFont;
7957 LISTVIEW_SaveTextMetrics(infoPtr);
7959 /* allocate memory for the data structure */
7960 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7961 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7962 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7963 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7964 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7965 return TRUE;
7967 fail:
7968 DestroyWindow(infoPtr->hwndHeader);
7969 ranges_destroy(infoPtr->selectionRanges);
7970 DPA_Destroy(infoPtr->hdpaItems);
7971 DPA_Destroy(infoPtr->hdpaPosX);
7972 DPA_Destroy(infoPtr->hdpaPosY);
7973 DPA_Destroy(infoPtr->hdpaColumns);
7974 Free(infoPtr);
7975 return FALSE;
7978 /***
7979 * DESCRIPTION:
7980 * Creates the listview control - the WM_CREATE phase. Most of the data is
7981 * already set up in LISTVIEW_NCCreate
7983 * PARAMETER(S):
7984 * [I] hwnd : window handle
7985 * [I] lpcs : the create parameters
7987 * RETURN:
7988 * Success: 0
7989 * Failure: -1
7991 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7993 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7994 UINT uView = lpcs->style & LVS_TYPEMASK;
7996 TRACE("(lpcs=%p)\n", lpcs);
7998 infoPtr->dwStyle = lpcs->style;
7999 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8000 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8002 /* create header */
8003 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
8004 WS_CHILD | HDS_HORZ | HDS_FULLDRAG | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
8005 0, 0, 0, 0, hwnd, NULL,
8006 lpcs->hInstance, NULL);
8007 if (!infoPtr->hwndHeader) return -1;
8009 /* set header unicode format */
8010 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
8012 /* set header font */
8013 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8015 /* init item size to avoid division by 0 */
8016 LISTVIEW_UpdateItemSize (infoPtr);
8018 if (uView == LVS_REPORT)
8020 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8022 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8024 else
8026 /* set HDS_HIDDEN flag to hide the header bar */
8027 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
8028 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
8032 OpenThemeData(hwnd, themeClass);
8034 /* initialize the icon sizes */
8035 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8036 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8037 return 0;
8040 /***
8041 * DESCRIPTION:
8042 * Destroys the listview control.
8044 * PARAMETER(S):
8045 * [I] infoPtr : valid pointer to the listview structure
8047 * RETURN:
8048 * Success: 0
8049 * Failure: -1
8051 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8053 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8054 CloseThemeData(theme);
8055 return 0;
8058 /***
8059 * DESCRIPTION:
8060 * Enables the listview control.
8062 * PARAMETER(S):
8063 * [I] infoPtr : valid pointer to the listview structure
8064 * [I] bEnable : specifies whether to enable or disable the window
8066 * RETURN:
8067 * SUCCESS : TRUE
8068 * FAILURE : FALSE
8070 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8072 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8073 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8074 return TRUE;
8077 /***
8078 * DESCRIPTION:
8079 * Erases the background of the listview control.
8081 * PARAMETER(S):
8082 * [I] infoPtr : valid pointer to the listview structure
8083 * [I] hdc : device context handle
8085 * RETURN:
8086 * SUCCESS : TRUE
8087 * FAILURE : FALSE
8089 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8091 RECT rc;
8093 TRACE("(hdc=%p)\n", hdc);
8095 if (!GetClipBox(hdc, &rc)) return FALSE;
8097 /* for double buffered controls we need to do this during refresh */
8098 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8100 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8104 /***
8105 * DESCRIPTION:
8106 * Helper function for LISTVIEW_[HV]Scroll *only*.
8107 * Performs vertical/horizontal scrolling by a give amount.
8109 * PARAMETER(S):
8110 * [I] infoPtr : valid pointer to the listview structure
8111 * [I] dx : amount of horizontal scroll
8112 * [I] dy : amount of vertical scroll
8114 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8116 /* now we can scroll the list */
8117 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8118 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8119 /* if we have focus, adjust rect */
8120 OffsetRect(&infoPtr->rcFocus, dx, dy);
8121 UpdateWindow(infoPtr->hwndSelf);
8124 /***
8125 * DESCRIPTION:
8126 * Performs vertical scrolling.
8128 * PARAMETER(S):
8129 * [I] infoPtr : valid pointer to the listview structure
8130 * [I] nScrollCode : scroll code
8131 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8132 * [I] hScrollWnd : scrollbar control window handle
8134 * RETURN:
8135 * Zero
8137 * NOTES:
8138 * SB_LINEUP/SB_LINEDOWN:
8139 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8140 * for LVS_REPORT is 1 line
8141 * for LVS_LIST cannot occur
8144 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8145 INT nScrollDiff, HWND hScrollWnd)
8147 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8148 INT nOldScrollPos, nNewScrollPos;
8149 SCROLLINFO scrollInfo;
8150 BOOL is_an_icon;
8152 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8153 debugscrollcode(nScrollCode), nScrollDiff);
8155 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8157 scrollInfo.cbSize = sizeof(SCROLLINFO);
8158 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8160 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8162 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8164 nOldScrollPos = scrollInfo.nPos;
8165 switch (nScrollCode)
8167 case SB_INTERNAL:
8168 break;
8170 case SB_LINEUP:
8171 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8172 break;
8174 case SB_LINEDOWN:
8175 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8176 break;
8178 case SB_PAGEUP:
8179 nScrollDiff = -scrollInfo.nPage;
8180 break;
8182 case SB_PAGEDOWN:
8183 nScrollDiff = scrollInfo.nPage;
8184 break;
8186 case SB_THUMBPOSITION:
8187 case SB_THUMBTRACK:
8188 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8189 break;
8191 default:
8192 nScrollDiff = 0;
8195 /* quit right away if pos isn't changing */
8196 if (nScrollDiff == 0) return 0;
8198 /* calculate new position, and handle overflows */
8199 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8200 if (nScrollDiff > 0) {
8201 if (nNewScrollPos < nOldScrollPos ||
8202 nNewScrollPos > scrollInfo.nMax)
8203 nNewScrollPos = scrollInfo.nMax;
8204 } else {
8205 if (nNewScrollPos > nOldScrollPos ||
8206 nNewScrollPos < scrollInfo.nMin)
8207 nNewScrollPos = scrollInfo.nMin;
8210 /* set the new position, and reread in case it changed */
8211 scrollInfo.fMask = SIF_POS;
8212 scrollInfo.nPos = nNewScrollPos;
8213 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8215 /* carry on only if it really changed */
8216 if (nNewScrollPos == nOldScrollPos) return 0;
8218 /* now adjust to client coordinates */
8219 nScrollDiff = nOldScrollPos - nNewScrollPos;
8220 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8222 /* and scroll the window */
8223 scroll_list(infoPtr, 0, nScrollDiff);
8225 return 0;
8228 /***
8229 * DESCRIPTION:
8230 * Performs horizontal scrolling.
8232 * PARAMETER(S):
8233 * [I] infoPtr : valid pointer to the listview structure
8234 * [I] nScrollCode : scroll code
8235 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8236 * [I] hScrollWnd : scrollbar control window handle
8238 * RETURN:
8239 * Zero
8241 * NOTES:
8242 * SB_LINELEFT/SB_LINERIGHT:
8243 * for LVS_ICON, LVS_SMALLICON 1 pixel
8244 * for LVS_REPORT is 1 pixel
8245 * for LVS_LIST is 1 column --> which is a 1 because the
8246 * scroll is based on columns not pixels
8249 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8250 INT nScrollDiff, HWND hScrollWnd)
8252 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8253 INT nOldScrollPos, nNewScrollPos;
8254 SCROLLINFO scrollInfo;
8256 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8257 debugscrollcode(nScrollCode), nScrollDiff);
8259 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8261 scrollInfo.cbSize = sizeof(SCROLLINFO);
8262 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8264 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8266 nOldScrollPos = scrollInfo.nPos;
8268 switch (nScrollCode)
8270 case SB_INTERNAL:
8271 break;
8273 case SB_LINELEFT:
8274 nScrollDiff = -1;
8275 break;
8277 case SB_LINERIGHT:
8278 nScrollDiff = 1;
8279 break;
8281 case SB_PAGELEFT:
8282 nScrollDiff = -scrollInfo.nPage;
8283 break;
8285 case SB_PAGERIGHT:
8286 nScrollDiff = scrollInfo.nPage;
8287 break;
8289 case SB_THUMBPOSITION:
8290 case SB_THUMBTRACK:
8291 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8292 break;
8294 default:
8295 nScrollDiff = 0;
8298 /* quit right away if pos isn't changing */
8299 if (nScrollDiff == 0) return 0;
8301 /* calculate new position, and handle overflows */
8302 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8303 if (nScrollDiff > 0) {
8304 if (nNewScrollPos < nOldScrollPos ||
8305 nNewScrollPos > scrollInfo.nMax)
8306 nNewScrollPos = scrollInfo.nMax;
8307 } else {
8308 if (nNewScrollPos > nOldScrollPos ||
8309 nNewScrollPos < scrollInfo.nMin)
8310 nNewScrollPos = scrollInfo.nMin;
8313 /* set the new position, and reread in case it changed */
8314 scrollInfo.fMask = SIF_POS;
8315 scrollInfo.nPos = nNewScrollPos;
8316 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8318 /* carry on only if it really changed */
8319 if (nNewScrollPos == nOldScrollPos) return 0;
8321 if(uView == LVS_REPORT)
8322 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8324 /* now adjust to client coordinates */
8325 nScrollDiff = nOldScrollPos - nNewScrollPos;
8326 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8328 /* and scroll the window */
8329 scroll_list(infoPtr, nScrollDiff, 0);
8331 return 0;
8334 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8336 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8337 INT gcWheelDelta = 0;
8338 INT pulScrollLines = 3;
8339 SCROLLINFO scrollInfo;
8341 TRACE("(wheelDelta=%d)\n", wheelDelta);
8343 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8344 gcWheelDelta -= wheelDelta;
8346 scrollInfo.cbSize = sizeof(SCROLLINFO);
8347 scrollInfo.fMask = SIF_POS;
8349 switch(uView)
8351 case LVS_ICON:
8352 case LVS_SMALLICON:
8354 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8355 * should be fixed in the future.
8357 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8358 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8359 break;
8361 case LVS_REPORT:
8362 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8364 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8365 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8366 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8368 break;
8370 case LVS_LIST:
8371 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8372 break;
8374 return 0;
8377 /***
8378 * DESCRIPTION:
8379 * ???
8381 * PARAMETER(S):
8382 * [I] infoPtr : valid pointer to the listview structure
8383 * [I] nVirtualKey : virtual key
8384 * [I] lKeyData : key data
8386 * RETURN:
8387 * Zero
8389 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8391 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8392 HWND hwndSelf = infoPtr->hwndSelf;
8393 INT nItem = -1;
8394 NMLVKEYDOWN nmKeyDown;
8396 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8398 /* send LVN_KEYDOWN notification */
8399 nmKeyDown.wVKey = nVirtualKey;
8400 nmKeyDown.flags = 0;
8401 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8402 if (!IsWindow(hwndSelf))
8403 return 0;
8405 switch (nVirtualKey)
8407 case VK_SPACE:
8408 nItem = infoPtr->nFocusedItem;
8409 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8410 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8411 break;
8413 case VK_RETURN:
8414 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8416 if (!notify(infoPtr, NM_RETURN)) return 0;
8417 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8419 break;
8421 case VK_HOME:
8422 if (infoPtr->nItemCount > 0)
8423 nItem = 0;
8424 break;
8426 case VK_END:
8427 if (infoPtr->nItemCount > 0)
8428 nItem = infoPtr->nItemCount - 1;
8429 break;
8431 case VK_LEFT:
8432 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8433 break;
8435 case VK_UP:
8436 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8437 break;
8439 case VK_RIGHT:
8440 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8441 break;
8443 case VK_DOWN:
8444 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8445 break;
8447 case VK_PRIOR:
8448 if (uView == LVS_REPORT)
8450 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8451 if (infoPtr->nFocusedItem == topidx)
8452 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8453 else
8454 nItem = topidx;
8456 else
8457 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8458 * LISTVIEW_GetCountPerRow(infoPtr);
8459 if(nItem < 0) nItem = 0;
8460 break;
8462 case VK_NEXT:
8463 if (uView == LVS_REPORT)
8465 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8466 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8467 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8468 nItem = infoPtr->nFocusedItem + cnt - 1;
8469 else
8470 nItem = topidx + cnt - 1;
8472 else
8473 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8474 * LISTVIEW_GetCountPerRow(infoPtr);
8475 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8476 break;
8479 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8480 LISTVIEW_KeySelection(infoPtr, nItem);
8482 return 0;
8485 /***
8486 * DESCRIPTION:
8487 * Kills the focus.
8489 * PARAMETER(S):
8490 * [I] infoPtr : valid pointer to the listview structure
8492 * RETURN:
8493 * Zero
8495 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8497 TRACE("()\n");
8499 /* if we did not have the focus, there's nothing to do */
8500 if (!infoPtr->bFocus) return 0;
8502 /* send NM_KILLFOCUS notification */
8503 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8505 /* if we have a focus rectagle, get rid of it */
8506 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8508 /* set window focus flag */
8509 infoPtr->bFocus = FALSE;
8511 /* invalidate the selected items before resetting focus flag */
8512 LISTVIEW_InvalidateSelectedItems(infoPtr);
8514 return 0;
8517 /***
8518 * DESCRIPTION:
8519 * Processes double click messages (left mouse button).
8521 * PARAMETER(S):
8522 * [I] infoPtr : valid pointer to the listview structure
8523 * [I] wKey : key flag
8524 * [I] x,y : mouse coordinate
8526 * RETURN:
8527 * Zero
8529 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8531 LVHITTESTINFO htInfo;
8533 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8535 /* Cancel the item edition if any */
8536 if (infoPtr->itemEdit.fEnabled)
8538 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8539 infoPtr->itemEdit.fEnabled = FALSE;
8542 /* send NM_RELEASEDCAPTURE notification */
8543 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8545 htInfo.pt.x = x;
8546 htInfo.pt.y = y;
8548 /* send NM_DBLCLK notification */
8549 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8550 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8552 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8553 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8555 return 0;
8558 /***
8559 * DESCRIPTION:
8560 * Processes mouse down messages (left mouse button).
8562 * PARAMETERS:
8563 * infoPtr [I ] valid pointer to the listview structure
8564 * wKey [I ] key flag
8565 * x,y [I ] mouse coordinate
8567 * RETURN:
8568 * Zero
8570 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8572 LVHITTESTINFO lvHitTestInfo;
8573 static BOOL bGroupSelect = TRUE;
8574 BOOL bReceivedFocus = FALSE;
8575 POINT pt = { x, y };
8576 INT nItem;
8578 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8580 /* send NM_RELEASEDCAPTURE notification */
8581 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8583 if (!infoPtr->bFocus)
8585 bReceivedFocus = TRUE;
8586 SetFocus(infoPtr->hwndSelf);
8589 /* set left button down flag and record the click position */
8590 infoPtr->bLButtonDown = TRUE;
8591 infoPtr->ptClickPos = pt;
8593 lvHitTestInfo.pt.x = x;
8594 lvHitTestInfo.pt.y = y;
8596 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8597 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8598 infoPtr->nEditLabelItem = -1;
8599 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8601 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8603 toggle_checkbox_state(infoPtr, nItem);
8604 return 0;
8607 if (infoPtr->dwStyle & LVS_SINGLESEL)
8609 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8610 infoPtr->nEditLabelItem = nItem;
8611 else
8612 LISTVIEW_SetSelection(infoPtr, nItem);
8614 else
8616 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8618 if (bGroupSelect)
8620 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8621 LISTVIEW_SetItemFocus(infoPtr, nItem);
8622 infoPtr->nSelectionMark = nItem;
8624 else
8626 LVITEMW item;
8628 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8629 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8631 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8632 infoPtr->nSelectionMark = nItem;
8635 else if (wKey & MK_CONTROL)
8637 LVITEMW item;
8639 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8641 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8642 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8643 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8644 infoPtr->nSelectionMark = nItem;
8646 else if (wKey & MK_SHIFT)
8648 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8650 else
8652 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8653 infoPtr->nEditLabelItem = nItem;
8655 /* set selection (clears other pre-existing selections) */
8656 LISTVIEW_SetSelection(infoPtr, nItem);
8660 else
8662 /* remove all selections */
8663 LISTVIEW_DeselectAll(infoPtr);
8664 ReleaseCapture();
8667 if (bReceivedFocus)
8668 infoPtr->nEditLabelItem = -1;
8670 return 0;
8673 /***
8674 * DESCRIPTION:
8675 * Processes mouse up messages (left mouse button).
8677 * PARAMETERS:
8678 * infoPtr [I ] valid pointer to the listview structure
8679 * wKey [I ] key flag
8680 * x,y [I ] mouse coordinate
8682 * RETURN:
8683 * Zero
8685 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8687 LVHITTESTINFO lvHitTestInfo;
8689 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8691 if (!infoPtr->bLButtonDown) return 0;
8693 lvHitTestInfo.pt.x = x;
8694 lvHitTestInfo.pt.y = y;
8696 /* send NM_CLICK notification */
8697 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8698 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8700 /* set left button flag */
8701 infoPtr->bLButtonDown = FALSE;
8703 /* if we clicked on a selected item, edit the label */
8704 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8706 /* we want to make sure the user doesn't want to do a double click. So we will
8707 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8709 infoPtr->itemEdit.fEnabled = TRUE;
8710 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8711 SetTimer(infoPtr->hwndSelf,
8712 (UINT_PTR)&infoPtr->itemEdit,
8713 GetDoubleClickTime(),
8714 LISTVIEW_DelayedEditItem);
8717 return 0;
8720 /***
8721 * DESCRIPTION:
8722 * Destroys the listview control (called after WM_DESTROY).
8724 * PARAMETER(S):
8725 * [I] infoPtr : valid pointer to the listview structure
8727 * RETURN:
8728 * Zero
8730 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8732 TRACE("()\n");
8734 /* delete all items */
8735 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8737 /* destroy data structure */
8738 DPA_Destroy(infoPtr->hdpaItems);
8739 DPA_Destroy(infoPtr->hdpaPosX);
8740 DPA_Destroy(infoPtr->hdpaPosY);
8741 DPA_Destroy(infoPtr->hdpaColumns);
8742 ranges_destroy(infoPtr->selectionRanges);
8744 /* destroy image lists */
8745 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8747 if (infoPtr->himlNormal)
8748 ImageList_Destroy(infoPtr->himlNormal);
8749 if (infoPtr->himlSmall)
8750 ImageList_Destroy(infoPtr->himlSmall);
8751 if (infoPtr->himlState)
8752 ImageList_Destroy(infoPtr->himlState);
8755 /* destroy font, bkgnd brush */
8756 infoPtr->hFont = 0;
8757 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8758 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8760 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8762 /* free listview info pointer*/
8763 Free(infoPtr);
8765 return 0;
8768 /***
8769 * DESCRIPTION:
8770 * Handles notifications from header.
8772 * PARAMETER(S):
8773 * [I] infoPtr : valid pointer to the listview structure
8774 * [I] nCtrlId : control identifier
8775 * [I] lpnmh : notification information
8777 * RETURN:
8778 * Zero
8780 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8782 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8783 HWND hwndSelf = infoPtr->hwndSelf;
8785 TRACE("(lpnmh=%p)\n", lpnmh);
8787 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8789 switch (lpnmh->hdr.code)
8791 case HDN_TRACKW:
8792 case HDN_TRACKA:
8794 COLUMN_INFO *lpColumnInfo;
8795 POINT ptOrigin;
8796 INT x;
8798 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8799 break;
8801 /* remove the old line (if any) */
8802 LISTVIEW_DrawTrackLine(infoPtr);
8804 /* compute & draw the new line */
8805 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8806 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8807 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8808 infoPtr->xTrackLine = x + ptOrigin.x;
8809 LISTVIEW_DrawTrackLine(infoPtr);
8810 break;
8813 case HDN_ENDTRACKA:
8814 case HDN_ENDTRACKW:
8815 /* remove the track line (if any) */
8816 LISTVIEW_DrawTrackLine(infoPtr);
8817 infoPtr->xTrackLine = -1;
8818 break;
8820 case HDN_ENDDRAG:
8821 FIXME("Changing column order not implemented\n");
8822 return TRUE;
8824 case HDN_ITEMCHANGINGW:
8825 case HDN_ITEMCHANGINGA:
8826 return notify_forward_header(infoPtr, lpnmh);
8828 case HDN_ITEMCHANGEDW:
8829 case HDN_ITEMCHANGEDA:
8831 COLUMN_INFO *lpColumnInfo;
8832 INT dx, cxy;
8834 notify_forward_header(infoPtr, lpnmh);
8835 if (!IsWindow(hwndSelf))
8836 break;
8838 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8840 HDITEMW hdi;
8842 hdi.mask = HDI_WIDTH;
8843 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8844 cxy = hdi.cxy;
8846 else
8847 cxy = lpnmh->pitem->cxy;
8849 /* determine how much we change since the last know position */
8850 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8851 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8852 if (dx != 0)
8854 lpColumnInfo->rcHeader.right += dx;
8855 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8856 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8857 else
8859 /* only needs to update the scrolls */
8860 infoPtr->nItemWidth += dx;
8861 LISTVIEW_UpdateScroll(infoPtr);
8863 LISTVIEW_UpdateItemSize(infoPtr);
8864 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8866 POINT ptOrigin;
8867 RECT rcCol = lpColumnInfo->rcHeader;
8869 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8870 OffsetRect(&rcCol, ptOrigin.x, 0);
8872 rcCol.top = infoPtr->rcList.top;
8873 rcCol.bottom = infoPtr->rcList.bottom;
8875 /* resizing left-aligned columns leaves most of the left side untouched */
8876 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8878 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8879 if (dx > 0)
8880 nMaxDirty += dx;
8881 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8884 /* when shrinking the last column clear the now unused field */
8885 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8886 rcCol.right -= dx;
8888 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8892 break;
8894 case HDN_ITEMCLICKW:
8895 case HDN_ITEMCLICKA:
8897 /* Handle sorting by Header Column */
8898 NMLISTVIEW nmlv;
8900 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8901 nmlv.iItem = -1;
8902 nmlv.iSubItem = lpnmh->iItem;
8903 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8905 break;
8907 case HDN_DIVIDERDBLCLICKW:
8908 case HDN_DIVIDERDBLCLICKA:
8909 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8910 break;
8913 return 0;
8916 /***
8917 * DESCRIPTION:
8918 * Paint non-client area of control.
8920 * PARAMETER(S):
8921 * [I] infoPtr : valid pointer to the listview structureof the sender
8922 * [I] region : update region
8924 * RETURN:
8925 * TRUE - frame was painted
8926 * FALSE - call default window proc
8928 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8930 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8931 HDC dc;
8932 RECT r;
8933 HRGN cliprgn;
8934 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8935 cyEdge = GetSystemMetrics (SM_CYEDGE);
8937 if (!theme) return FALSE;
8939 GetWindowRect(infoPtr->hwndSelf, &r);
8941 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8942 r.right - cxEdge, r.bottom - cyEdge);
8943 if (region != (HRGN)1)
8944 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8945 OffsetRect(&r, -r.left, -r.top);
8947 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8948 OffsetRect(&r, -r.left, -r.top);
8950 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8951 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8952 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8953 ReleaseDC(infoPtr->hwndSelf, dc);
8955 /* Call default proc to get the scrollbars etc. painted */
8956 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8958 return TRUE;
8961 /***
8962 * DESCRIPTION:
8963 * Determines the type of structure to use.
8965 * PARAMETER(S):
8966 * [I] infoPtr : valid pointer to the listview structureof the sender
8967 * [I] hwndFrom : listview window handle
8968 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8970 * RETURN:
8971 * Zero
8973 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8975 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8977 if (nCommand == NF_REQUERY)
8978 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8980 return infoPtr->notifyFormat;
8983 /***
8984 * DESCRIPTION:
8985 * Paints/Repaints the listview control.
8987 * PARAMETER(S):
8988 * [I] infoPtr : valid pointer to the listview structure
8989 * [I] hdc : device context handle
8991 * RETURN:
8992 * Zero
8994 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8996 TRACE("(hdc=%p)\n", hdc);
8998 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9000 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9002 infoPtr->bNoItemMetrics = FALSE;
9003 LISTVIEW_UpdateItemSize(infoPtr);
9004 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9005 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9006 LISTVIEW_UpdateScroll(infoPtr);
9009 UpdateWindow(infoPtr->hwndHeader);
9011 if (hdc)
9012 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9013 else
9015 PAINTSTRUCT ps;
9017 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9018 if (!hdc) return 1;
9019 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9020 EndPaint(infoPtr->hwndSelf, &ps);
9023 return 0;
9027 /***
9028 * DESCRIPTION:
9029 * Paints/Repaints the listview control.
9031 * PARAMETER(S):
9032 * [I] infoPtr : valid pointer to the listview structure
9033 * [I] hdc : device context handle
9034 * [I] options : drawing options
9036 * RETURN:
9037 * Zero
9039 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9041 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9043 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9044 return 0;
9046 if (options & PRF_ERASEBKGND)
9047 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9049 if (options & PRF_CLIENT)
9050 LISTVIEW_Paint(infoPtr, hdc);
9052 return 0;
9056 /***
9057 * DESCRIPTION:
9058 * Processes double click messages (right mouse button).
9060 * PARAMETER(S):
9061 * [I] infoPtr : valid pointer to the listview structure
9062 * [I] wKey : key flag
9063 * [I] x,y : mouse coordinate
9065 * RETURN:
9066 * Zero
9068 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9070 LVHITTESTINFO lvHitTestInfo;
9072 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9074 /* send NM_RELEASEDCAPTURE notification */
9075 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9077 /* send NM_RDBLCLK notification */
9078 lvHitTestInfo.pt.x = x;
9079 lvHitTestInfo.pt.y = y;
9080 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9081 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9083 return 0;
9086 /***
9087 * DESCRIPTION:
9088 * Processes mouse down messages (right mouse button).
9090 * PARAMETER(S):
9091 * [I] infoPtr : valid pointer to the listview structure
9092 * [I] wKey : key flag
9093 * [I] x,y : mouse coordinate
9095 * RETURN:
9096 * Zero
9098 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9100 LVHITTESTINFO lvHitTestInfo;
9101 INT nItem;
9103 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9105 /* send NM_RELEASEDCAPTURE notification */
9106 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9108 /* make sure the listview control window has the focus */
9109 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9111 /* set right button down flag */
9112 infoPtr->bRButtonDown = TRUE;
9114 /* determine the index of the selected item */
9115 lvHitTestInfo.pt.x = x;
9116 lvHitTestInfo.pt.y = y;
9117 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9119 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9121 LISTVIEW_SetItemFocus(infoPtr, nItem);
9122 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9123 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9124 LISTVIEW_SetSelection(infoPtr, nItem);
9126 else
9128 LISTVIEW_DeselectAll(infoPtr);
9131 return 0;
9134 /***
9135 * DESCRIPTION:
9136 * Processes mouse up messages (right mouse button).
9138 * PARAMETER(S):
9139 * [I] infoPtr : valid pointer to the listview structure
9140 * [I] wKey : key flag
9141 * [I] x,y : mouse coordinate
9143 * RETURN:
9144 * Zero
9146 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9148 LVHITTESTINFO lvHitTestInfo;
9149 POINT pt;
9151 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9153 if (!infoPtr->bRButtonDown) return 0;
9155 /* set button flag */
9156 infoPtr->bRButtonDown = FALSE;
9158 /* Send NM_RClICK notification */
9159 lvHitTestInfo.pt.x = x;
9160 lvHitTestInfo.pt.y = y;
9161 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9162 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9164 /* Change to screen coordinate for WM_CONTEXTMENU */
9165 pt = lvHitTestInfo.pt;
9166 ClientToScreen(infoPtr->hwndSelf, &pt);
9168 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9169 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9170 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9172 return 0;
9176 /***
9177 * DESCRIPTION:
9178 * Sets the cursor.
9180 * PARAMETER(S):
9181 * [I] infoPtr : valid pointer to the listview structure
9182 * [I] hwnd : window handle of window containing the cursor
9183 * [I] nHittest : hit-test code
9184 * [I] wMouseMsg : ideintifier of the mouse message
9186 * RETURN:
9187 * TRUE if cursor is set
9188 * FALSE otherwise
9190 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9192 LVHITTESTINFO lvHitTestInfo;
9194 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9196 if(!infoPtr->hHotCursor) return FALSE;
9198 GetCursorPos(&lvHitTestInfo.pt);
9199 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9201 SetCursor(infoPtr->hHotCursor);
9203 return TRUE;
9206 /***
9207 * DESCRIPTION:
9208 * Sets the focus.
9210 * PARAMETER(S):
9211 * [I] infoPtr : valid pointer to the listview structure
9212 * [I] hwndLoseFocus : handle of previously focused window
9214 * RETURN:
9215 * Zero
9217 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9219 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9221 /* if we have the focus already, there's nothing to do */
9222 if (infoPtr->bFocus) return 0;
9224 /* send NM_SETFOCUS notification */
9225 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9227 /* set window focus flag */
9228 infoPtr->bFocus = TRUE;
9230 /* put the focus rect back on */
9231 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9233 /* redraw all visible selected items */
9234 LISTVIEW_InvalidateSelectedItems(infoPtr);
9236 return 0;
9239 /***
9240 * DESCRIPTION:
9241 * Sets the font.
9243 * PARAMETER(S):
9244 * [I] infoPtr : valid pointer to the listview structure
9245 * [I] fRedraw : font handle
9246 * [I] fRedraw : redraw flag
9248 * RETURN:
9249 * Zero
9251 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9253 HFONT oldFont = infoPtr->hFont;
9255 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9257 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9258 if (infoPtr->hFont == oldFont) return 0;
9260 LISTVIEW_SaveTextMetrics(infoPtr);
9262 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9263 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9265 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9267 return 0;
9270 /***
9271 * DESCRIPTION:
9272 * Message handling for WM_SETREDRAW.
9273 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9275 * PARAMETER(S):
9276 * [I] infoPtr : valid pointer to the listview structure
9277 * [I] bRedraw: state of redraw flag
9279 * RETURN:
9280 * DefWinProc return value
9282 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9284 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9286 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9287 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9289 infoPtr->bRedraw = bRedraw;
9291 if(!bRedraw) return 0;
9293 if (is_autoarrange(infoPtr))
9294 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9295 LISTVIEW_UpdateScroll(infoPtr);
9297 /* despite what the WM_SETREDRAW docs says, apps expect us
9298 * to invalidate the listview here... stupid! */
9299 LISTVIEW_InvalidateList(infoPtr);
9301 return 0;
9304 /***
9305 * DESCRIPTION:
9306 * Resizes the listview control. This function processes WM_SIZE
9307 * messages. At this time, the width and height are not used.
9309 * PARAMETER(S):
9310 * [I] infoPtr : valid pointer to the listview structure
9311 * [I] Width : new width
9312 * [I] Height : new height
9314 * RETURN:
9315 * Zero
9317 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9319 RECT rcOld = infoPtr->rcList;
9321 TRACE("(width=%d, height=%d)\n", Width, Height);
9323 LISTVIEW_UpdateSize(infoPtr);
9324 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9326 /* do not bother with display related stuff if we're not redrawing */
9327 if (!is_redrawing(infoPtr)) return 0;
9329 if (is_autoarrange(infoPtr))
9330 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9332 LISTVIEW_UpdateScroll(infoPtr);
9334 /* refresh all only for lists whose height changed significantly */
9335 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9336 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9337 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9338 LISTVIEW_InvalidateList(infoPtr);
9340 return 0;
9343 /***
9344 * DESCRIPTION:
9345 * Sets the size information.
9347 * PARAMETER(S):
9348 * [I] infoPtr : valid pointer to the listview structure
9350 * RETURN:
9351 * None
9353 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9355 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9357 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9359 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9361 if (uView == LVS_LIST)
9363 /* Apparently the "LIST" style is supposed to have the same
9364 * number of items in a column even if there is no scroll bar.
9365 * Since if a scroll bar already exists then the bottom is already
9366 * reduced, only reduce if the scroll bar does not currently exist.
9367 * The "2" is there to mimic the native control. I think it may be
9368 * related to either padding or edges. (GLA 7/2002)
9370 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9371 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9372 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9374 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
9376 HDLAYOUT hl;
9377 WINDOWPOS wp;
9379 hl.prc = &infoPtr->rcList;
9380 hl.pwpos = &wp;
9381 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9382 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9383 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9384 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9386 infoPtr->rcList.top = max(wp.cy, 0);
9387 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9390 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9393 /***
9394 * DESCRIPTION:
9395 * Processes WM_STYLECHANGED messages.
9397 * PARAMETER(S):
9398 * [I] infoPtr : valid pointer to the listview structure
9399 * [I] wStyleType : window style type (normal or extended)
9400 * [I] lpss : window style information
9402 * RETURN:
9403 * Zero
9405 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9406 const STYLESTRUCT *lpss)
9408 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9409 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9411 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9412 wStyleType, lpss->styleOld, lpss->styleNew);
9414 if (wStyleType != GWL_STYLE) return 0;
9416 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9417 /* what if LVS_OWNERDATA changed? */
9418 /* or LVS_SINGLESEL */
9419 /* or LVS_SORT{AS,DES}CENDING */
9421 infoPtr->dwStyle = lpss->styleNew;
9423 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9424 ((lpss->styleNew & WS_HSCROLL) == 0))
9425 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9427 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9428 ((lpss->styleNew & WS_VSCROLL) == 0))
9429 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9431 if (uNewView != uOldView)
9433 SIZE oldIconSize = infoPtr->iconSize;
9434 HIMAGELIST himl;
9436 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9437 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9439 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9440 SetRectEmpty(&infoPtr->rcFocus);
9442 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9443 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9445 if (uNewView == LVS_ICON)
9447 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9449 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9450 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9451 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9454 else if (uNewView == LVS_REPORT)
9456 HDLAYOUT hl;
9457 WINDOWPOS wp;
9459 hl.prc = &infoPtr->rcList;
9460 hl.pwpos = &wp;
9461 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9462 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9465 LISTVIEW_UpdateItemSize(infoPtr);
9468 if (uNewView == LVS_REPORT)
9469 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
9471 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9472 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9473 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9475 /* update the size of the client area */
9476 LISTVIEW_UpdateSize(infoPtr);
9478 /* add scrollbars if needed */
9479 LISTVIEW_UpdateScroll(infoPtr);
9481 /* invalidate client area + erase background */
9482 LISTVIEW_InvalidateList(infoPtr);
9484 return 0;
9487 /***
9488 * DESCRIPTION:
9489 * Window procedure of the listview control.
9492 static LRESULT WINAPI
9493 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9495 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9497 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9499 if (!infoPtr && (uMsg != WM_NCCREATE))
9500 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9502 switch (uMsg)
9504 case LVM_APPROXIMATEVIEWRECT:
9505 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9506 LOWORD(lParam), HIWORD(lParam));
9507 case LVM_ARRANGE:
9508 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9510 /* case LVM_CANCELEDITLABEL: */
9512 case LVM_CREATEDRAGIMAGE:
9513 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9515 case LVM_DELETEALLITEMS:
9516 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9518 case LVM_DELETECOLUMN:
9519 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9521 case LVM_DELETEITEM:
9522 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9524 case LVM_EDITLABELW:
9525 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9527 case LVM_EDITLABELA:
9528 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9530 /* case LVM_ENABLEGROUPVIEW: */
9532 case LVM_ENSUREVISIBLE:
9533 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9535 case LVM_FINDITEMW:
9536 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9538 case LVM_FINDITEMA:
9539 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9541 case LVM_GETBKCOLOR:
9542 return infoPtr->clrBk;
9544 /* case LVM_GETBKIMAGE: */
9546 case LVM_GETCALLBACKMASK:
9547 return infoPtr->uCallbackMask;
9549 case LVM_GETCOLUMNA:
9550 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9552 case LVM_GETCOLUMNW:
9553 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9555 case LVM_GETCOLUMNORDERARRAY:
9556 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9558 case LVM_GETCOLUMNWIDTH:
9559 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9561 case LVM_GETCOUNTPERPAGE:
9562 return LISTVIEW_GetCountPerPage(infoPtr);
9564 case LVM_GETEDITCONTROL:
9565 return (LRESULT)infoPtr->hwndEdit;
9567 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9568 return infoPtr->dwLvExStyle;
9570 /* case LVM_GETGROUPINFO: */
9572 /* case LVM_GETGROUPMETRICS: */
9574 case LVM_GETHEADER:
9575 return (LRESULT)infoPtr->hwndHeader;
9577 case LVM_GETHOTCURSOR:
9578 return (LRESULT)infoPtr->hHotCursor;
9580 case LVM_GETHOTITEM:
9581 return infoPtr->nHotItem;
9583 case LVM_GETHOVERTIME:
9584 return infoPtr->dwHoverTime;
9586 case LVM_GETIMAGELIST:
9587 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9589 /* case LVM_GETINSERTMARK: */
9591 /* case LVM_GETINSERTMARKCOLOR: */
9593 /* case LVM_GETINSERTMARKRECT: */
9595 case LVM_GETISEARCHSTRINGA:
9596 case LVM_GETISEARCHSTRINGW:
9597 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9598 return FALSE;
9600 case LVM_GETITEMA:
9601 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9603 case LVM_GETITEMW:
9604 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9606 case LVM_GETITEMCOUNT:
9607 return infoPtr->nItemCount;
9609 case LVM_GETITEMPOSITION:
9610 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9612 case LVM_GETITEMRECT:
9613 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9615 case LVM_GETITEMSPACING:
9616 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9618 case LVM_GETITEMSTATE:
9619 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9621 case LVM_GETITEMTEXTA:
9622 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9624 case LVM_GETITEMTEXTW:
9625 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9627 case LVM_GETNEXTITEM:
9628 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9630 case LVM_GETNUMBEROFWORKAREAS:
9631 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9632 return 1;
9634 case LVM_GETORIGIN:
9635 if (!lParam) return FALSE;
9636 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9637 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9638 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9639 return TRUE;
9641 /* case LVM_GETOUTLINECOLOR: */
9643 /* case LVM_GETSELECTEDCOLUMN: */
9645 case LVM_GETSELECTEDCOUNT:
9646 return LISTVIEW_GetSelectedCount(infoPtr);
9648 case LVM_GETSELECTIONMARK:
9649 return infoPtr->nSelectionMark;
9651 case LVM_GETSTRINGWIDTHA:
9652 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9654 case LVM_GETSTRINGWIDTHW:
9655 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9657 case LVM_GETSUBITEMRECT:
9658 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9660 case LVM_GETTEXTBKCOLOR:
9661 return infoPtr->clrTextBk;
9663 case LVM_GETTEXTCOLOR:
9664 return infoPtr->clrText;
9666 /* case LVM_GETTILEINFO: */
9668 /* case LVM_GETTILEVIEWINFO: */
9670 case LVM_GETTOOLTIPS:
9671 if( !infoPtr->hwndToolTip )
9672 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9673 return (LRESULT)infoPtr->hwndToolTip;
9675 case LVM_GETTOPINDEX:
9676 return LISTVIEW_GetTopIndex(infoPtr);
9678 /*case LVM_GETUNICODEFORMAT:
9679 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9680 return FALSE;*/
9682 /* case LVM_GETVIEW: */
9684 case LVM_GETVIEWRECT:
9685 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9687 case LVM_GETWORKAREAS:
9688 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9689 return FALSE;
9691 /* case LVM_HASGROUP: */
9693 case LVM_HITTEST:
9694 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9696 case LVM_INSERTCOLUMNA:
9697 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9699 case LVM_INSERTCOLUMNW:
9700 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9702 /* case LVM_INSERTGROUP: */
9704 /* case LVM_INSERTGROUPSORTED: */
9706 case LVM_INSERTITEMA:
9707 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9709 case LVM_INSERTITEMW:
9710 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9712 /* case LVM_INSERTMARKHITTEST: */
9714 /* case LVM_ISGROUPVIEWENABLED: */
9716 /* case LVM_MAPIDTOINDEX: */
9718 /* case LVM_MAPINDEXTOID: */
9720 /* case LVM_MOVEGROUP: */
9722 /* case LVM_MOVEITEMTOGROUP: */
9724 case LVM_REDRAWITEMS:
9725 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9727 /* case LVM_REMOVEALLGROUPS: */
9729 /* case LVM_REMOVEGROUP: */
9731 case LVM_SCROLL:
9732 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9734 case LVM_SETBKCOLOR:
9735 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9737 /* case LVM_SETBKIMAGE: */
9739 case LVM_SETCALLBACKMASK:
9740 infoPtr->uCallbackMask = (UINT)wParam;
9741 return TRUE;
9743 case LVM_SETCOLUMNA:
9744 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9746 case LVM_SETCOLUMNW:
9747 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9749 case LVM_SETCOLUMNORDERARRAY:
9750 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9752 case LVM_SETCOLUMNWIDTH:
9753 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9755 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9756 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9758 /* case LVM_SETGROUPINFO: */
9760 /* case LVM_SETGROUPMETRICS: */
9762 case LVM_SETHOTCURSOR:
9763 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9765 case LVM_SETHOTITEM:
9766 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9768 case LVM_SETHOVERTIME:
9769 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9771 case LVM_SETICONSPACING:
9772 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9774 case LVM_SETIMAGELIST:
9775 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9777 /* case LVM_SETINFOTIP: */
9779 /* case LVM_SETINSERTMARK: */
9781 /* case LVM_SETINSERTMARKCOLOR: */
9783 case LVM_SETITEMA:
9784 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9786 case LVM_SETITEMW:
9787 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9789 case LVM_SETITEMCOUNT:
9790 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9792 case LVM_SETITEMPOSITION:
9794 POINT pt;
9795 pt.x = (short)LOWORD(lParam);
9796 pt.y = (short)HIWORD(lParam);
9797 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9800 case LVM_SETITEMPOSITION32:
9801 if (lParam == 0) return FALSE;
9802 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9804 case LVM_SETITEMSTATE:
9805 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9807 case LVM_SETITEMTEXTA:
9808 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9810 case LVM_SETITEMTEXTW:
9811 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9813 /* case LVM_SETOUTLINECOLOR: */
9815 /* case LVM_SETSELECTEDCOLUMN: */
9817 case LVM_SETSELECTIONMARK:
9818 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9820 case LVM_SETTEXTBKCOLOR:
9821 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9823 case LVM_SETTEXTCOLOR:
9824 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9826 /* case LVM_SETTILEINFO: */
9828 /* case LVM_SETTILEVIEWINFO: */
9830 /* case LVM_SETTILEWIDTH: */
9832 case LVM_SETTOOLTIPS:
9833 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9835 case LVM_SETUNICODEFORMAT:
9836 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9838 /* case LVM_SETVIEW: */
9840 /* case LVM_SETWORKAREAS: */
9842 /* case LVM_SORTGROUPS: */
9844 case LVM_SORTITEMS:
9845 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9847 /* LVM_SORTITEMSEX: */
9849 case LVM_SUBITEMHITTEST:
9850 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9852 case LVM_UPDATE:
9853 return LISTVIEW_Update(infoPtr, (INT)wParam);
9855 case WM_CHAR:
9856 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9858 case WM_COMMAND:
9859 return LISTVIEW_Command(infoPtr, wParam, lParam);
9861 case WM_NCCREATE:
9862 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9864 case WM_CREATE:
9865 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9867 case WM_DESTROY:
9868 return LISTVIEW_Destroy(infoPtr);
9870 case WM_ENABLE:
9871 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9873 case WM_ERASEBKGND:
9874 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9876 case WM_GETDLGCODE:
9877 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9879 case WM_GETFONT:
9880 return (LRESULT)infoPtr->hFont;
9882 case WM_HSCROLL:
9883 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9885 case WM_KEYDOWN:
9886 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9888 case WM_KILLFOCUS:
9889 return LISTVIEW_KillFocus(infoPtr);
9891 case WM_LBUTTONDBLCLK:
9892 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9894 case WM_LBUTTONDOWN:
9895 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9897 case WM_LBUTTONUP:
9898 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9900 case WM_MOUSEMOVE:
9901 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9903 case WM_MOUSEHOVER:
9904 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9906 case WM_NCDESTROY:
9907 return LISTVIEW_NCDestroy(infoPtr);
9909 case WM_NCPAINT:
9910 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9911 return 0;
9912 goto fwd_msg;
9914 case WM_NOTIFY:
9915 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9916 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9917 else return 0;
9919 case WM_NOTIFYFORMAT:
9920 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9922 case WM_PRINTCLIENT:
9923 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9925 case WM_PAINT:
9926 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9928 case WM_RBUTTONDBLCLK:
9929 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9931 case WM_RBUTTONDOWN:
9932 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9934 case WM_RBUTTONUP:
9935 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9937 case WM_SETCURSOR:
9938 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9939 return TRUE;
9940 goto fwd_msg;
9942 case WM_SETFOCUS:
9943 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9945 case WM_SETFONT:
9946 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9948 case WM_SETREDRAW:
9949 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9951 case WM_SIZE:
9952 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9954 case WM_STYLECHANGED:
9955 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9957 case WM_SYSCOLORCHANGE:
9958 COMCTL32_RefreshSysColors();
9959 return 0;
9961 /* case WM_TIMER: */
9962 case WM_THEMECHANGED:
9963 return LISTVIEW_ThemeChanged(infoPtr);
9965 case WM_VSCROLL:
9966 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9968 case WM_MOUSEWHEEL:
9969 if (wParam & (MK_SHIFT | MK_CONTROL))
9970 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9971 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9973 case WM_WINDOWPOSCHANGED:
9974 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9976 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9977 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9978 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9980 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9982 MEASUREITEMSTRUCT mis;
9983 mis.CtlType = ODT_LISTVIEW;
9984 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9985 mis.itemID = -1;
9986 mis.itemWidth = 0;
9987 mis.itemData = 0;
9988 mis.itemHeight= infoPtr->nItemHeight;
9989 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9990 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9991 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9994 LISTVIEW_UpdateSize(infoPtr);
9995 LISTVIEW_UpdateScroll(infoPtr);
9997 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9999 /* case WM_WININICHANGE: */
10001 default:
10002 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
10003 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10005 fwd_msg:
10006 /* call default window procedure */
10007 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10012 /***
10013 * DESCRIPTION:
10014 * Registers the window class.
10016 * PARAMETER(S):
10017 * None
10019 * RETURN:
10020 * None
10022 void LISTVIEW_Register(void)
10024 WNDCLASSW wndClass;
10026 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10027 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10028 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10029 wndClass.cbClsExtra = 0;
10030 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10031 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10032 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10033 wndClass.lpszClassName = WC_LISTVIEWW;
10034 RegisterClassW(&wndClass);
10037 /***
10038 * DESCRIPTION:
10039 * Unregisters the window class.
10041 * PARAMETER(S):
10042 * None
10044 * RETURN:
10045 * None
10047 void LISTVIEW_Unregister(void)
10049 UnregisterClassW(WC_LISTVIEWW, NULL);
10052 /***
10053 * DESCRIPTION:
10054 * Handle any WM_COMMAND messages
10056 * PARAMETER(S):
10057 * [I] infoPtr : valid pointer to the listview structure
10058 * [I] wParam : the first message parameter
10059 * [I] lParam : the second message parameter
10061 * RETURN:
10062 * Zero.
10064 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10066 switch (HIWORD(wParam))
10068 case EN_UPDATE:
10071 * Adjust the edit window size
10073 WCHAR buffer[1024];
10074 HDC hdc = GetDC(infoPtr->hwndEdit);
10075 HFONT hFont, hOldFont = 0;
10076 RECT rect;
10077 SIZE sz;
10079 if (!infoPtr->hwndEdit || !hdc) return 0;
10080 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10081 GetWindowRect(infoPtr->hwndEdit, &rect);
10083 /* Select font to get the right dimension of the string */
10084 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10085 if(hFont != 0)
10087 hOldFont = SelectObject(hdc, hFont);
10090 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10092 TEXTMETRICW textMetric;
10094 /* Add Extra spacing for the next character */
10095 GetTextMetricsW(hdc, &textMetric);
10096 sz.cx += (textMetric.tmMaxCharWidth * 2);
10098 SetWindowPos (
10099 infoPtr->hwndEdit,
10100 HWND_TOP,
10103 sz.cx,
10104 rect.bottom - rect.top,
10105 SWP_DRAWFRAME|SWP_NOMOVE);
10107 if(hFont != 0)
10108 SelectObject(hdc, hOldFont);
10110 ReleaseDC(infoPtr->hwndEdit, hdc);
10112 break;
10115 default:
10116 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10119 return 0;
10123 /***
10124 * DESCRIPTION:
10125 * Subclassed edit control windproc function
10127 * PARAMETER(S):
10128 * [I] hwnd : the edit window handle
10129 * [I] uMsg : the message that is to be processed
10130 * [I] wParam : first message parameter
10131 * [I] lParam : second message parameter
10132 * [I] isW : TRUE if input is Unicode
10134 * RETURN:
10135 * Zero.
10137 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10139 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10140 BOOL cancel = FALSE;
10142 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10143 hwnd, uMsg, wParam, lParam, isW);
10145 switch (uMsg)
10147 case WM_GETDLGCODE:
10148 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10150 case WM_KILLFOCUS:
10151 break;
10153 case WM_DESTROY:
10155 WNDPROC editProc = infoPtr->EditWndProc;
10156 infoPtr->EditWndProc = 0;
10157 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10158 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10161 case WM_KEYDOWN:
10162 if (VK_ESCAPE == (INT)wParam)
10164 cancel = TRUE;
10165 break;
10167 else if (VK_RETURN == (INT)wParam)
10168 break;
10170 default:
10171 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10174 /* kill the edit */
10175 if (infoPtr->hwndEdit)
10177 LPWSTR buffer = NULL;
10179 infoPtr->hwndEdit = 0;
10180 if (!cancel)
10182 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10184 if (len)
10186 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10188 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10189 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10193 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10195 Free(buffer);
10198 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10199 return 0;
10202 /***
10203 * DESCRIPTION:
10204 * Subclassed edit control Unicode windproc function
10206 * PARAMETER(S):
10207 * [I] hwnd : the edit window handle
10208 * [I] uMsg : the message that is to be processed
10209 * [I] wParam : first message parameter
10210 * [I] lParam : second message parameter
10212 * RETURN:
10214 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10216 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10219 /***
10220 * DESCRIPTION:
10221 * Subclassed edit control ANSI windproc function
10223 * PARAMETER(S):
10224 * [I] hwnd : the edit window handle
10225 * [I] uMsg : the message that is to be processed
10226 * [I] wParam : first message parameter
10227 * [I] lParam : second message parameter
10229 * RETURN:
10231 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10233 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10236 /***
10237 * DESCRIPTION:
10238 * Creates a subclassed edit control
10240 * PARAMETER(S):
10241 * [I] infoPtr : valid pointer to the listview structure
10242 * [I] text : initial text for the edit
10243 * [I] style : the window style
10244 * [I] isW : TRUE if input is Unicode
10246 * RETURN:
10248 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10249 INT x, INT y, INT width, INT height, BOOL isW)
10251 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10252 HWND hedit;
10253 SIZE sz;
10254 HDC hdc;
10255 HDC hOldFont=0;
10256 TEXTMETRICW textMetric;
10257 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10259 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10261 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10262 hdc = GetDC(infoPtr->hwndSelf);
10264 /* Select the font to get appropriate metric dimensions */
10265 if(infoPtr->hFont != 0)
10266 hOldFont = SelectObject(hdc, infoPtr->hFont);
10268 /*Get String Length in pixels */
10269 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10271 /*Add Extra spacing for the next character */
10272 GetTextMetricsW(hdc, &textMetric);
10273 sz.cx += (textMetric.tmMaxCharWidth * 2);
10275 if(infoPtr->hFont != 0)
10276 SelectObject(hdc, hOldFont);
10278 ReleaseDC(infoPtr->hwndSelf, hdc);
10279 if (isW)
10280 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10281 else
10282 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10284 if (!hedit) return 0;
10286 infoPtr->EditWndProc = (WNDPROC)
10287 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10288 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10290 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10292 return hedit;