comctl32: Set listview focus on WM_LBUTTONUP instead of WM_LBUTTONDOWN.
[wine/wine64.git] / dlls / comctl32 / listview.c
blobc620892aa1ac4b995a14513f46be477102d472df
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 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1642 SWP_NOZORDER | SWP_NOACTIVATE);
1645 /***
1646 * DESCRIPTION:
1647 * Update the scrollbars. This functions should be called whenever
1648 * the content, size or view changes.
1650 * PARAMETER(S):
1651 * [I] infoPtr : valid pointer to the listview structure
1653 * RETURN:
1654 * None
1656 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1658 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1659 SCROLLINFO horzInfo, vertInfo;
1660 INT dx, dy;
1662 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1664 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1665 horzInfo.cbSize = sizeof(SCROLLINFO);
1666 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1668 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1669 if (uView == LVS_LIST)
1671 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1672 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1674 /* scroll by at least one column per page */
1675 if(horzInfo.nPage < infoPtr->nItemWidth)
1676 horzInfo.nPage = infoPtr->nItemWidth;
1678 horzInfo.nPage /= infoPtr->nItemWidth;
1680 else if (uView == LVS_REPORT)
1682 horzInfo.nMax = infoPtr->nItemWidth;
1684 else /* LVS_ICON, or LVS_SMALLICON */
1686 RECT rcView;
1688 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1691 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1692 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1693 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1694 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1695 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1697 /* Setting the horizontal scroll can change the listview size
1698 * (and potentially everything else) so we need to recompute
1699 * everything again for the vertical scroll
1702 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1703 vertInfo.cbSize = sizeof(SCROLLINFO);
1704 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1706 if (uView == LVS_REPORT)
1708 vertInfo.nMax = infoPtr->nItemCount;
1710 /* scroll by at least one page */
1711 if(vertInfo.nPage < infoPtr->nItemHeight)
1712 vertInfo.nPage = infoPtr->nItemHeight;
1714 vertInfo.nPage /= infoPtr->nItemHeight;
1716 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1718 RECT rcView;
1720 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1723 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1724 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1725 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1726 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1727 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1729 /* Change of the range may have changed the scroll pos. If so move the content */
1730 if (dx != 0 || dy != 0)
1732 RECT listRect;
1733 listRect = infoPtr->rcList;
1734 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1735 SW_ERASE | SW_INVALIDATE);
1738 /* Update the Header Control */
1739 if (uView == LVS_REPORT)
1741 horzInfo.fMask = SIF_POS;
1742 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1743 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1748 /***
1749 * DESCRIPTION:
1750 * Shows/hides the focus rectangle.
1752 * PARAMETER(S):
1753 * [I] infoPtr : valid pointer to the listview structure
1754 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1756 * RETURN:
1757 * None
1759 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1761 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1762 HDC hdc;
1764 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1766 if (infoPtr->nFocusedItem < 0) return;
1768 /* we need some gymnastics in ICON mode to handle large items */
1769 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1771 RECT rcBox;
1773 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1774 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1776 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1777 return;
1781 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1783 /* for some reason, owner draw should work only in report mode */
1784 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1786 DRAWITEMSTRUCT dis;
1787 LVITEMW item;
1789 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1790 HFONT hOldFont = SelectObject(hdc, hFont);
1792 item.iItem = infoPtr->nFocusedItem;
1793 item.iSubItem = 0;
1794 item.mask = LVIF_PARAM;
1795 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1797 ZeroMemory(&dis, sizeof(dis));
1798 dis.CtlType = ODT_LISTVIEW;
1799 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1800 dis.itemID = item.iItem;
1801 dis.itemAction = ODA_FOCUS;
1802 if (fShow) dis.itemState |= ODS_FOCUS;
1803 dis.hwndItem = infoPtr->hwndSelf;
1804 dis.hDC = hdc;
1805 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1806 dis.itemData = item.lParam;
1808 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1810 SelectObject(hdc, hOldFont);
1812 else
1814 DrawFocusRect(hdc, &infoPtr->rcFocus);
1816 done:
1817 ReleaseDC(infoPtr->hwndSelf, hdc);
1820 /***
1821 * Invalidates all visible selected items.
1823 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1825 ITERATOR i;
1827 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1828 while(iterator_next(&i))
1830 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1831 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1833 iterator_destroy(&i);
1837 /***
1838 * DESCRIPTION: [INTERNAL]
1839 * Computes an item's (left,top) corner, relative to rcView.
1840 * That is, the position has NOT been made relative to the Origin.
1841 * This is deliberate, to avoid computing the Origin over, and
1842 * over again, when this function is called in a loop. Instead,
1843 * one can factor the computation of the Origin before the loop,
1844 * and offset the value returned by this function, on every iteration.
1846 * PARAMETER(S):
1847 * [I] infoPtr : valid pointer to the listview structure
1848 * [I] nItem : item number
1849 * [O] lpptOrig : item top, left corner
1851 * RETURN:
1852 * None.
1854 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1856 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1858 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1860 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1862 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1863 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1865 else if (uView == LVS_LIST)
1867 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1868 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1869 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1871 else /* LVS_REPORT */
1873 lpptPosition->x = 0;
1874 lpptPosition->y = nItem * infoPtr->nItemHeight;
1878 /***
1879 * DESCRIPTION: [INTERNAL]
1880 * Compute the rectangles of an item. This is to localize all
1881 * the computations in one place. If you are not interested in some
1882 * of these values, simply pass in a NULL -- the function is smart
1883 * enough to compute only what's necessary. The function computes
1884 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1885 * one, the BOX rectangle. This rectangle is very cheap to compute,
1886 * and is guaranteed to contain all the other rectangles. Computing
1887 * the ICON rect is also cheap, but all the others are potentially
1888 * expensive. This gives an easy and effective optimization when
1889 * searching (like point inclusion, or rectangle intersection):
1890 * first test against the BOX, and if TRUE, test against the desired
1891 * rectangle.
1892 * If the function does not have all the necessary information
1893 * to computed the requested rectangles, will crash with a
1894 * failed assertion. This is done so we catch all programming
1895 * errors, given that the function is called only from our code.
1897 * We have the following 'special' meanings for a few fields:
1898 * * If LVIS_FOCUSED is set, we assume the item has the focus
1899 * This is important in ICON mode, where it might get a larger
1900 * then usual rectangle
1902 * Please note that subitem support works only in REPORT mode.
1904 * PARAMETER(S):
1905 * [I] infoPtr : valid pointer to the listview structure
1906 * [I] lpLVItem : item to compute the measures for
1907 * [O] lprcBox : ptr to Box rectangle
1908 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1909 * [0] lprcSelectBox : ptr to select box rectangle
1910 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1911 * [O] lprcIcon : ptr to Icon rectangle
1912 * Same as LVM_GETITEMRECT with LVIR_ICON
1913 * [O] lprcStateIcon: ptr to State Icon rectangle
1914 * [O] lprcLabel : ptr to Label rectangle
1915 * Same as LVM_GETITEMRECT with LVIR_LABEL
1917 * RETURN:
1918 * None.
1920 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1921 LPRECT lprcBox, LPRECT lprcSelectBox,
1922 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1924 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1925 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1926 RECT Box, SelectBox, Icon, Label;
1927 COLUMN_INFO *lpColumnInfo = NULL;
1928 SIZE labelSize = { 0, 0 };
1930 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1932 /* Be smart and try to figure out the minimum we have to do */
1933 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1934 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1936 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1937 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1939 if (lprcSelectBox) doSelectBox = TRUE;
1940 if (lprcLabel) doLabel = TRUE;
1941 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1942 if (doSelectBox)
1944 doIcon = TRUE;
1945 doLabel = TRUE;
1948 /************************************************************/
1949 /* compute the box rectangle (it should be cheap to do) */
1950 /************************************************************/
1951 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1952 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1954 if (lpLVItem->iSubItem)
1956 Box = lpColumnInfo->rcHeader;
1958 else
1960 Box.left = 0;
1961 Box.right = infoPtr->nItemWidth;
1963 Box.top = 0;
1964 Box.bottom = infoPtr->nItemHeight;
1966 /******************************************************************/
1967 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1968 /******************************************************************/
1969 if (doIcon)
1971 LONG state_width = 0;
1973 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1974 state_width = infoPtr->iconStateSize.cx;
1976 if (uView == LVS_ICON)
1978 Icon.left = Box.left + state_width;
1979 if (infoPtr->himlNormal)
1980 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1981 Icon.top = Box.top + ICON_TOP_PADDING;
1982 Icon.right = Icon.left;
1983 Icon.bottom = Icon.top;
1984 if (infoPtr->himlNormal)
1986 Icon.right += infoPtr->iconSize.cx;
1987 Icon.bottom += infoPtr->iconSize.cy;
1990 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1992 Icon.left = Box.left + state_width;
1994 if (uView == LVS_REPORT)
1995 Icon.left += REPORT_MARGINX;
1997 Icon.top = Box.top;
1998 Icon.right = Icon.left;
1999 if (infoPtr->himlSmall &&
2000 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2001 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2002 Icon.right += infoPtr->iconSize.cx;
2003 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2005 if(lprcIcon) *lprcIcon = Icon;
2006 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2008 /* TODO: is this correct? */
2009 if (lprcStateIcon)
2011 lprcStateIcon->left = Icon.left - state_width;
2012 lprcStateIcon->right = Icon.left;
2013 lprcStateIcon->top = Icon.top;
2014 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2015 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2018 else Icon.right = 0;
2020 /************************************************************/
2021 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2022 /************************************************************/
2023 if (doLabel)
2025 /* calculate how far to the right can the label stretch */
2026 Label.right = Box.right;
2027 if (uView == LVS_REPORT)
2029 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2032 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2034 labelSize.cx = infoPtr->nItemWidth;
2035 labelSize.cy = infoPtr->nItemHeight;
2036 goto calc_label;
2039 /* we need the text in non owner draw mode */
2040 assert(lpLVItem->mask & LVIF_TEXT);
2041 if (is_textT(lpLVItem->pszText, TRUE))
2043 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2044 HDC hdc = GetDC(infoPtr->hwndSelf);
2045 HFONT hOldFont = SelectObject(hdc, hFont);
2046 UINT uFormat;
2047 RECT rcText;
2049 /* compute rough rectangle where the label will go */
2050 SetRectEmpty(&rcText);
2051 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2052 rcText.bottom = infoPtr->nItemHeight;
2053 if (uView == LVS_ICON)
2054 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2056 /* now figure out the flags */
2057 if (uView == LVS_ICON)
2058 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2059 else
2060 uFormat = LV_SL_DT_FLAGS;
2062 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2064 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2065 labelSize.cy = rcText.bottom - rcText.top;
2067 SelectObject(hdc, hOldFont);
2068 ReleaseDC(infoPtr->hwndSelf, hdc);
2071 calc_label:
2072 if (uView == LVS_ICON)
2074 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2075 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2076 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2077 Label.right = Label.left + labelSize.cx;
2078 Label.bottom = Label.top + infoPtr->nItemHeight;
2079 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2081 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2082 labelSize.cy /= infoPtr->ntmHeight;
2083 labelSize.cy = max(labelSize.cy, 1);
2084 labelSize.cy *= infoPtr->ntmHeight;
2086 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2088 else if (uView == LVS_REPORT)
2090 Label.left = Icon.right;
2091 Label.top = Box.top;
2092 Label.right = lpColumnInfo->rcHeader.right;
2093 Label.bottom = Label.top + infoPtr->nItemHeight;
2095 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2097 Label.left = Icon.right;
2098 Label.top = Box.top;
2099 Label.right = min(Label.left + labelSize.cx, Label.right);
2100 Label.bottom = Label.top + infoPtr->nItemHeight;
2103 if (lprcLabel) *lprcLabel = Label;
2104 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2107 /************************************************************/
2108 /* compute STATEICON bounding box */
2109 /************************************************************/
2110 if (doSelectBox)
2112 if (uView == LVS_REPORT)
2114 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2115 SelectBox.top = Box.top;
2116 SelectBox.bottom = Box.bottom;
2117 if (lpLVItem->iSubItem == 0)
2119 /* we need the indent in report mode */
2120 assert(lpLVItem->mask & LVIF_INDENT);
2121 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2123 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2125 else
2127 UnionRect(&SelectBox, &Icon, &Label);
2129 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2130 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2133 /* Fix the Box if necessary */
2134 if (lprcBox)
2136 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2137 else *lprcBox = Box;
2139 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2142 /***
2143 * DESCRIPTION: [INTERNAL]
2145 * PARAMETER(S):
2146 * [I] infoPtr : valid pointer to the listview structure
2147 * [I] nItem : item number
2148 * [O] lprcBox : ptr to Box rectangle
2150 * RETURN:
2151 * None.
2153 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2155 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2156 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2157 POINT Position, Origin;
2158 LVITEMW lvItem;
2160 LISTVIEW_GetOrigin(infoPtr, &Origin);
2161 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2163 /* Be smart and try to figure out the minimum we have to do */
2164 lvItem.mask = 0;
2165 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2166 lvItem.mask |= LVIF_TEXT;
2167 lvItem.iItem = nItem;
2168 lvItem.iSubItem = 0;
2169 lvItem.pszText = szDispText;
2170 lvItem.cchTextMax = DISP_TEXT_SIZE;
2171 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2172 if (uView == LVS_ICON)
2174 lvItem.mask |= LVIF_STATE;
2175 lvItem.stateMask = LVIS_FOCUSED;
2176 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2178 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2180 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2184 /***
2185 * DESCRIPTION:
2186 * Returns the current icon position, and advances it along the top.
2187 * The returned position is not offset by Origin.
2189 * PARAMETER(S):
2190 * [I] infoPtr : valid pointer to the listview structure
2191 * [O] lpPos : will get the current icon position
2193 * RETURN:
2194 * None
2196 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2198 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2200 *lpPos = infoPtr->currIconPos;
2202 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2203 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2205 infoPtr->currIconPos.x = 0;
2206 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2210 /***
2211 * DESCRIPTION:
2212 * Returns the current icon position, and advances it down the left edge.
2213 * The returned position is not offset by Origin.
2215 * PARAMETER(S):
2216 * [I] infoPtr : valid pointer to the listview structure
2217 * [O] lpPos : will get the current icon position
2219 * RETURN:
2220 * None
2222 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2224 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2226 *lpPos = infoPtr->currIconPos;
2228 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2229 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2231 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2232 infoPtr->currIconPos.y = 0;
2236 /***
2237 * DESCRIPTION:
2238 * Moves an icon to the specified position.
2239 * It takes care of invalidating the item, etc.
2241 * PARAMETER(S):
2242 * [I] infoPtr : valid pointer to the listview structure
2243 * [I] nItem : the item to move
2244 * [I] lpPos : the new icon position
2245 * [I] isNew : flags the item as being new
2247 * RETURN:
2248 * Success: TRUE
2249 * Failure: FALSE
2251 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2253 POINT old;
2255 if (!isNew)
2257 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2258 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2260 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2261 LISTVIEW_InvalidateItem(infoPtr, nItem);
2264 /* Allocating a POINTER for every item is too resource intensive,
2265 * so we'll keep the (x,y) in different arrays */
2266 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2267 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2269 LISTVIEW_InvalidateItem(infoPtr, nItem);
2271 return TRUE;
2274 /***
2275 * DESCRIPTION:
2276 * Arranges listview items in icon display mode.
2278 * PARAMETER(S):
2279 * [I] infoPtr : valid pointer to the listview structure
2280 * [I] nAlignCode : alignment code
2282 * RETURN:
2283 * SUCCESS : TRUE
2284 * FAILURE : FALSE
2286 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2288 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2289 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2290 POINT pos;
2291 INT i;
2293 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2295 TRACE("nAlignCode=%d\n", nAlignCode);
2297 if (nAlignCode == LVA_DEFAULT)
2299 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2300 else nAlignCode = LVA_ALIGNTOP;
2303 switch (nAlignCode)
2305 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2306 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2307 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2308 default: return FALSE;
2311 infoPtr->bAutoarrange = TRUE;
2312 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2313 for (i = 0; i < infoPtr->nItemCount; i++)
2315 next_pos(infoPtr, &pos);
2316 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2319 return TRUE;
2322 /***
2323 * DESCRIPTION:
2324 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2326 * PARAMETER(S):
2327 * [I] infoPtr : valid pointer to the listview structure
2328 * [O] lprcView : bounding rectangle
2330 * RETURN:
2331 * SUCCESS : TRUE
2332 * FAILURE : FALSE
2334 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2336 INT i, x, y;
2338 SetRectEmpty(lprcView);
2340 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2342 case LVS_ICON:
2343 case LVS_SMALLICON:
2344 for (i = 0; i < infoPtr->nItemCount; i++)
2346 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2347 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2348 lprcView->right = max(lprcView->right, x);
2349 lprcView->bottom = max(lprcView->bottom, y);
2351 if (infoPtr->nItemCount > 0)
2353 lprcView->right += infoPtr->nItemWidth;
2354 lprcView->bottom += infoPtr->nItemHeight;
2356 break;
2358 case LVS_LIST:
2359 y = LISTVIEW_GetCountPerColumn(infoPtr);
2360 x = infoPtr->nItemCount / y;
2361 if (infoPtr->nItemCount % y) x++;
2362 lprcView->right = x * infoPtr->nItemWidth;
2363 lprcView->bottom = y * infoPtr->nItemHeight;
2364 break;
2366 case LVS_REPORT:
2367 lprcView->right = infoPtr->nItemWidth;
2368 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2369 break;
2373 /***
2374 * DESCRIPTION:
2375 * Retrieves the bounding rectangle of all the items.
2377 * PARAMETER(S):
2378 * [I] infoPtr : valid pointer to the listview structure
2379 * [O] lprcView : bounding rectangle
2381 * RETURN:
2382 * SUCCESS : TRUE
2383 * FAILURE : FALSE
2385 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2387 POINT ptOrigin;
2389 TRACE("(lprcView=%p)\n", lprcView);
2391 if (!lprcView) return FALSE;
2393 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2394 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2395 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2397 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2399 return TRUE;
2402 /***
2403 * DESCRIPTION:
2404 * Retrieves the subitem pointer associated with the subitem index.
2406 * PARAMETER(S):
2407 * [I] hdpaSubItems : DPA handle for a specific item
2408 * [I] nSubItem : index of subitem
2410 * RETURN:
2411 * SUCCESS : subitem pointer
2412 * FAILURE : NULL
2414 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2416 SUBITEM_INFO *lpSubItem;
2417 INT i;
2419 /* we should binary search here if need be */
2420 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2422 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2423 if (lpSubItem->iSubItem == nSubItem)
2424 return lpSubItem;
2427 return NULL;
2431 /***
2432 * DESCRIPTION:
2433 * Calculates the desired item width.
2435 * PARAMETER(S):
2436 * [I] infoPtr : valid pointer to the listview structure
2438 * RETURN:
2439 * The desired item width.
2441 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2443 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2444 INT nItemWidth = 0;
2446 TRACE("uView=%d\n", uView);
2448 if (uView == LVS_ICON)
2449 nItemWidth = infoPtr->iconSpacing.cx;
2450 else if (uView == LVS_REPORT)
2452 RECT rcHeader;
2454 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2456 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2457 nItemWidth = rcHeader.right;
2460 else /* LVS_SMALLICON, or LVS_LIST */
2462 INT i;
2464 for (i = 0; i < infoPtr->nItemCount; i++)
2465 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2467 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2468 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2470 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2473 return max(nItemWidth, 1);
2476 /***
2477 * DESCRIPTION:
2478 * Calculates the desired item height.
2480 * PARAMETER(S):
2481 * [I] infoPtr : valid pointer to the listview structure
2483 * RETURN:
2484 * The desired item height.
2486 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2488 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2489 INT nItemHeight;
2491 TRACE("uView=%d\n", uView);
2493 if (uView == LVS_ICON)
2494 nItemHeight = infoPtr->iconSpacing.cy;
2495 else
2497 nItemHeight = infoPtr->ntmHeight;
2498 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2499 nItemHeight++;
2500 if (infoPtr->himlState)
2501 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2502 if (infoPtr->himlSmall)
2503 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2504 if (infoPtr->himlState || infoPtr->himlSmall)
2505 nItemHeight += HEIGHT_PADDING;
2506 if (infoPtr->nMeasureItemHeight > 0)
2507 nItemHeight = infoPtr->nMeasureItemHeight;
2510 return max(nItemHeight, 1);
2513 /***
2514 * DESCRIPTION:
2515 * Updates the width, and height of an item.
2517 * PARAMETER(S):
2518 * [I] infoPtr : valid pointer to the listview structure
2520 * RETURN:
2521 * None.
2523 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2525 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2526 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2530 /***
2531 * DESCRIPTION:
2532 * Retrieves and saves important text metrics info for the current
2533 * Listview font.
2535 * PARAMETER(S):
2536 * [I] infoPtr : valid pointer to the listview structure
2539 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2541 HDC hdc = GetDC(infoPtr->hwndSelf);
2542 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2543 HFONT hOldFont = SelectObject(hdc, hFont);
2544 TEXTMETRICW tm;
2545 SIZE sz;
2547 if (GetTextMetricsW(hdc, &tm))
2549 infoPtr->ntmHeight = tm.tmHeight;
2550 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2553 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2554 infoPtr->nEllipsisWidth = sz.cx;
2556 SelectObject(hdc, hOldFont);
2557 ReleaseDC(infoPtr->hwndSelf, hdc);
2559 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2562 /***
2563 * DESCRIPTION:
2564 * A compare function for ranges
2566 * PARAMETER(S)
2567 * [I] range1 : pointer to range 1;
2568 * [I] range2 : pointer to range 2;
2569 * [I] flags : flags
2571 * RETURNS:
2572 * > 0 : if range 1 > range 2
2573 * < 0 : if range 2 > range 1
2574 * = 0 : if range intersects range 2
2576 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2578 INT cmp;
2580 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2581 cmp = -1;
2582 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2583 cmp = 1;
2584 else
2585 cmp = 0;
2587 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2589 return cmp;
2592 #if DEBUG_RANGES
2593 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2594 #else
2595 #define ranges_check(ranges, desc) do { } while(0)
2596 #endif
2598 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2600 INT i;
2601 RANGE *prev, *curr;
2603 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2604 assert (ranges);
2605 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2606 ranges_dump(ranges);
2607 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2608 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2609 assert (prev->lower >= 0 && prev->lower < prev->upper);
2610 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2612 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2613 assert (prev->upper <= curr->lower);
2614 assert (curr->lower < curr->upper);
2615 prev = curr;
2617 TRACE("--- Done checking---\n");
2620 static RANGES ranges_create(int count)
2622 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2623 if (!ranges) return NULL;
2624 ranges->hdpa = DPA_Create(count);
2625 if (ranges->hdpa) return ranges;
2626 Free(ranges);
2627 return NULL;
2630 static void ranges_clear(RANGES ranges)
2632 INT i;
2634 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2635 Free(DPA_GetPtr(ranges->hdpa, i));
2636 DPA_DeleteAllPtrs(ranges->hdpa);
2640 static void ranges_destroy(RANGES ranges)
2642 if (!ranges) return;
2643 ranges_clear(ranges);
2644 DPA_Destroy(ranges->hdpa);
2645 Free(ranges);
2648 static RANGES ranges_clone(RANGES ranges)
2650 RANGES clone;
2651 INT i;
2653 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2655 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2657 RANGE *newrng = Alloc(sizeof(RANGE));
2658 if (!newrng) goto fail;
2659 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2660 DPA_SetPtr(clone->hdpa, i, newrng);
2662 return clone;
2664 fail:
2665 TRACE ("clone failed\n");
2666 ranges_destroy(clone);
2667 return NULL;
2670 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2672 INT i;
2674 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2675 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2677 return ranges;
2680 static void ranges_dump(RANGES ranges)
2682 INT i;
2684 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2685 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2688 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2690 RANGE srchrng = { nItem, nItem + 1 };
2692 TRACE("(nItem=%d)\n", nItem);
2693 ranges_check(ranges, "before contain");
2694 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2697 static INT ranges_itemcount(RANGES ranges)
2699 INT i, count = 0;
2701 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2703 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2704 count += sel->upper - sel->lower;
2707 return count;
2710 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2712 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2713 INT index;
2715 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2716 if (index == -1) return TRUE;
2718 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2720 chkrng = DPA_GetPtr(ranges->hdpa, index);
2721 if (chkrng->lower >= nItem)
2722 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2723 if (chkrng->upper > nItem)
2724 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2726 return TRUE;
2729 static BOOL ranges_add(RANGES ranges, RANGE range)
2731 RANGE srchrgn;
2732 INT index;
2734 TRACE("(%s)\n", debugrange(&range));
2735 ranges_check(ranges, "before add");
2737 /* try find overlapping regions first */
2738 srchrgn.lower = range.lower - 1;
2739 srchrgn.upper = range.upper + 1;
2740 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2742 if (index == -1)
2744 RANGE *newrgn;
2746 TRACE("Adding new range\n");
2748 /* create the brand new range to insert */
2749 newrgn = Alloc(sizeof(RANGE));
2750 if(!newrgn) goto fail;
2751 *newrgn = range;
2753 /* figure out where to insert it */
2754 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2755 TRACE("index=%d\n", index);
2756 if (index == -1) index = 0;
2758 /* and get it over with */
2759 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2761 Free(newrgn);
2762 goto fail;
2765 else
2767 RANGE *chkrgn, *mrgrgn;
2768 INT fromindex, mergeindex;
2770 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2771 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2773 chkrgn->lower = min(range.lower, chkrgn->lower);
2774 chkrgn->upper = max(range.upper, chkrgn->upper);
2776 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2778 /* merge now common ranges */
2779 fromindex = 0;
2780 srchrgn.lower = chkrgn->lower - 1;
2781 srchrgn.upper = chkrgn->upper + 1;
2785 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2786 if (mergeindex == -1) break;
2787 if (mergeindex == index)
2789 fromindex = index + 1;
2790 continue;
2793 TRACE("Merge with index %i\n", mergeindex);
2795 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2796 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2797 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2798 Free(mrgrgn);
2799 DPA_DeletePtr(ranges->hdpa, mergeindex);
2800 if (mergeindex < index) index --;
2801 } while(1);
2804 ranges_check(ranges, "after add");
2805 return TRUE;
2807 fail:
2808 ranges_check(ranges, "failed add");
2809 return FALSE;
2812 static BOOL ranges_del(RANGES ranges, RANGE range)
2814 RANGE *chkrgn;
2815 INT index;
2817 TRACE("(%s)\n", debugrange(&range));
2818 ranges_check(ranges, "before del");
2820 /* we don't use DPAS_SORTED here, since we need *
2821 * to find the first overlapping range */
2822 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2823 while(index != -1)
2825 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2827 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2829 /* case 1: Same range */
2830 if ( (chkrgn->upper == range.upper) &&
2831 (chkrgn->lower == range.lower) )
2833 DPA_DeletePtr(ranges->hdpa, index);
2834 break;
2836 /* case 2: engulf */
2837 else if ( (chkrgn->upper <= range.upper) &&
2838 (chkrgn->lower >= range.lower) )
2840 DPA_DeletePtr(ranges->hdpa, index);
2842 /* case 3: overlap upper */
2843 else if ( (chkrgn->upper <= range.upper) &&
2844 (chkrgn->lower < range.lower) )
2846 chkrgn->upper = range.lower;
2848 /* case 4: overlap lower */
2849 else if ( (chkrgn->upper > range.upper) &&
2850 (chkrgn->lower >= range.lower) )
2852 chkrgn->lower = range.upper;
2853 break;
2855 /* case 5: fully internal */
2856 else
2858 RANGE tmprgn = *chkrgn, *newrgn;
2860 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2861 newrgn->lower = chkrgn->lower;
2862 newrgn->upper = range.lower;
2863 chkrgn->lower = range.upper;
2864 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2866 Free(newrgn);
2867 goto fail;
2869 chkrgn = &tmprgn;
2870 break;
2873 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2876 ranges_check(ranges, "after del");
2877 return TRUE;
2879 fail:
2880 ranges_check(ranges, "failed del");
2881 return FALSE;
2884 /***
2885 * DESCRIPTION:
2886 * Removes all selection ranges
2888 * Parameters(s):
2889 * [I] infoPtr : valid pointer to the listview structure
2890 * [I] toSkip : item range to skip removing the selection
2892 * RETURNS:
2893 * SUCCESS : TRUE
2894 * FAILURE : TRUE
2896 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2898 LVITEMW lvItem;
2899 ITERATOR i;
2900 RANGES clone;
2902 TRACE("()\n");
2904 lvItem.state = 0;
2905 lvItem.stateMask = LVIS_SELECTED;
2907 /* need to clone the DPA because callbacks can change it */
2908 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2909 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2910 while(iterator_next(&i))
2911 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2912 /* note that the iterator destructor will free the cloned range */
2913 iterator_destroy(&i);
2915 return TRUE;
2918 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2920 RANGES toSkip;
2922 if (!(toSkip = ranges_create(1))) return FALSE;
2923 if (nItem != -1) ranges_additem(toSkip, nItem);
2924 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2925 ranges_destroy(toSkip);
2926 return TRUE;
2929 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2931 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2934 /***
2935 * DESCRIPTION:
2936 * Retrieves the number of items that are marked as selected.
2938 * PARAMETER(S):
2939 * [I] infoPtr : valid pointer to the listview structure
2941 * RETURN:
2942 * Number of items selected.
2944 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2946 INT nSelectedCount = 0;
2948 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2950 INT i;
2951 for (i = 0; i < infoPtr->nItemCount; i++)
2953 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2954 nSelectedCount++;
2957 else
2958 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2960 TRACE("nSelectedCount=%d\n", nSelectedCount);
2961 return nSelectedCount;
2964 /***
2965 * DESCRIPTION:
2966 * Manages the item focus.
2968 * PARAMETER(S):
2969 * [I] infoPtr : valid pointer to the listview structure
2970 * [I] nItem : item index
2972 * RETURN:
2973 * TRUE : focused item changed
2974 * FALSE : focused item has NOT changed
2976 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2978 INT oldFocus = infoPtr->nFocusedItem;
2979 LVITEMW lvItem;
2981 if (nItem == infoPtr->nFocusedItem) return FALSE;
2983 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2984 lvItem.stateMask = LVIS_FOCUSED;
2985 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2987 return oldFocus != infoPtr->nFocusedItem;
2990 /* Helper function for LISTVIEW_ShiftIndices *only* */
2991 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2993 if (nShiftItem < nItem) return nShiftItem;
2995 if (nShiftItem > nItem) return nShiftItem + direction;
2997 if (direction > 0) return nShiftItem + direction;
2999 return min(nShiftItem, infoPtr->nItemCount - 1);
3003 * DESCRIPTION:
3004 * Updates the various indices after an item has been inserted or deleted.
3006 * PARAMETER(S):
3007 * [I] infoPtr : valid pointer to the listview structure
3008 * [I] nItem : item index
3009 * [I] direction : Direction of shift, +1 or -1.
3011 * RETURN:
3012 * None
3014 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3016 INT nNewFocus;
3017 BOOL bOldChange;
3019 /* temporarily disable change notification while shifting items */
3020 bOldChange = infoPtr->bDoChangeNotify;
3021 infoPtr->bDoChangeNotify = FALSE;
3023 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3025 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3027 assert(abs(direction) == 1);
3029 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3031 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3032 if (nNewFocus != infoPtr->nFocusedItem)
3033 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3035 /* But we are not supposed to modify nHotItem! */
3037 infoPtr->bDoChangeNotify = bOldChange;
3042 * DESCRIPTION:
3043 * Adds a block of selections.
3045 * PARAMETER(S):
3046 * [I] infoPtr : valid pointer to the listview structure
3047 * [I] nItem : item index
3049 * RETURN:
3050 * Whether the window is still valid.
3052 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3054 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3055 INT nLast = max(infoPtr->nSelectionMark, nItem);
3056 HWND hwndSelf = infoPtr->hwndSelf;
3057 NMLVODSTATECHANGE nmlv;
3058 LVITEMW item;
3059 BOOL bOldChange;
3060 INT i;
3062 /* Temporarily disable change notification
3063 * If the control is LVS_OWNERDATA, we need to send
3064 * only one LVN_ODSTATECHANGED notification.
3065 * See MSDN documentation for LVN_ITEMCHANGED.
3067 bOldChange = infoPtr->bDoChangeNotify;
3068 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3070 if (nFirst == -1) nFirst = nItem;
3072 item.state = LVIS_SELECTED;
3073 item.stateMask = LVIS_SELECTED;
3075 for (i = nFirst; i <= nLast; i++)
3076 LISTVIEW_SetItemState(infoPtr,i,&item);
3078 ZeroMemory(&nmlv, sizeof(nmlv));
3079 nmlv.iFrom = nFirst;
3080 nmlv.iTo = nLast;
3081 nmlv.uNewState = 0;
3082 nmlv.uOldState = item.state;
3084 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3085 if (!IsWindow(hwndSelf))
3086 return FALSE;
3087 infoPtr->bDoChangeNotify = bOldChange;
3088 return TRUE;
3092 /***
3093 * DESCRIPTION:
3094 * Sets a single group selection.
3096 * PARAMETER(S):
3097 * [I] infoPtr : valid pointer to the listview structure
3098 * [I] nItem : item index
3100 * RETURN:
3101 * None
3103 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3105 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3106 RANGES selection;
3107 LVITEMW item;
3108 ITERATOR i;
3109 BOOL bOldChange;
3111 if (!(selection = ranges_create(100))) return;
3113 item.state = LVIS_SELECTED;
3114 item.stateMask = LVIS_SELECTED;
3116 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3118 if (infoPtr->nSelectionMark == -1)
3120 infoPtr->nSelectionMark = nItem;
3121 ranges_additem(selection, nItem);
3123 else
3125 RANGE sel;
3127 sel.lower = min(infoPtr->nSelectionMark, nItem);
3128 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3129 ranges_add(selection, sel);
3132 else
3134 RECT rcItem, rcSel, rcSelMark;
3135 POINT ptItem;
3137 rcItem.left = LVIR_BOUNDS;
3138 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3139 rcSelMark.left = LVIR_BOUNDS;
3140 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3141 UnionRect(&rcSel, &rcItem, &rcSelMark);
3142 iterator_frameditems(&i, infoPtr, &rcSel);
3143 while(iterator_next(&i))
3145 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3146 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3148 iterator_destroy(&i);
3151 bOldChange = infoPtr->bDoChangeNotify;
3152 infoPtr->bDoChangeNotify = FALSE;
3154 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3157 iterator_rangesitems(&i, selection);
3158 while(iterator_next(&i))
3159 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3160 /* this will also destroy the selection */
3161 iterator_destroy(&i);
3163 infoPtr->bDoChangeNotify = bOldChange;
3165 LISTVIEW_SetItemFocus(infoPtr, nItem);
3168 /***
3169 * DESCRIPTION:
3170 * Sets a single selection.
3172 * PARAMETER(S):
3173 * [I] infoPtr : valid pointer to the listview structure
3174 * [I] nItem : item index
3176 * RETURN:
3177 * None
3179 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3181 LVITEMW lvItem;
3183 TRACE("nItem=%d\n", nItem);
3185 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3187 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3188 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3189 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3191 infoPtr->nSelectionMark = nItem;
3194 /***
3195 * DESCRIPTION:
3196 * Set selection(s) with keyboard.
3198 * PARAMETER(S):
3199 * [I] infoPtr : valid pointer to the listview structure
3200 * [I] nItem : item index
3202 * RETURN:
3203 * SUCCESS : TRUE (needs to be repainted)
3204 * FAILURE : FALSE (nothing has changed)
3206 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3208 /* FIXME: pass in the state */
3209 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3210 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3211 BOOL bResult = FALSE;
3213 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3214 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3216 if (infoPtr->dwStyle & LVS_SINGLESEL)
3218 bResult = TRUE;
3219 LISTVIEW_SetSelection(infoPtr, nItem);
3221 else
3223 if (wShift)
3225 bResult = TRUE;
3226 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3228 else if (wCtrl)
3230 LVITEMW lvItem;
3231 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3232 lvItem.stateMask = LVIS_SELECTED;
3233 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3235 if (lvItem.state & LVIS_SELECTED)
3236 infoPtr->nSelectionMark = nItem;
3238 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3240 else
3242 bResult = TRUE;
3243 LISTVIEW_SetSelection(infoPtr, nItem);
3246 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3249 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3250 return bResult;
3253 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3255 LVHITTESTINFO lvHitTestInfo;
3257 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3258 lvHitTestInfo.pt.x = pt.x;
3259 lvHitTestInfo.pt.y = pt.y;
3261 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3263 lpLVItem->mask = LVIF_PARAM;
3264 lpLVItem->iItem = lvHitTestInfo.iItem;
3265 lpLVItem->iSubItem = 0;
3267 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3270 /***
3271 * DESCRIPTION:
3272 * Called when the mouse is being actively tracked and has hovered for a specified
3273 * amount of time
3275 * PARAMETER(S):
3276 * [I] infoPtr : valid pointer to the listview structure
3277 * [I] fwKeys : key indicator
3278 * [I] x,y : mouse position
3280 * RETURN:
3281 * 0 if the message was processed, non-zero if there was an error
3283 * INFO:
3284 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3285 * over the item for a certain period of time.
3288 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3290 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3292 LVITEMW item;
3293 POINT pt;
3295 pt.x = x;
3296 pt.y = y;
3298 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3299 LISTVIEW_SetSelection(infoPtr, item.iItem);
3302 return 0;
3305 /***
3306 * DESCRIPTION:
3307 * Called whenever WM_MOUSEMOVE is received.
3309 * PARAMETER(S):
3310 * [I] infoPtr : valid pointer to the listview structure
3311 * [I] fwKeys : key indicator
3312 * [I] x,y : mouse position
3314 * RETURN:
3315 * 0 if the message is processed, non-zero if there was an error
3317 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3319 TRACKMOUSEEVENT trackinfo;
3321 if (!(fwKeys & MK_LBUTTON))
3322 infoPtr->bLButtonDown = FALSE;
3324 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3326 LVHITTESTINFO lvHitTestInfo;
3327 NMLISTVIEW nmlv;
3329 lvHitTestInfo.pt = infoPtr->ptClickPos;
3330 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3332 ZeroMemory(&nmlv, sizeof(nmlv));
3333 nmlv.iItem = lvHitTestInfo.iItem;
3334 nmlv.ptAction = infoPtr->ptClickPos;
3336 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3338 return 0;
3340 else
3341 infoPtr->bLButtonDown = FALSE;
3343 /* see if we are supposed to be tracking mouse hovering */
3344 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3345 /* fill in the trackinfo struct */
3346 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3347 trackinfo.dwFlags = TME_QUERY;
3348 trackinfo.hwndTrack = infoPtr->hwndSelf;
3349 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3351 /* see if we are already tracking this hwnd */
3352 _TrackMouseEvent(&trackinfo);
3354 if(!(trackinfo.dwFlags & TME_HOVER)) {
3355 trackinfo.dwFlags = TME_HOVER;
3357 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3358 _TrackMouseEvent(&trackinfo);
3362 return 0;
3366 /***
3367 * Tests whether the item is assignable to a list with style lStyle
3369 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3371 if ( (lpLVItem->mask & LVIF_TEXT) &&
3372 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3373 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3375 return TRUE;
3379 /***
3380 * DESCRIPTION:
3381 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3383 * PARAMETER(S):
3384 * [I] infoPtr : valid pointer to the listview structure
3385 * [I] lpLVItem : valid pointer to new item attributes
3386 * [I] isNew : the item being set is being inserted
3387 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3388 * [O] bChanged : will be set to TRUE if the item really changed
3390 * RETURN:
3391 * SUCCESS : TRUE
3392 * FAILURE : FALSE
3394 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3396 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3397 ITEM_INFO *lpItem;
3398 NMLISTVIEW nmlv;
3399 UINT uChanged = 0;
3400 LVITEMW item;
3402 TRACE("()\n");
3404 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3406 if (lpLVItem->mask == 0) return TRUE;
3408 if (infoPtr->dwStyle & LVS_OWNERDATA)
3410 /* a virtual listview only stores selection and focus */
3411 if (lpLVItem->mask & ~LVIF_STATE)
3412 return FALSE;
3413 lpItem = NULL;
3415 else
3417 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3418 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3419 assert (lpItem);
3422 /* we need to get the lParam and state of the item */
3423 item.iItem = lpLVItem->iItem;
3424 item.iSubItem = lpLVItem->iSubItem;
3425 item.mask = LVIF_STATE | LVIF_PARAM;
3426 item.stateMask = ~0;
3427 item.state = 0;
3428 item.lParam = 0;
3429 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3431 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3432 /* determine what fields will change */
3433 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3434 uChanged |= LVIF_STATE;
3436 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3437 uChanged |= LVIF_IMAGE;
3439 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3440 uChanged |= LVIF_PARAM;
3442 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3443 uChanged |= LVIF_INDENT;
3445 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3446 uChanged |= LVIF_TEXT;
3448 TRACE("uChanged=0x%x\n", uChanged);
3449 if (!uChanged) return TRUE;
3450 *bChanged = TRUE;
3452 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3453 nmlv.iItem = lpLVItem->iItem;
3454 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3455 nmlv.uOldState = item.state;
3456 nmlv.uChanged = uChanged;
3457 nmlv.lParam = item.lParam;
3459 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3460 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3461 /* are enabled */
3462 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3464 HWND hwndSelf = infoPtr->hwndSelf;
3466 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3467 return FALSE;
3468 if (!IsWindow(hwndSelf))
3469 return FALSE;
3472 /* copy information */
3473 if (lpLVItem->mask & LVIF_TEXT)
3474 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3476 if (lpLVItem->mask & LVIF_IMAGE)
3477 lpItem->hdr.iImage = lpLVItem->iImage;
3479 if (lpLVItem->mask & LVIF_PARAM)
3480 lpItem->lParam = lpLVItem->lParam;
3482 if (lpLVItem->mask & LVIF_INDENT)
3483 lpItem->iIndent = lpLVItem->iIndent;
3485 if (uChanged & LVIF_STATE)
3487 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3489 lpItem->state &= ~lpLVItem->stateMask;
3490 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3492 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3494 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3495 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3497 else if (lpLVItem->stateMask & LVIS_SELECTED)
3498 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3500 /* if we are asked to change focus, and we manage it, do it */
3501 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3503 if (lpLVItem->state & LVIS_FOCUSED)
3505 LISTVIEW_SetItemFocus(infoPtr, -1);
3506 infoPtr->nFocusedItem = lpLVItem->iItem;
3507 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3509 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3510 infoPtr->nFocusedItem = -1;
3514 /* if we're inserting the item, we're done */
3515 if (isNew) return TRUE;
3517 /* send LVN_ITEMCHANGED notification */
3518 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3519 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3521 return TRUE;
3524 /***
3525 * DESCRIPTION:
3526 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3528 * PARAMETER(S):
3529 * [I] infoPtr : valid pointer to the listview structure
3530 * [I] lpLVItem : valid pointer to new subitem attributes
3531 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3532 * [O] bChanged : will be set to TRUE if the item really changed
3534 * RETURN:
3535 * SUCCESS : TRUE
3536 * FAILURE : FALSE
3538 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3540 HDPA hdpaSubItems;
3541 SUBITEM_INFO *lpSubItem;
3543 /* we do not support subitems for virtual listviews */
3544 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3546 /* set subitem only if column is present */
3547 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3549 /* First do some sanity checks */
3550 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3551 particularly useful. We currently do not actually do anything with
3552 the flag on subitems.
3554 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3555 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3557 /* get the subitem structure, and create it if not there */
3558 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3559 assert (hdpaSubItems);
3561 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3562 if (!lpSubItem)
3564 SUBITEM_INFO *tmpSubItem;
3565 INT i;
3567 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3568 if (!lpSubItem) return FALSE;
3569 /* we could binary search here, if need be...*/
3570 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3572 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3573 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3575 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3577 Free(lpSubItem);
3578 return FALSE;
3580 lpSubItem->iSubItem = lpLVItem->iSubItem;
3581 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3582 *bChanged = TRUE;
3585 if (lpLVItem->mask & LVIF_IMAGE)
3586 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3588 lpSubItem->hdr.iImage = lpLVItem->iImage;
3589 *bChanged = TRUE;
3592 if (lpLVItem->mask & LVIF_TEXT)
3593 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3595 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3596 *bChanged = TRUE;
3599 return TRUE;
3602 /***
3603 * DESCRIPTION:
3604 * Sets item attributes.
3606 * PARAMETER(S):
3607 * [I] infoPtr : valid pointer to the listview structure
3608 * [I] lpLVItem : new item attributes
3609 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3611 * RETURN:
3612 * SUCCESS : TRUE
3613 * FAILURE : FALSE
3615 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3617 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3618 HWND hwndSelf = infoPtr->hwndSelf;
3619 LPWSTR pszText = NULL;
3620 BOOL bResult, bChanged = FALSE;
3622 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3624 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3625 return FALSE;
3627 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3628 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3630 pszText = lpLVItem->pszText;
3631 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3634 /* actually set the fields */
3635 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3637 if (lpLVItem->iSubItem)
3638 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3639 else
3640 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3641 if (!IsWindow(hwndSelf))
3642 return FALSE;
3644 /* redraw item, if necessary */
3645 if (bChanged && !infoPtr->bIsDrawing)
3647 /* this little optimization eliminates some nasty flicker */
3648 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3649 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3650 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3651 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3652 else
3653 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3655 /* restore text */
3656 if (pszText)
3658 textfreeT(lpLVItem->pszText, isW);
3659 lpLVItem->pszText = pszText;
3662 return bResult;
3665 /***
3666 * DESCRIPTION:
3667 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3669 * PARAMETER(S):
3670 * [I] infoPtr : valid pointer to the listview structure
3672 * RETURN:
3673 * item index
3675 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3677 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3678 INT nItem = 0;
3679 SCROLLINFO scrollInfo;
3681 scrollInfo.cbSize = sizeof(SCROLLINFO);
3682 scrollInfo.fMask = SIF_POS;
3684 if (uView == LVS_LIST)
3686 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3687 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3689 else if (uView == LVS_REPORT)
3691 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3692 nItem = scrollInfo.nPos;
3694 else
3696 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3697 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3700 TRACE("nItem=%d\n", nItem);
3702 return nItem;
3706 /***
3707 * DESCRIPTION:
3708 * Erases the background of the given rectangle
3710 * PARAMETER(S):
3711 * [I] infoPtr : valid pointer to the listview structure
3712 * [I] hdc : device context handle
3713 * [I] lprcBox : clipping rectangle
3715 * RETURN:
3716 * Success: TRUE
3717 * Failure: FALSE
3719 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3721 if (!infoPtr->hBkBrush) return FALSE;
3723 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3725 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3728 /***
3729 * DESCRIPTION:
3730 * Draws an item.
3732 * PARAMETER(S):
3733 * [I] infoPtr : valid pointer to the listview structure
3734 * [I] hdc : device context handle
3735 * [I] nItem : item index
3736 * [I] nSubItem : subitem index
3737 * [I] pos : item position in client coordinates
3738 * [I] cdmode : custom draw mode
3740 * RETURN:
3741 * Success: TRUE
3742 * Failure: FALSE
3744 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3746 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3747 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3748 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3749 DWORD cdsubitemmode = CDRF_DODEFAULT;
3750 LPRECT lprcFocus;
3751 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3752 NMLVCUSTOMDRAW nmlvcd;
3753 HIMAGELIST himl;
3754 LVITEMW lvItem;
3755 HFONT hOldFont;
3757 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3759 /* get information needed for drawing the item */
3760 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3761 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3762 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3763 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3764 lvItem.iItem = nItem;
3765 lvItem.iSubItem = nSubItem;
3766 lvItem.state = 0;
3767 lvItem.lParam = 0;
3768 lvItem.cchTextMax = DISP_TEXT_SIZE;
3769 lvItem.pszText = szDispText;
3770 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3771 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3772 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3773 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3774 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3776 /* now check if we need to update the focus rectangle */
3777 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3779 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3780 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3781 OffsetRect(&rcBox, pos.x, pos.y);
3782 OffsetRect(&rcSelect, pos.x, pos.y);
3783 OffsetRect(&rcIcon, pos.x, pos.y);
3784 OffsetRect(&rcStateIcon, pos.x, pos.y);
3785 OffsetRect(&rcLabel, pos.x, pos.y);
3786 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3787 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3788 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3790 /* fill in the custom draw structure */
3791 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3793 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3794 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3795 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3796 if (cdmode & CDRF_NOTIFYITEMDRAW)
3797 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3798 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3799 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3800 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3801 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3803 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3804 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3806 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3807 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3808 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3809 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3811 /* in full row select, subitems, will just use main item's colors */
3812 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3813 nmlvcd.clrTextBk = CLR_NONE;
3815 /* state icons */
3816 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3818 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3819 if (uStateImage)
3821 TRACE("uStateImage=%d\n", uStateImage);
3822 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3823 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3827 /* small icons */
3828 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3829 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3831 TRACE("iImage=%d\n", lvItem.iImage);
3832 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3833 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3834 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3837 /* Don't bother painting item being edited */
3838 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3840 /* FIXME: temporary hack */
3841 rcSelect.left = rcLabel.left;
3843 /* draw the selection background, if we're drawing the main item */
3844 if (nSubItem == 0)
3846 /* in icon mode, the label rect is really what we want to draw the
3847 * background for */
3848 if (uView == LVS_ICON)
3849 rcSelect = rcLabel;
3851 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3852 rcSelect.right = rcBox.right;
3854 if (nmlvcd.clrTextBk != CLR_NONE)
3855 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3856 if(lprcFocus) *lprcFocus = rcSelect;
3859 /* figure out the text drawing flags */
3860 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3861 if (uView == LVS_ICON)
3862 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3863 else if (nSubItem)
3865 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3867 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3868 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3869 default: uFormat |= DT_LEFT;
3872 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3874 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3875 else rcLabel.left += LABEL_HOR_PADDING;
3877 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3879 /* for GRIDLINES reduce the bottom so the text formats correctly */
3880 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3881 rcLabel.bottom--;
3883 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3885 postpaint:
3886 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3887 notify_postpaint(infoPtr, &nmlvcd);
3888 if (cdsubitemmode & CDRF_NEWFONT)
3889 SelectObject(hdc, hOldFont);
3890 return TRUE;
3893 /***
3894 * DESCRIPTION:
3895 * Draws listview items when in owner draw mode.
3897 * PARAMETER(S):
3898 * [I] infoPtr : valid pointer to the listview structure
3899 * [I] hdc : device context handle
3901 * RETURN:
3902 * None
3904 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3906 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3907 DWORD cditemmode = CDRF_DODEFAULT;
3908 NMLVCUSTOMDRAW nmlvcd;
3909 POINT Origin, Position;
3910 DRAWITEMSTRUCT dis;
3911 LVITEMW item;
3913 TRACE("()\n");
3915 ZeroMemory(&dis, sizeof(dis));
3917 /* Get scroll info once before loop */
3918 LISTVIEW_GetOrigin(infoPtr, &Origin);
3920 /* iterate through the invalidated rows */
3921 while(iterator_next(i))
3923 item.iItem = i->nItem;
3924 item.iSubItem = 0;
3925 item.mask = LVIF_PARAM | LVIF_STATE;
3926 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3927 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3929 dis.CtlType = ODT_LISTVIEW;
3930 dis.CtlID = uID;
3931 dis.itemID = item.iItem;
3932 dis.itemAction = ODA_DRAWENTIRE;
3933 dis.itemState = 0;
3934 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3935 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3936 dis.hwndItem = infoPtr->hwndSelf;
3937 dis.hDC = hdc;
3938 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3939 dis.rcItem.left = Position.x + Origin.x;
3940 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3941 dis.rcItem.top = Position.y + Origin.y;
3942 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3943 dis.itemData = item.lParam;
3945 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3948 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3949 * structure for the rest. of the paint cycle
3951 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3952 if (cdmode & CDRF_NOTIFYITEMDRAW)
3953 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3955 if (!(cditemmode & CDRF_SKIPDEFAULT))
3957 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3958 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3961 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3962 notify_postpaint(infoPtr, &nmlvcd);
3966 /***
3967 * DESCRIPTION:
3968 * Draws listview items when in report display mode.
3970 * PARAMETER(S):
3971 * [I] infoPtr : valid pointer to the listview structure
3972 * [I] hdc : device context handle
3973 * [I] cdmode : custom draw mode
3975 * RETURN:
3976 * None
3978 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3980 INT rgntype;
3981 RECT rcClip, rcItem;
3982 POINT Origin, Position;
3983 RANGE colRange;
3984 ITERATOR j;
3986 TRACE("()\n");
3988 /* figure out what to draw */
3989 rgntype = GetClipBox(hdc, &rcClip);
3990 if (rgntype == NULLREGION) return;
3992 /* Get scroll info once before loop */
3993 LISTVIEW_GetOrigin(infoPtr, &Origin);
3995 /* narrow down the columns we need to paint */
3996 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3998 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3999 if (rcItem.right + Origin.x >= rcClip.left) break;
4001 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4003 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4004 if (rcItem.left + Origin.x < rcClip.right) break;
4006 iterator_rangeitems(&j, colRange);
4008 /* in full row select, we _have_ to draw the main item */
4009 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4010 j.nSpecial = 0;
4012 /* iterate through the invalidated rows */
4013 while(iterator_next(i))
4015 /* iterate through the invalidated columns */
4016 while(iterator_next(&j))
4018 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4019 Position.x += Origin.x;
4020 Position.y += Origin.y;
4022 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4024 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4025 rcItem.top = 0;
4026 rcItem.bottom = infoPtr->nItemHeight;
4027 OffsetRect(&rcItem, Position.x, Position.y);
4028 if (!RectVisible(hdc, &rcItem)) continue;
4031 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4034 iterator_destroy(&j);
4037 /***
4038 * DESCRIPTION:
4039 * Draws the gridlines if necessary when in report display mode.
4041 * PARAMETER(S):
4042 * [I] infoPtr : valid pointer to the listview structure
4043 * [I] hdc : device context handle
4045 * RETURN:
4046 * None
4048 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4050 INT rgntype;
4051 INT y, itemheight;
4052 HPEN hPen, hOldPen;
4053 RECT rcClip, rcItem;
4054 POINT Origin;
4055 RANGE colRange;
4056 ITERATOR j;
4058 TRACE("()\n");
4060 /* figure out what to draw */
4061 rgntype = GetClipBox(hdc, &rcClip);
4062 if (rgntype == NULLREGION) return;
4064 /* Get scroll info once before loop */
4065 LISTVIEW_GetOrigin(infoPtr, &Origin);
4067 /* narrow down the columns we need to paint */
4068 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4070 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4071 if (rcItem.right + Origin.x >= rcClip.left) break;
4073 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4075 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4076 if (rcItem.left + Origin.x < rcClip.right) break;
4078 iterator_rangeitems(&j, colRange);
4080 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4082 hOldPen = SelectObject ( hdc, hPen );
4084 /* draw the vertical lines for the columns */
4085 iterator_rangeitems(&j, colRange);
4086 while(iterator_next(&j))
4088 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4089 if (rcItem.left == 0) continue; /* skip first column */
4090 rcItem.left += Origin.x;
4091 rcItem.right += Origin.x;
4092 rcItem.top = infoPtr->rcList.top;
4093 rcItem.bottom = infoPtr->rcList.bottom;
4094 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4095 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4096 LineTo (hdc, rcItem.left, rcItem.bottom);
4098 iterator_destroy(&j);
4100 /* draw the horizontial lines for the rows */
4101 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4102 rcItem.left = infoPtr->rcList.left + Origin.x;
4103 rcItem.right = infoPtr->rcList.right + Origin.x;
4104 rcItem.bottom = rcItem.top = Origin.y - 1;
4105 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4106 LineTo(hdc, rcItem.right, rcItem.top);
4107 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4109 rcItem.bottom = rcItem.top = y;
4110 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4111 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4112 LineTo (hdc, rcItem.right, rcItem.top);
4115 SelectObject( hdc, hOldPen );
4116 DeleteObject( hPen );
4120 /***
4121 * DESCRIPTION:
4122 * Draws listview items when in list display mode.
4124 * PARAMETER(S):
4125 * [I] infoPtr : valid pointer to the listview structure
4126 * [I] hdc : device context handle
4127 * [I] cdmode : custom draw mode
4129 * RETURN:
4130 * None
4132 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4134 POINT Origin, Position;
4136 /* Get scroll info once before loop */
4137 LISTVIEW_GetOrigin(infoPtr, &Origin);
4139 while(iterator_prev(i))
4141 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4142 Position.x += Origin.x;
4143 Position.y += Origin.y;
4145 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4150 /***
4151 * DESCRIPTION:
4152 * Draws listview items.
4154 * PARAMETER(S):
4155 * [I] infoPtr : valid pointer to the listview structure
4156 * [I] hdc : device context handle
4157 * [I] prcErase : rect to be erased before refresh (may be NULL)
4159 * RETURN:
4160 * NoneX
4162 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4164 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4165 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4166 NMLVCUSTOMDRAW nmlvcd;
4167 HFONT hOldFont = 0;
4168 DWORD cdmode;
4169 INT oldBkMode = 0;
4170 RECT rcClient;
4171 ITERATOR i;
4172 HDC hdcOrig = hdc;
4173 HBITMAP hbmp = NULL;
4175 LISTVIEW_DUMP(infoPtr);
4177 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4178 TRACE("double buffering\n");
4180 hdc = CreateCompatibleDC(hdcOrig);
4181 if (!hdc) {
4182 ERR("Failed to create DC for backbuffer\n");
4183 return;
4185 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4186 infoPtr->rcList.bottom);
4187 if (!hbmp) {
4188 ERR("Failed to create bitmap for backbuffer\n");
4189 DeleteDC(hdc);
4190 return;
4193 SelectObject(hdc, hbmp);
4194 SelectObject(hdc, infoPtr->hFont);
4195 } else {
4196 /* Save dc values we're gonna trash while drawing
4197 * FIXME: Should be done in LISTVIEW_DrawItem() */
4198 hOldFont = SelectObject(hdc, infoPtr->hFont);
4199 oldBkMode = GetBkMode(hdc);
4200 oldBkColor = GetBkColor(hdc);
4201 oldTextColor = GetTextColor(hdc);
4204 infoPtr->bIsDrawing = TRUE;
4206 if (prcErase) {
4207 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4208 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4209 /* If no erasing was done (usually because RedrawWindow was called
4210 * with RDW_INVALIDATE only) we need to copy the old contents into
4211 * the backbuffer before continuing. */
4212 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4213 infoPtr->rcList.right - infoPtr->rcList.left,
4214 infoPtr->rcList.bottom - infoPtr->rcList.top,
4215 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4218 /* FIXME: Shouldn't need to do this */
4219 oldClrTextBk = infoPtr->clrTextBk;
4220 oldClrText = infoPtr->clrText;
4222 infoPtr->cditemmode = CDRF_DODEFAULT;
4224 GetClientRect(infoPtr->hwndSelf, &rcClient);
4225 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4226 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4227 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4228 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4230 /* Use these colors to draw the items */
4231 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4232 infoPtr->clrText = nmlvcd.clrText;
4234 /* nothing to draw */
4235 if(infoPtr->nItemCount == 0) goto enddraw;
4237 /* figure out what we need to draw */
4238 iterator_visibleitems(&i, infoPtr, hdc);
4240 /* send cache hint notification */
4241 if (infoPtr->dwStyle & LVS_OWNERDATA)
4243 RANGE range = iterator_range(&i);
4244 NMLVCACHEHINT nmlv;
4246 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4247 nmlv.iFrom = range.lower;
4248 nmlv.iTo = range.upper - 1;
4249 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4252 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4253 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4254 else
4256 if (uView == LVS_REPORT)
4257 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4258 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4259 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4261 /* if we have a focus rect, draw it */
4262 if (infoPtr->bFocus)
4263 DrawFocusRect(hdc, &infoPtr->rcFocus);
4265 iterator_destroy(&i);
4267 enddraw:
4268 /* For LVS_EX_GRIDLINES go and draw lines */
4269 /* This includes the case where there were *no* items */
4270 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4271 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4272 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4274 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4275 notify_postpaint(infoPtr, &nmlvcd);
4277 infoPtr->clrTextBk = oldClrTextBk;
4278 infoPtr->clrText = oldClrText;
4280 if(hbmp) {
4281 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4282 infoPtr->rcList.right - infoPtr->rcList.left,
4283 infoPtr->rcList.bottom - infoPtr->rcList.top,
4284 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4286 DeleteObject(hbmp);
4287 DeleteDC(hdc);
4288 } else {
4289 SelectObject(hdc, hOldFont);
4290 SetBkMode(hdc, oldBkMode);
4291 SetBkColor(hdc, oldBkColor);
4292 SetTextColor(hdc, oldTextColor);
4295 infoPtr->bIsDrawing = FALSE;
4299 /***
4300 * DESCRIPTION:
4301 * Calculates the approximate width and height of a given number of items.
4303 * PARAMETER(S):
4304 * [I] infoPtr : valid pointer to the listview structure
4305 * [I] nItemCount : number of items
4306 * [I] wWidth : width
4307 * [I] wHeight : height
4309 * RETURN:
4310 * Returns a DWORD. The width in the low word and the height in high word.
4312 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4313 WORD wWidth, WORD wHeight)
4315 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4316 INT nItemCountPerColumn = 1;
4317 INT nColumnCount = 0;
4318 DWORD dwViewRect = 0;
4320 if (nItemCount == -1)
4321 nItemCount = infoPtr->nItemCount;
4323 if (uView == LVS_LIST)
4325 if (wHeight == 0xFFFF)
4327 /* use current height */
4328 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4331 if (wHeight < infoPtr->nItemHeight)
4332 wHeight = infoPtr->nItemHeight;
4334 if (nItemCount > 0)
4336 if (infoPtr->nItemHeight > 0)
4338 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4339 if (nItemCountPerColumn == 0)
4340 nItemCountPerColumn = 1;
4342 if (nItemCount % nItemCountPerColumn != 0)
4343 nColumnCount = nItemCount / nItemCountPerColumn;
4344 else
4345 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4349 /* Microsoft padding magic */
4350 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4351 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4353 dwViewRect = MAKELONG(wWidth, wHeight);
4355 else if (uView == LVS_REPORT)
4357 RECT rcBox;
4359 if (infoPtr->nItemCount > 0)
4361 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4362 wWidth = rcBox.right - rcBox.left;
4363 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4365 else
4367 /* use current height and width */
4368 if (wHeight == 0xffff)
4369 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4370 if (wWidth == 0xffff)
4371 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4374 dwViewRect = MAKELONG(wWidth, wHeight);
4376 else if (uView == LVS_SMALLICON)
4377 FIXME("uView == LVS_SMALLICON: not implemented\n");
4378 else if (uView == LVS_ICON)
4379 FIXME("uView == LVS_ICON: not implemented\n");
4381 return dwViewRect;
4385 /***
4386 * DESCRIPTION:
4387 * Create a drag image list for the specified item.
4389 * PARAMETER(S):
4390 * [I] infoPtr : valid pointer to the listview structure
4391 * [I] iItem : index of item
4392 * [O] lppt : Upperr-left corner of the image
4394 * RETURN:
4395 * Returns a handle to the image list if successful, NULL otherwise.
4397 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4399 RECT rcItem;
4400 SIZE size;
4401 POINT pos;
4402 HDC hdc, hdcOrig;
4403 HBITMAP hbmp, hOldbmp;
4404 HIMAGELIST dragList = 0;
4405 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4407 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4408 return 0;
4410 rcItem.left = LVIR_BOUNDS;
4411 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4412 return 0;
4414 lppt->x = rcItem.left;
4415 lppt->y = rcItem.top;
4417 size.cx = rcItem.right - rcItem.left;
4418 size.cy = rcItem.bottom - rcItem.top;
4420 hdcOrig = GetDC(infoPtr->hwndSelf);
4421 hdc = CreateCompatibleDC(hdcOrig);
4422 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4423 hOldbmp = SelectObject(hdc, hbmp);
4425 rcItem.left = rcItem.top = 0;
4426 rcItem.right = size.cx;
4427 rcItem.bottom = size.cy;
4428 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4430 pos.x = pos.y = 0;
4431 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4433 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4434 SelectObject(hdc, hOldbmp);
4435 ImageList_Add(dragList, hbmp, 0);
4437 else
4438 SelectObject(hdc, hOldbmp);
4440 DeleteObject(hbmp);
4441 DeleteDC(hdc);
4442 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4444 TRACE("ret=%p\n", dragList);
4446 return dragList;
4450 /***
4451 * DESCRIPTION:
4452 * Removes all listview items and subitems.
4454 * PARAMETER(S):
4455 * [I] infoPtr : valid pointer to the listview structure
4457 * RETURN:
4458 * SUCCESS : TRUE
4459 * FAILURE : FALSE
4461 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4463 NMLISTVIEW nmlv;
4464 HDPA hdpaSubItems = NULL;
4465 BOOL bSuppress;
4466 ITEMHDR *hdrItem;
4467 INT i, j;
4469 TRACE("()\n");
4471 /* we do it directly, to avoid notifications */
4472 ranges_clear(infoPtr->selectionRanges);
4473 infoPtr->nSelectionMark = -1;
4474 infoPtr->nFocusedItem = -1;
4475 SetRectEmpty(&infoPtr->rcFocus);
4476 /* But we are supposed to leave nHotItem as is! */
4479 /* send LVN_DELETEALLITEMS notification */
4480 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4481 nmlv.iItem = -1;
4482 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4484 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4486 /* send LVN_DELETEITEM notification, if not suppressed */
4487 if (!bSuppress) notify_deleteitem(infoPtr, i);
4488 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4490 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4491 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4493 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4494 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4495 Free(hdrItem);
4497 DPA_Destroy(hdpaSubItems);
4498 DPA_DeletePtr(infoPtr->hdpaItems, i);
4500 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4501 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4502 infoPtr->nItemCount --;
4505 if (!destroy)
4507 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4508 LISTVIEW_UpdateScroll(infoPtr);
4510 LISTVIEW_InvalidateList(infoPtr);
4512 return TRUE;
4515 /***
4516 * DESCRIPTION:
4517 * Scrolls, and updates the columns, when a column is changing width.
4519 * PARAMETER(S):
4520 * [I] infoPtr : valid pointer to the listview structure
4521 * [I] nColumn : column to scroll
4522 * [I] dx : amount of scroll, in pixels
4524 * RETURN:
4525 * None.
4527 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4529 COLUMN_INFO *lpColumnInfo;
4530 RECT rcOld, rcCol;
4531 POINT ptOrigin;
4532 INT nCol;
4534 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4535 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4536 rcCol = lpColumnInfo->rcHeader;
4537 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4538 rcCol.left = rcCol.right;
4540 /* adjust the other columns */
4541 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4543 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4544 lpColumnInfo->rcHeader.left += dx;
4545 lpColumnInfo->rcHeader.right += dx;
4548 /* do not update screen if not in report mode */
4549 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4551 /* if we have a focus, we must first erase the focus rect */
4552 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4554 /* Need to reset the item width when inserting a new column */
4555 infoPtr->nItemWidth += dx;
4557 LISTVIEW_UpdateScroll(infoPtr);
4558 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4560 /* scroll to cover the deleted column, and invalidate for redraw */
4561 rcOld = infoPtr->rcList;
4562 rcOld.left = ptOrigin.x + rcCol.left + dx;
4563 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4565 /* we can restore focus now */
4566 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4569 /***
4570 * DESCRIPTION:
4571 * Removes a column from the listview control.
4573 * PARAMETER(S):
4574 * [I] infoPtr : valid pointer to the listview structure
4575 * [I] nColumn : column index
4577 * RETURN:
4578 * SUCCESS : TRUE
4579 * FAILURE : FALSE
4581 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4583 RECT rcCol;
4585 TRACE("nColumn=%d\n", nColumn);
4587 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4588 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4590 /* While the MSDN specifically says that column zero should not be deleted,
4591 what actually happens is that the column itself is deleted but no items or subitems
4592 are removed.
4595 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4597 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4598 return FALSE;
4600 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4601 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4603 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4605 SUBITEM_INFO *lpSubItem, *lpDelItem;
4606 HDPA hdpaSubItems;
4607 INT nItem, nSubItem, i;
4609 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4611 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4612 nSubItem = 0;
4613 lpDelItem = 0;
4614 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4616 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4617 if (lpSubItem->iSubItem == nColumn)
4619 nSubItem = i;
4620 lpDelItem = lpSubItem;
4622 else if (lpSubItem->iSubItem > nColumn)
4624 lpSubItem->iSubItem--;
4628 /* if we found our subitem, zapp it */
4629 if (nSubItem > 0)
4631 /* free string */
4632 if (is_textW(lpDelItem->hdr.pszText))
4633 Free(lpDelItem->hdr.pszText);
4635 /* free item */
4636 Free(lpDelItem);
4638 /* free dpa memory */
4639 DPA_DeletePtr(hdpaSubItems, nSubItem);
4644 /* update the other column info */
4645 LISTVIEW_UpdateItemSize(infoPtr);
4646 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4647 LISTVIEW_InvalidateList(infoPtr);
4648 else
4649 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4651 return TRUE;
4654 /***
4655 * DESCRIPTION:
4656 * Invalidates the listview after an item's insertion or deletion.
4658 * PARAMETER(S):
4659 * [I] infoPtr : valid pointer to the listview structure
4660 * [I] nItem : item index
4661 * [I] dir : -1 if deleting, 1 if inserting
4663 * RETURN:
4664 * None
4666 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4668 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4669 INT nPerCol, nItemCol, nItemRow;
4670 RECT rcScroll;
4671 POINT Origin;
4673 /* if we don't refresh, what's the point of scrolling? */
4674 if (!is_redrawing(infoPtr)) return;
4676 assert (abs(dir) == 1);
4678 /* arrange icons if autoarrange is on */
4679 if (is_autoarrange(infoPtr))
4681 BOOL arrange = TRUE;
4682 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4683 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4684 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4687 /* scrollbars need updating */
4688 LISTVIEW_UpdateScroll(infoPtr);
4690 /* figure out the item's position */
4691 if (uView == LVS_REPORT)
4692 nPerCol = infoPtr->nItemCount + 1;
4693 else if (uView == LVS_LIST)
4694 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4695 else /* LVS_ICON, or LVS_SMALLICON */
4696 return;
4698 nItemCol = nItem / nPerCol;
4699 nItemRow = nItem % nPerCol;
4700 LISTVIEW_GetOrigin(infoPtr, &Origin);
4702 /* move the items below up a slot */
4703 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4704 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4705 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4706 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4707 OffsetRect(&rcScroll, Origin.x, Origin.y);
4708 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4709 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4711 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4712 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4713 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4716 /* report has only that column, so we're done */
4717 if (uView == LVS_REPORT) return;
4719 /* now for LISTs, we have to deal with the columns to the right */
4720 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4721 rcScroll.top = 0;
4722 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4723 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4724 OffsetRect(&rcScroll, Origin.x, Origin.y);
4725 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4726 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4727 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4730 /***
4731 * DESCRIPTION:
4732 * Removes an item from the listview control.
4734 * PARAMETER(S):
4735 * [I] infoPtr : valid pointer to the listview structure
4736 * [I] nItem : item index
4738 * RETURN:
4739 * SUCCESS : TRUE
4740 * FAILURE : FALSE
4742 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4744 LVITEMW item;
4745 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4746 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4748 TRACE("(nItem=%d)\n", nItem);
4750 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4752 /* remove selection, and focus */
4753 item.state = 0;
4754 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4755 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4757 /* send LVN_DELETEITEM notification. */
4758 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4760 /* we need to do this here, because we'll be deleting stuff */
4761 if (is_icon)
4762 LISTVIEW_InvalidateItem(infoPtr, nItem);
4764 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4766 HDPA hdpaSubItems;
4767 ITEMHDR *hdrItem;
4768 INT i;
4770 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4771 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4773 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4774 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4775 Free(hdrItem);
4777 DPA_Destroy(hdpaSubItems);
4780 if (is_icon)
4782 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4783 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4786 infoPtr->nItemCount--;
4787 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4789 /* now is the invalidation fun */
4790 if (!is_icon)
4791 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4792 return TRUE;
4796 /***
4797 * DESCRIPTION:
4798 * Callback implementation for editlabel control
4800 * PARAMETER(S):
4801 * [I] infoPtr : valid pointer to the listview structure
4802 * [I] pszText : modified text
4803 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4805 * RETURN:
4806 * SUCCESS : TRUE
4807 * FAILURE : FALSE
4809 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4811 HWND hwndSelf = infoPtr->hwndSelf;
4812 NMLVDISPINFOW dispInfo;
4814 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4816 ZeroMemory(&dispInfo, sizeof(dispInfo));
4817 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4818 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4819 dispInfo.item.iSubItem = 0;
4820 dispInfo.item.stateMask = ~0;
4821 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4822 /* add the text from the edit in */
4823 dispInfo.item.mask |= LVIF_TEXT;
4824 dispInfo.item.pszText = pszText;
4825 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4827 /* Do we need to update the Item Text */
4828 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4829 if (!IsWindow(hwndSelf))
4830 return FALSE;
4831 if (!pszText) return TRUE;
4833 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4835 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4836 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4837 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4839 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4840 return TRUE;
4844 ZeroMemory(&dispInfo, sizeof(dispInfo));
4845 dispInfo.item.mask = LVIF_TEXT;
4846 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4847 dispInfo.item.iSubItem = 0;
4848 dispInfo.item.pszText = pszText;
4849 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4850 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4853 /***
4854 * DESCRIPTION:
4855 * Begin in place editing of specified list view item
4857 * PARAMETER(S):
4858 * [I] infoPtr : valid pointer to the listview structure
4859 * [I] nItem : item index
4860 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4862 * RETURN:
4863 * SUCCESS : TRUE
4864 * FAILURE : FALSE
4866 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4868 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4869 NMLVDISPINFOW dispInfo;
4870 RECT rect;
4871 HWND hwndSelf = infoPtr->hwndSelf;
4873 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4875 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4876 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4878 infoPtr->nEditLabelItem = nItem;
4880 /* Is the EditBox still there, if so remove it */
4881 if(infoPtr->hwndEdit != 0)
4883 SetFocus(infoPtr->hwndSelf);
4884 infoPtr->hwndEdit = 0;
4887 LISTVIEW_SetSelection(infoPtr, nItem);
4888 LISTVIEW_SetItemFocus(infoPtr, nItem);
4889 LISTVIEW_InvalidateItem(infoPtr, nItem);
4891 rect.left = LVIR_LABEL;
4892 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4894 ZeroMemory(&dispInfo, sizeof(dispInfo));
4895 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4896 dispInfo.item.iItem = nItem;
4897 dispInfo.item.iSubItem = 0;
4898 dispInfo.item.stateMask = ~0;
4899 dispInfo.item.pszText = szDispText;
4900 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4901 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4903 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4904 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4905 if (!infoPtr->hwndEdit) return 0;
4907 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4909 if (!IsWindow(hwndSelf))
4910 return 0;
4911 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4912 infoPtr->hwndEdit = 0;
4913 return 0;
4916 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4917 SetFocus(infoPtr->hwndEdit);
4918 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4919 return infoPtr->hwndEdit;
4923 /***
4924 * DESCRIPTION:
4925 * Ensures the specified item is visible, scrolling into view if necessary.
4927 * PARAMETER(S):
4928 * [I] infoPtr : valid pointer to the listview structure
4929 * [I] nItem : item index
4930 * [I] bPartial : partially or entirely visible
4932 * RETURN:
4933 * SUCCESS : TRUE
4934 * FAILURE : FALSE
4936 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4938 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4939 INT nScrollPosHeight = 0;
4940 INT nScrollPosWidth = 0;
4941 INT nHorzAdjust = 0;
4942 INT nVertAdjust = 0;
4943 INT nHorzDiff = 0;
4944 INT nVertDiff = 0;
4945 RECT rcItem, rcTemp;
4947 rcItem.left = LVIR_BOUNDS;
4948 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4950 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4952 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4954 /* scroll left/right, but in LVS_REPORT mode */
4955 if (uView == LVS_LIST)
4956 nScrollPosWidth = infoPtr->nItemWidth;
4957 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4958 nScrollPosWidth = 1;
4960 if (rcItem.left < infoPtr->rcList.left)
4962 nHorzAdjust = -1;
4963 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4965 else
4967 nHorzAdjust = 1;
4968 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4972 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4974 /* scroll up/down, but not in LVS_LIST mode */
4975 if (uView == LVS_REPORT)
4976 nScrollPosHeight = infoPtr->nItemHeight;
4977 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4978 nScrollPosHeight = 1;
4980 if (rcItem.top < infoPtr->rcList.top)
4982 nVertAdjust = -1;
4983 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4985 else
4987 nVertAdjust = 1;
4988 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4992 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4994 if (nScrollPosWidth)
4996 INT diff = nHorzDiff / nScrollPosWidth;
4997 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4998 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5001 if (nScrollPosHeight)
5003 INT diff = nVertDiff / nScrollPosHeight;
5004 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5005 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5008 return TRUE;
5011 /***
5012 * DESCRIPTION:
5013 * Searches for an item with specific characteristics.
5015 * PARAMETER(S):
5016 * [I] hwnd : window handle
5017 * [I] nStart : base item index
5018 * [I] lpFindInfo : item information to look for
5020 * RETURN:
5021 * SUCCESS : index of item
5022 * FAILURE : -1
5024 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5025 const LVFINDINFOW *lpFindInfo)
5027 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5028 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5029 BOOL bWrap = FALSE, bNearest = FALSE;
5030 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5031 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5032 POINT Position, Destination;
5033 LVITEMW lvItem;
5035 if (!lpFindInfo || nItem < 0) return -1;
5037 lvItem.mask = 0;
5038 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5040 lvItem.mask |= LVIF_TEXT;
5041 lvItem.pszText = szDispText;
5042 lvItem.cchTextMax = DISP_TEXT_SIZE;
5045 if (lpFindInfo->flags & LVFI_WRAP)
5046 bWrap = TRUE;
5048 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5049 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5051 POINT Origin;
5052 RECT rcArea;
5054 LISTVIEW_GetOrigin(infoPtr, &Origin);
5055 Destination.x = lpFindInfo->pt.x - Origin.x;
5056 Destination.y = lpFindInfo->pt.y - Origin.y;
5057 switch(lpFindInfo->vkDirection)
5059 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5060 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5061 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5062 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5063 case VK_HOME: Destination.x = Destination.y = 0; break;
5064 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5065 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5066 case VK_END:
5067 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5068 Destination.x = rcArea.right;
5069 Destination.y = rcArea.bottom;
5070 break;
5071 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5073 bNearest = TRUE;
5075 else Destination.x = Destination.y = 0;
5077 /* if LVFI_PARAM is specified, all other flags are ignored */
5078 if (lpFindInfo->flags & LVFI_PARAM)
5080 lvItem.mask |= LVIF_PARAM;
5081 bNearest = FALSE;
5082 lvItem.mask &= ~LVIF_TEXT;
5085 again:
5086 for (; nItem < nLast; nItem++)
5088 lvItem.iItem = nItem;
5089 lvItem.iSubItem = 0;
5090 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5092 if (lvItem.mask & LVIF_PARAM)
5094 if (lpFindInfo->lParam == lvItem.lParam)
5095 return nItem;
5096 else
5097 continue;
5100 if (lvItem.mask & LVIF_TEXT)
5102 if (lpFindInfo->flags & LVFI_PARTIAL)
5104 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5106 else
5108 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5112 if (!bNearest) return nItem;
5114 /* This is very inefficient. To do a good job here,
5115 * we need a sorted array of (x,y) item positions */
5116 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5118 /* compute the distance^2 to the destination */
5119 xdist = Destination.x - Position.x;
5120 ydist = Destination.y - Position.y;
5121 dist = xdist * xdist + ydist * ydist;
5123 /* remember the distance, and item if it's closer */
5124 if (dist < mindist)
5126 mindist = dist;
5127 nNearestItem = nItem;
5131 if (bWrap)
5133 nItem = 0;
5134 nLast = min(nStart + 1, infoPtr->nItemCount);
5135 bWrap = FALSE;
5136 goto again;
5139 return nNearestItem;
5142 /***
5143 * DESCRIPTION:
5144 * Searches for an item with specific characteristics.
5146 * PARAMETER(S):
5147 * [I] hwnd : window handle
5148 * [I] nStart : base item index
5149 * [I] lpFindInfo : item information to look for
5151 * RETURN:
5152 * SUCCESS : index of item
5153 * FAILURE : -1
5155 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5156 const LVFINDINFOA *lpFindInfo)
5158 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5159 LVFINDINFOW fiw;
5160 INT res;
5161 LPWSTR strW = NULL;
5163 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5164 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5165 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5166 textfreeT(strW, FALSE);
5167 return res;
5170 /***
5171 * DESCRIPTION:
5172 * Retrieves the background image of the listview control.
5174 * PARAMETER(S):
5175 * [I] infoPtr : valid pointer to the listview structure
5176 * [O] lpBkImage : background image attributes
5178 * RETURN:
5179 * SUCCESS : TRUE
5180 * FAILURE : FALSE
5182 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5183 /* { */
5184 /* FIXME (listview, "empty stub!\n"); */
5185 /* return FALSE; */
5186 /* } */
5188 /***
5189 * DESCRIPTION:
5190 * Retrieves column attributes.
5192 * PARAMETER(S):
5193 * [I] infoPtr : valid pointer to the listview structure
5194 * [I] nColumn : column index
5195 * [IO] lpColumn : column information
5196 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5197 * otherwise it is in fact a LPLVCOLUMNA
5199 * RETURN:
5200 * SUCCESS : TRUE
5201 * FAILURE : FALSE
5203 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5205 COLUMN_INFO *lpColumnInfo;
5206 HDITEMW hdi;
5208 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5209 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5211 /* initialize memory */
5212 ZeroMemory(&hdi, sizeof(hdi));
5214 if (lpColumn->mask & LVCF_TEXT)
5216 hdi.mask |= HDI_TEXT;
5217 hdi.pszText = lpColumn->pszText;
5218 hdi.cchTextMax = lpColumn->cchTextMax;
5221 if (lpColumn->mask & LVCF_IMAGE)
5222 hdi.mask |= HDI_IMAGE;
5224 if (lpColumn->mask & LVCF_ORDER)
5225 hdi.mask |= HDI_ORDER;
5227 if (lpColumn->mask & LVCF_SUBITEM)
5228 hdi.mask |= HDI_LPARAM;
5230 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5232 if (lpColumn->mask & LVCF_FMT)
5233 lpColumn->fmt = lpColumnInfo->fmt;
5235 if (lpColumn->mask & LVCF_WIDTH)
5236 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5238 if (lpColumn->mask & LVCF_IMAGE)
5239 lpColumn->iImage = hdi.iImage;
5241 if (lpColumn->mask & LVCF_ORDER)
5242 lpColumn->iOrder = hdi.iOrder;
5244 if (lpColumn->mask & LVCF_SUBITEM)
5245 lpColumn->iSubItem = hdi.lParam;
5247 return TRUE;
5251 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5253 INT i;
5255 if (!lpiArray)
5256 return FALSE;
5258 /* FIXME: little hack */
5259 for (i = 0; i < iCount; i++)
5260 lpiArray[i] = i;
5262 return TRUE;
5265 /***
5266 * DESCRIPTION:
5267 * Retrieves the column width.
5269 * PARAMETER(S):
5270 * [I] infoPtr : valid pointer to the listview structure
5271 * [I] int : column index
5273 * RETURN:
5274 * SUCCESS : column width
5275 * FAILURE : zero
5277 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5279 INT nColumnWidth = 0;
5280 HDITEMW hdItem;
5282 TRACE("nColumn=%d\n", nColumn);
5284 /* we have a 'column' in LIST and REPORT mode only */
5285 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5287 case LVS_LIST:
5288 nColumnWidth = infoPtr->nItemWidth;
5289 break;
5290 case LVS_REPORT:
5291 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5292 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5293 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5295 * TODO: should we do the same in LVM_GETCOLUMN?
5297 hdItem.mask = HDI_WIDTH;
5298 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5300 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5301 return 0;
5303 nColumnWidth = hdItem.cxy;
5304 break;
5307 TRACE("nColumnWidth=%d\n", nColumnWidth);
5308 return nColumnWidth;
5311 /***
5312 * DESCRIPTION:
5313 * In list or report display mode, retrieves the number of items that can fit
5314 * vertically in the visible area. In icon or small icon display mode,
5315 * retrieves the total number of visible items.
5317 * PARAMETER(S):
5318 * [I] infoPtr : valid pointer to the listview structure
5320 * RETURN:
5321 * Number of fully visible items.
5323 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5325 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5327 case LVS_ICON:
5328 case LVS_SMALLICON:
5329 return infoPtr->nItemCount;
5330 case LVS_REPORT:
5331 return LISTVIEW_GetCountPerColumn(infoPtr);
5332 case LVS_LIST:
5333 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5335 assert(FALSE);
5336 return 0;
5339 /***
5340 * DESCRIPTION:
5341 * Retrieves an image list handle.
5343 * PARAMETER(S):
5344 * [I] infoPtr : valid pointer to the listview structure
5345 * [I] nImageList : image list identifier
5347 * RETURN:
5348 * SUCCESS : image list handle
5349 * FAILURE : NULL
5351 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5353 switch (nImageList)
5355 case LVSIL_NORMAL: return infoPtr->himlNormal;
5356 case LVSIL_SMALL: return infoPtr->himlSmall;
5357 case LVSIL_STATE: return infoPtr->himlState;
5359 return NULL;
5362 /* LISTVIEW_GetISearchString */
5364 /***
5365 * DESCRIPTION:
5366 * Retrieves item attributes.
5368 * PARAMETER(S):
5369 * [I] hwnd : window handle
5370 * [IO] lpLVItem : item info
5371 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5372 * if FALSE, then lpLVItem is a LPLVITEMA.
5374 * NOTE:
5375 * This is the internal 'GetItem' interface -- it tries to
5376 * be smart and avoid text copies, if possible, by modifying
5377 * lpLVItem->pszText to point to the text string. Please note
5378 * that this is not always possible (e.g. OWNERDATA), so on
5379 * entry you *must* supply valid values for pszText, and cchTextMax.
5380 * The only difference to the documented interface is that upon
5381 * return, you should use *only* the lpLVItem->pszText, rather than
5382 * the buffer pointer you provided on input. Most code already does
5383 * that, so it's not a problem.
5384 * For the two cases when the text must be copied (that is,
5385 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5387 * RETURN:
5388 * SUCCESS : TRUE
5389 * FAILURE : FALSE
5391 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5393 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5394 NMLVDISPINFOW dispInfo;
5395 ITEM_INFO *lpItem;
5396 ITEMHDR* pItemHdr;
5397 HDPA hdpaSubItems;
5398 INT isubitem;
5400 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5402 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5403 return FALSE;
5405 if (lpLVItem->mask == 0) return TRUE;
5407 /* make a local copy */
5408 isubitem = lpLVItem->iSubItem;
5410 /* a quick optimization if all we're asked is the focus state
5411 * these queries are worth optimising since they are common,
5412 * and can be answered in constant time, without the heavy accesses */
5413 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5414 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5416 lpLVItem->state = 0;
5417 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5418 lpLVItem->state |= LVIS_FOCUSED;
5419 return TRUE;
5422 ZeroMemory(&dispInfo, sizeof(dispInfo));
5424 /* if the app stores all the data, handle it separately */
5425 if (infoPtr->dwStyle & LVS_OWNERDATA)
5427 dispInfo.item.state = 0;
5429 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5430 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5432 /* NOTE: copy only fields which we _know_ are initialized, some apps
5433 * depend on the uninitialized fields being 0 */
5434 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5435 dispInfo.item.iItem = lpLVItem->iItem;
5436 dispInfo.item.iSubItem = isubitem;
5437 if (lpLVItem->mask & LVIF_TEXT)
5439 dispInfo.item.pszText = lpLVItem->pszText;
5440 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5442 if (lpLVItem->mask & LVIF_STATE)
5443 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5444 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5445 dispInfo.item.stateMask = lpLVItem->stateMask;
5446 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5448 /* full size structure expected - _WIN32IE >= 0x560 */
5449 *lpLVItem = dispInfo.item;
5451 else if (lpLVItem->mask & LVIF_INDENT)
5453 /* indent member expected - _WIN32IE >= 0x300 */
5454 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5456 else
5458 /* minimal structure expected */
5459 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5461 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5464 /* make sure lParam is zeroed out */
5465 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5467 /* we store only a little state, so if we're not asked, we're done */
5468 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5470 /* if focus is handled by us, report it */
5471 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5473 lpLVItem->state &= ~LVIS_FOCUSED;
5474 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5475 lpLVItem->state |= LVIS_FOCUSED;
5478 /* and do the same for selection, if we handle it */
5479 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5481 lpLVItem->state &= ~LVIS_SELECTED;
5482 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5483 lpLVItem->state |= LVIS_SELECTED;
5486 return TRUE;
5489 /* find the item and subitem structures before we proceed */
5490 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5491 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5492 assert (lpItem);
5494 if (isubitem)
5496 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5497 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5498 if (!lpSubItem)
5500 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5501 isubitem = 0;
5504 else
5505 pItemHdr = &lpItem->hdr;
5507 /* Do we need to query the state from the app? */
5508 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5510 dispInfo.item.mask |= LVIF_STATE;
5511 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5514 /* Do we need to enquire about the image? */
5515 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5516 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5518 dispInfo.item.mask |= LVIF_IMAGE;
5519 dispInfo.item.iImage = I_IMAGECALLBACK;
5522 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5523 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5525 dispInfo.item.mask |= LVIF_TEXT;
5526 dispInfo.item.pszText = lpLVItem->pszText;
5527 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5528 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5529 *dispInfo.item.pszText = '\0';
5532 /* If we don't have all the requested info, query the application */
5533 if (dispInfo.item.mask != 0)
5535 dispInfo.item.iItem = lpLVItem->iItem;
5536 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5537 dispInfo.item.lParam = lpItem->lParam;
5538 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5539 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5542 /* we should not store values for subitems */
5543 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5545 /* Now, handle the iImage field */
5546 if (dispInfo.item.mask & LVIF_IMAGE)
5548 lpLVItem->iImage = dispInfo.item.iImage;
5549 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5550 pItemHdr->iImage = dispInfo.item.iImage;
5552 else if (lpLVItem->mask & LVIF_IMAGE)
5554 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5555 lpLVItem->iImage = pItemHdr->iImage;
5556 else
5557 lpLVItem->iImage = 0;
5560 /* The pszText field */
5561 if (dispInfo.item.mask & LVIF_TEXT)
5563 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5564 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5566 lpLVItem->pszText = dispInfo.item.pszText;
5568 else if (lpLVItem->mask & LVIF_TEXT)
5570 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5571 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5574 /* Next is the lParam field */
5575 if (dispInfo.item.mask & LVIF_PARAM)
5577 lpLVItem->lParam = dispInfo.item.lParam;
5578 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5579 lpItem->lParam = dispInfo.item.lParam;
5581 else if (lpLVItem->mask & LVIF_PARAM)
5582 lpLVItem->lParam = lpItem->lParam;
5584 /* if this is a subitem, we're done */
5585 if (isubitem) return TRUE;
5587 /* ... the state field (this one is different due to uCallbackmask) */
5588 if (lpLVItem->mask & LVIF_STATE)
5590 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5591 if (dispInfo.item.mask & LVIF_STATE)
5593 lpLVItem->state &= ~dispInfo.item.stateMask;
5594 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5596 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5598 lpLVItem->state &= ~LVIS_FOCUSED;
5599 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5600 lpLVItem->state |= LVIS_FOCUSED;
5602 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5604 lpLVItem->state &= ~LVIS_SELECTED;
5605 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5606 lpLVItem->state |= LVIS_SELECTED;
5610 /* and last, but not least, the indent field */
5611 if (lpLVItem->mask & LVIF_INDENT)
5612 lpLVItem->iIndent = lpItem->iIndent;
5614 return TRUE;
5617 /***
5618 * DESCRIPTION:
5619 * Retrieves item attributes.
5621 * PARAMETER(S):
5622 * [I] hwnd : window handle
5623 * [IO] lpLVItem : item info
5624 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5625 * if FALSE, then lpLVItem is a LPLVITEMA.
5627 * NOTE:
5628 * This is the external 'GetItem' interface -- it properly copies
5629 * the text in the provided buffer.
5631 * RETURN:
5632 * SUCCESS : TRUE
5633 * FAILURE : FALSE
5635 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5637 LPWSTR pszText;
5638 BOOL bResult;
5640 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5641 return FALSE;
5643 pszText = lpLVItem->pszText;
5644 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5645 if (bResult && lpLVItem->pszText != pszText)
5646 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5647 lpLVItem->pszText = pszText;
5649 return bResult;
5653 /***
5654 * DESCRIPTION:
5655 * Retrieves the position (upper-left) of the listview control item.
5656 * Note that for LVS_ICON style, the upper-left is that of the icon
5657 * and not the bounding box.
5659 * PARAMETER(S):
5660 * [I] infoPtr : valid pointer to the listview structure
5661 * [I] nItem : item index
5662 * [O] lpptPosition : coordinate information
5664 * RETURN:
5665 * SUCCESS : TRUE
5666 * FAILURE : FALSE
5668 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5670 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5671 POINT Origin;
5673 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5675 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5677 LISTVIEW_GetOrigin(infoPtr, &Origin);
5678 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5680 if (uView == LVS_ICON)
5682 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5683 lpptPosition->y += ICON_TOP_PADDING;
5685 lpptPosition->x += Origin.x;
5686 lpptPosition->y += Origin.y;
5688 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5689 return TRUE;
5693 /***
5694 * DESCRIPTION:
5695 * Retrieves the bounding rectangle for a listview control item.
5697 * PARAMETER(S):
5698 * [I] infoPtr : valid pointer to the listview structure
5699 * [I] nItem : item index
5700 * [IO] lprc : bounding rectangle coordinates
5701 * lprc->left specifies the portion of the item for which the bounding
5702 * rectangle will be retrieved.
5704 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5705 * including the icon and label.
5707 * * For LVS_ICON
5708 * * Experiment shows that native control returns:
5709 * * width = min (48, length of text line)
5710 * * .left = position.x - (width - iconsize.cx)/2
5711 * * .right = .left + width
5712 * * height = #lines of text * ntmHeight + icon height + 8
5713 * * .top = position.y - 2
5714 * * .bottom = .top + height
5715 * * separation between items .y = itemSpacing.cy - height
5716 * * .x = itemSpacing.cx - width
5717 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5719 * * For LVS_ICON
5720 * * Experiment shows that native control returns:
5721 * * width = iconSize.cx + 16
5722 * * .left = position.x - (width - iconsize.cx)/2
5723 * * .right = .left + width
5724 * * height = iconSize.cy + 4
5725 * * .top = position.y - 2
5726 * * .bottom = .top + height
5727 * * separation between items .y = itemSpacing.cy - height
5728 * * .x = itemSpacing.cx - width
5729 * LVIR_LABEL Returns the bounding rectangle of the item text.
5731 * * For LVS_ICON
5732 * * Experiment shows that native control returns:
5733 * * width = text length
5734 * * .left = position.x - width/2
5735 * * .right = .left + width
5736 * * height = ntmH * linecount + 2
5737 * * .top = position.y + iconSize.cy + 6
5738 * * .bottom = .top + height
5739 * * separation between items .y = itemSpacing.cy - height
5740 * * .x = itemSpacing.cx - width
5741 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5742 * rectangles, but excludes columns in report view.
5744 * RETURN:
5745 * SUCCESS : TRUE
5746 * FAILURE : FALSE
5748 * NOTES
5749 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5750 * upon whether the window has the focus currently and on whether the item
5751 * is the one with the focus. Ensure that the control's record of which
5752 * item has the focus agrees with the items' records.
5754 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5756 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5757 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5758 BOOL doLabel = TRUE, oversizedBox = FALSE;
5759 POINT Position, Origin;
5760 LVITEMW lvItem;
5762 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5764 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5766 LISTVIEW_GetOrigin(infoPtr, &Origin);
5767 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5769 /* Be smart and try to figure out the minimum we have to do */
5770 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5771 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5772 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5773 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5774 oversizedBox = TRUE;
5776 /* get what we need from the item before hand, so we make
5777 * only one request. This can speed up things, if data
5778 * is stored on the app side */
5779 lvItem.mask = 0;
5780 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5781 if (doLabel) lvItem.mask |= LVIF_TEXT;
5782 lvItem.iItem = nItem;
5783 lvItem.iSubItem = 0;
5784 lvItem.pszText = szDispText;
5785 lvItem.cchTextMax = DISP_TEXT_SIZE;
5786 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5787 /* we got the state already up, simulate it here, to avoid a reget */
5788 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5790 lvItem.mask |= LVIF_STATE;
5791 lvItem.stateMask = LVIS_FOCUSED;
5792 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5795 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5796 lprc->left = LVIR_BOUNDS;
5797 switch(lprc->left)
5799 case LVIR_ICON:
5800 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5801 break;
5803 case LVIR_LABEL:
5804 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5805 break;
5807 case LVIR_BOUNDS:
5808 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5809 break;
5811 case LVIR_SELECTBOUNDS:
5812 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5813 break;
5815 default:
5816 WARN("Unknown value: %d\n", lprc->left);
5817 return FALSE;
5820 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5822 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5824 return TRUE;
5827 /***
5828 * DESCRIPTION:
5829 * Retrieves the spacing between listview control items.
5831 * PARAMETER(S):
5832 * [I] infoPtr : valid pointer to the listview structure
5833 * [IO] lprc : rectangle to receive the output
5834 * on input, lprc->top = nSubItem
5835 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5837 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5838 * not only those of the first column.
5839 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5841 * RETURN:
5842 * TRUE: success
5843 * FALSE: failure
5845 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5847 POINT Position;
5848 LVITEMW lvItem;
5849 INT nColumn;
5851 if (!lprc) return FALSE;
5853 nColumn = lprc->top;
5855 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5856 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5857 if (lprc->top == 0)
5858 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5860 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5862 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5864 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5866 lvItem.mask = 0;
5867 lvItem.iItem = nItem;
5868 lvItem.iSubItem = nColumn;
5870 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5871 switch(lprc->left)
5873 case LVIR_ICON:
5874 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5875 break;
5877 case LVIR_LABEL:
5878 case LVIR_BOUNDS:
5879 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5880 break;
5882 default:
5883 ERR("Unknown bounds=%d\n", lprc->left);
5884 return FALSE;
5887 OffsetRect(lprc, Position.x, Position.y);
5888 return TRUE;
5892 /***
5893 * DESCRIPTION:
5894 * Retrieves the width of a label.
5896 * PARAMETER(S):
5897 * [I] infoPtr : valid pointer to the listview structure
5899 * RETURN:
5900 * SUCCESS : string width (in pixels)
5901 * FAILURE : zero
5903 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5905 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5906 LVITEMW lvItem;
5908 TRACE("(nItem=%d)\n", nItem);
5910 lvItem.mask = LVIF_TEXT;
5911 lvItem.iItem = nItem;
5912 lvItem.iSubItem = 0;
5913 lvItem.pszText = szDispText;
5914 lvItem.cchTextMax = DISP_TEXT_SIZE;
5915 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5917 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5920 /***
5921 * DESCRIPTION:
5922 * Retrieves the spacing between listview control items.
5924 * PARAMETER(S):
5925 * [I] infoPtr : valid pointer to the listview structure
5926 * [I] bSmall : flag for small or large icon
5928 * RETURN:
5929 * Horizontal + vertical spacing
5931 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5933 LONG lResult;
5935 if (!bSmall)
5937 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5939 else
5941 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5942 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5943 else
5944 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5946 return lResult;
5949 /***
5950 * DESCRIPTION:
5951 * Retrieves the state of a listview control item.
5953 * PARAMETER(S):
5954 * [I] infoPtr : valid pointer to the listview structure
5955 * [I] nItem : item index
5956 * [I] uMask : state mask
5958 * RETURN:
5959 * State specified by the mask.
5961 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5963 LVITEMW lvItem;
5965 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5967 lvItem.iItem = nItem;
5968 lvItem.iSubItem = 0;
5969 lvItem.mask = LVIF_STATE;
5970 lvItem.stateMask = uMask;
5971 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5973 return lvItem.state & uMask;
5976 /***
5977 * DESCRIPTION:
5978 * Retrieves the text of a listview control item or subitem.
5980 * PARAMETER(S):
5981 * [I] hwnd : window handle
5982 * [I] nItem : item index
5983 * [IO] lpLVItem : item information
5984 * [I] isW : TRUE if lpLVItem is Unicode
5986 * RETURN:
5987 * SUCCESS : string length
5988 * FAILURE : 0
5990 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5992 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5994 lpLVItem->mask = LVIF_TEXT;
5995 lpLVItem->iItem = nItem;
5996 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5998 return textlenT(lpLVItem->pszText, isW);
6001 /***
6002 * DESCRIPTION:
6003 * Searches for an item based on properties + relationships.
6005 * PARAMETER(S):
6006 * [I] infoPtr : valid pointer to the listview structure
6007 * [I] nItem : item index
6008 * [I] uFlags : relationship flag
6010 * RETURN:
6011 * SUCCESS : item index
6012 * FAILURE : -1
6014 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6016 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6017 UINT uMask = 0;
6018 LVFINDINFOW lvFindInfo;
6019 INT nCountPerColumn;
6020 INT nCountPerRow;
6021 INT i;
6023 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6024 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6026 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6028 if (uFlags & LVNI_CUT)
6029 uMask |= LVIS_CUT;
6031 if (uFlags & LVNI_DROPHILITED)
6032 uMask |= LVIS_DROPHILITED;
6034 if (uFlags & LVNI_FOCUSED)
6035 uMask |= LVIS_FOCUSED;
6037 if (uFlags & LVNI_SELECTED)
6038 uMask |= LVIS_SELECTED;
6040 /* if we're asked for the focused item, that's only one,
6041 * so it's worth optimizing */
6042 if (uFlags & LVNI_FOCUSED)
6044 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6045 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6048 if (uFlags & LVNI_ABOVE)
6050 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6052 while (nItem >= 0)
6054 nItem--;
6055 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6056 return nItem;
6059 else
6061 /* Special case for autoarrange - move 'til the top of a list */
6062 if (is_autoarrange(infoPtr))
6064 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6065 while (nItem - nCountPerRow >= 0)
6067 nItem -= nCountPerRow;
6068 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6069 return nItem;
6071 return -1;
6073 lvFindInfo.flags = LVFI_NEARESTXY;
6074 lvFindInfo.vkDirection = VK_UP;
6075 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6076 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6078 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6079 return nItem;
6083 else if (uFlags & LVNI_BELOW)
6085 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6087 while (nItem < infoPtr->nItemCount)
6089 nItem++;
6090 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6091 return nItem;
6094 else
6096 /* Special case for autoarrange - move 'til the bottom of a list */
6097 if (is_autoarrange(infoPtr))
6099 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6100 while (nItem + nCountPerRow < infoPtr->nItemCount )
6102 nItem += nCountPerRow;
6103 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6104 return nItem;
6106 return -1;
6108 lvFindInfo.flags = LVFI_NEARESTXY;
6109 lvFindInfo.vkDirection = VK_DOWN;
6110 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6111 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6113 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6114 return nItem;
6118 else if (uFlags & LVNI_TOLEFT)
6120 if (uView == LVS_LIST)
6122 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6123 while (nItem - nCountPerColumn >= 0)
6125 nItem -= nCountPerColumn;
6126 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6127 return nItem;
6130 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6132 /* Special case for autoarrange - move 'ti the beginning of a row */
6133 if (is_autoarrange(infoPtr))
6135 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6136 while (nItem % nCountPerRow > 0)
6138 nItem --;
6139 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6140 return nItem;
6142 return -1;
6144 lvFindInfo.flags = LVFI_NEARESTXY;
6145 lvFindInfo.vkDirection = VK_LEFT;
6146 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6147 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6149 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6150 return nItem;
6154 else if (uFlags & LVNI_TORIGHT)
6156 if (uView == LVS_LIST)
6158 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6159 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6161 nItem += nCountPerColumn;
6162 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6163 return nItem;
6166 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6168 /* Special case for autoarrange - move 'til the end of a row */
6169 if (is_autoarrange(infoPtr))
6171 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6172 while (nItem % nCountPerRow < nCountPerRow - 1 )
6174 nItem ++;
6175 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6176 return nItem;
6178 return -1;
6180 lvFindInfo.flags = LVFI_NEARESTXY;
6181 lvFindInfo.vkDirection = VK_RIGHT;
6182 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6183 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6185 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6186 return nItem;
6190 else
6192 nItem++;
6194 /* search by index */
6195 for (i = nItem; i < infoPtr->nItemCount; i++)
6197 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6198 return i;
6202 return -1;
6205 /* LISTVIEW_GetNumberOfWorkAreas */
6207 /***
6208 * DESCRIPTION:
6209 * Retrieves the origin coordinates when in icon or small icon display mode.
6211 * PARAMETER(S):
6212 * [I] infoPtr : valid pointer to the listview structure
6213 * [O] lpptOrigin : coordinate information
6215 * RETURN:
6216 * None.
6218 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6220 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6221 INT nHorzPos = 0, nVertPos = 0;
6222 SCROLLINFO scrollInfo;
6224 scrollInfo.cbSize = sizeof(SCROLLINFO);
6225 scrollInfo.fMask = SIF_POS;
6227 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6228 nHorzPos = scrollInfo.nPos;
6229 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6230 nVertPos = scrollInfo.nPos;
6232 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6234 lpptOrigin->x = infoPtr->rcList.left;
6235 lpptOrigin->y = infoPtr->rcList.top;
6236 if (uView == LVS_LIST)
6237 nHorzPos *= infoPtr->nItemWidth;
6238 else if (uView == LVS_REPORT)
6239 nVertPos *= infoPtr->nItemHeight;
6241 lpptOrigin->x -= nHorzPos;
6242 lpptOrigin->y -= nVertPos;
6244 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6247 /***
6248 * DESCRIPTION:
6249 * Retrieves the width of a string.
6251 * PARAMETER(S):
6252 * [I] hwnd : window handle
6253 * [I] lpszText : text string to process
6254 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6256 * RETURN:
6257 * SUCCESS : string width (in pixels)
6258 * FAILURE : zero
6260 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6262 SIZE stringSize;
6264 stringSize.cx = 0;
6265 if (is_textT(lpszText, isW))
6267 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6268 HDC hdc = GetDC(infoPtr->hwndSelf);
6269 HFONT hOldFont = SelectObject(hdc, hFont);
6271 if (isW)
6272 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6273 else
6274 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6275 SelectObject(hdc, hOldFont);
6276 ReleaseDC(infoPtr->hwndSelf, hdc);
6278 return stringSize.cx;
6281 /***
6282 * DESCRIPTION:
6283 * Determines which listview item is located at the specified position.
6285 * PARAMETER(S):
6286 * [I] infoPtr : valid pointer to the listview structure
6287 * [IO] lpht : hit test information
6288 * [I] subitem : fill out iSubItem.
6289 * [I] select : return the index only if the hit selects the item
6291 * NOTE:
6292 * (mm 20001022): We must not allow iSubItem to be touched, for
6293 * an app might pass only a structure with space up to iItem!
6294 * (MS Office 97 does that for instance in the file open dialog)
6296 * RETURN:
6297 * SUCCESS : item index
6298 * FAILURE : -1
6300 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6302 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6303 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6304 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6305 POINT Origin, Position, opt;
6306 LVITEMW lvItem;
6307 ITERATOR i;
6308 INT iItem;
6310 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6312 lpht->flags = 0;
6313 lpht->iItem = -1;
6314 if (subitem) lpht->iSubItem = 0;
6316 if (infoPtr->rcList.left > lpht->pt.x)
6317 lpht->flags |= LVHT_TOLEFT;
6318 else if (infoPtr->rcList.right < lpht->pt.x)
6319 lpht->flags |= LVHT_TORIGHT;
6321 if (infoPtr->rcList.top > lpht->pt.y)
6322 lpht->flags |= LVHT_ABOVE;
6323 else if (infoPtr->rcList.bottom < lpht->pt.y)
6324 lpht->flags |= LVHT_BELOW;
6326 TRACE("lpht->flags=0x%x\n", lpht->flags);
6327 if (lpht->flags) return -1;
6329 lpht->flags |= LVHT_NOWHERE;
6331 LISTVIEW_GetOrigin(infoPtr, &Origin);
6333 /* first deal with the large items */
6334 rcSearch.left = lpht->pt.x;
6335 rcSearch.top = lpht->pt.y;
6336 rcSearch.right = rcSearch.left + 1;
6337 rcSearch.bottom = rcSearch.top + 1;
6339 iterator_frameditems(&i, infoPtr, &rcSearch);
6340 iterator_next(&i); /* go to first item in the sequence */
6341 iItem = i.nItem;
6342 iterator_destroy(&i);
6344 TRACE("lpht->iItem=%d\n", iItem);
6345 if (iItem == -1) return -1;
6347 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6348 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6349 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6350 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6351 lvItem.iItem = iItem;
6352 lvItem.iSubItem = 0;
6353 lvItem.pszText = szDispText;
6354 lvItem.cchTextMax = DISP_TEXT_SIZE;
6355 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6356 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6358 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6359 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6360 opt.x = lpht->pt.x - Position.x - Origin.x;
6361 opt.y = lpht->pt.y - Position.y - Origin.y;
6363 if (uView == LVS_REPORT)
6364 rcBounds = rcBox;
6365 else
6367 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6368 UnionRect(&rcBounds, &rcBounds, &rcState);
6370 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6371 if (!PtInRect(&rcBounds, opt)) return -1;
6373 if (PtInRect(&rcIcon, opt))
6374 lpht->flags |= LVHT_ONITEMICON;
6375 else if (PtInRect(&rcLabel, opt))
6376 lpht->flags |= LVHT_ONITEMLABEL;
6377 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6378 lpht->flags |= LVHT_ONITEMSTATEICON;
6379 if (lpht->flags & LVHT_ONITEM)
6380 lpht->flags &= ~LVHT_NOWHERE;
6382 TRACE("lpht->flags=0x%x\n", lpht->flags);
6383 if (uView == LVS_REPORT && subitem)
6385 INT j;
6387 rcBounds.right = rcBounds.left;
6388 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6390 rcBounds.left = rcBounds.right;
6391 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6392 if (PtInRect(&rcBounds, opt))
6394 lpht->iSubItem = j;
6395 break;
6400 if (select && !(uView == LVS_REPORT &&
6401 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6402 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6404 if (uView == LVS_REPORT)
6406 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6407 UnionRect(&rcBounds, &rcBounds, &rcState);
6409 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6411 return lpht->iItem = iItem;
6415 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6416 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6417 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6418 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6419 their own sort proc. when sending LVM_SORTITEMS.
6421 /* Platform SDK:
6422 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6424 LVS_SORTXXX must be specified,
6425 LVS_OWNERDRAW is not set,
6426 <item>.pszText is not LPSTR_TEXTCALLBACK.
6428 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6429 are sorted based on item text..."
6431 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6433 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6434 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6435 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6437 /* if we're sorting descending, negate the return value */
6438 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6441 /***
6442 * DESCRIPTION:
6443 * Inserts a new item in the listview control.
6445 * PARAMETER(S):
6446 * [I] infoPtr : valid pointer to the listview structure
6447 * [I] lpLVItem : item information
6448 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6450 * RETURN:
6451 * SUCCESS : new item index
6452 * FAILURE : -1
6454 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6456 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6457 INT nItem;
6458 HDPA hdpaSubItems;
6459 NMLISTVIEW nmlv;
6460 ITEM_INFO *lpItem;
6461 BOOL is_sorted, has_changed;
6462 LVITEMW item;
6463 HWND hwndSelf = infoPtr->hwndSelf;
6465 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6467 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6469 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6470 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6472 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6474 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6476 /* insert item in listview control data structure */
6477 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6478 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6480 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6481 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6483 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6485 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6486 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6487 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6488 if (nItem == -1) goto fail;
6489 infoPtr->nItemCount++;
6491 /* shift indices first so they don't get tangled */
6492 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6494 /* set the item attributes */
6495 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6497 /* full size structure expected - _WIN32IE >= 0x560 */
6498 item = *lpLVItem;
6500 else if (lpLVItem->mask & LVIF_INDENT)
6502 /* indent member expected - _WIN32IE >= 0x300 */
6503 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6505 else
6507 /* minimal structure expected */
6508 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6510 item.iItem = nItem;
6511 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6513 item.mask |= LVIF_STATE;
6514 item.stateMask |= LVIS_STATEIMAGEMASK;
6515 item.state &= ~LVIS_STATEIMAGEMASK;
6516 item.state |= INDEXTOSTATEIMAGEMASK(1);
6518 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6520 /* if we're sorted, sort the list, and update the index */
6521 if (is_sorted)
6523 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6524 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6525 assert(nItem != -1);
6528 /* make room for the position, if we are in the right mode */
6529 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6531 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6532 goto undo;
6533 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6535 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6536 goto undo;
6540 /* send LVN_INSERTITEM notification */
6541 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6542 nmlv.iItem = nItem;
6543 nmlv.lParam = lpItem->lParam;
6544 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6545 if (!IsWindow(hwndSelf))
6546 return -1;
6548 /* align items (set position of each item) */
6549 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6551 POINT pt;
6553 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6554 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6555 else
6556 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6558 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6561 /* now is the invalidation fun */
6562 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6563 return nItem;
6565 undo:
6566 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6567 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6568 infoPtr->nItemCount--;
6569 fail:
6570 DPA_DeletePtr(hdpaSubItems, 0);
6571 DPA_Destroy (hdpaSubItems);
6572 Free (lpItem);
6573 return -1;
6576 /***
6577 * DESCRIPTION:
6578 * Redraws a range of items.
6580 * PARAMETER(S):
6581 * [I] infoPtr : valid pointer to the listview structure
6582 * [I] nFirst : first item
6583 * [I] nLast : last item
6585 * RETURN:
6586 * SUCCESS : TRUE
6587 * FAILURE : FALSE
6589 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6591 INT i;
6593 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6594 max(nFirst, nLast) >= infoPtr->nItemCount)
6595 return FALSE;
6597 for (i = nFirst; i <= nLast; i++)
6598 LISTVIEW_InvalidateItem(infoPtr, i);
6600 return TRUE;
6603 /***
6604 * DESCRIPTION:
6605 * Scroll the content of a listview.
6607 * PARAMETER(S):
6608 * [I] infoPtr : valid pointer to the listview structure
6609 * [I] dx : horizontal scroll amount in pixels
6610 * [I] dy : vertical scroll amount in pixels
6612 * RETURN:
6613 * SUCCESS : TRUE
6614 * FAILURE : FALSE
6616 * COMMENTS:
6617 * If the control is in report mode (LVS_REPORT) the control can
6618 * be scrolled only in line increments. "dy" will be rounded to the
6619 * nearest number of pixels that are a whole line. Ex: if line height
6620 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6621 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6623 * For: (per experimentation with native control and CSpy ListView)
6624 * LVS_ICON dy=1 = 1 pixel (vertical only)
6625 * dx ignored
6626 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6627 * dx ignored
6628 * LVS_LIST dx=1 = 1 column (horizontal only)
6629 * but will only scroll 1 column per message
6630 * no matter what the value.
6631 * dy must be 0 or FALSE returned.
6632 * LVS_REPORT dx=1 = 1 pixel
6633 * dy= see above
6636 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6638 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6639 case LVS_REPORT:
6640 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6641 dy /= infoPtr->nItemHeight;
6642 break;
6643 case LVS_LIST:
6644 if (dy != 0) return FALSE;
6645 break;
6646 default: /* icon */
6647 dx = 0;
6648 break;
6651 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6652 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6654 return TRUE;
6657 /***
6658 * DESCRIPTION:
6659 * Sets the background color.
6661 * PARAMETER(S):
6662 * [I] infoPtr : valid pointer to the listview structure
6663 * [I] clrBk : background color
6665 * RETURN:
6666 * SUCCESS : TRUE
6667 * FAILURE : FALSE
6669 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6671 TRACE("(clrBk=%x)\n", clrBk);
6673 if(infoPtr->clrBk != clrBk) {
6674 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6675 infoPtr->clrBk = clrBk;
6676 if (clrBk == CLR_NONE)
6677 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6678 else
6679 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6680 LISTVIEW_InvalidateList(infoPtr);
6683 return TRUE;
6686 /* LISTVIEW_SetBkImage */
6688 /*** Helper for {Insert,Set}ColumnT *only* */
6689 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6690 const LVCOLUMNW *lpColumn, BOOL isW)
6692 if (lpColumn->mask & LVCF_FMT)
6694 /* format member is valid */
6695 lphdi->mask |= HDI_FORMAT;
6697 /* set text alignment (leftmost column must be left-aligned) */
6698 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6699 lphdi->fmt |= HDF_LEFT;
6700 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6701 lphdi->fmt |= HDF_RIGHT;
6702 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6703 lphdi->fmt |= HDF_CENTER;
6705 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6706 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6708 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6710 lphdi->fmt |= HDF_IMAGE;
6711 lphdi->iImage = I_IMAGECALLBACK;
6715 if (lpColumn->mask & LVCF_WIDTH)
6717 lphdi->mask |= HDI_WIDTH;
6718 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6720 /* make it fill the remainder of the controls width */
6721 RECT rcHeader;
6722 INT item_index;
6724 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6726 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6727 lphdi->cxy += rcHeader.right - rcHeader.left;
6730 /* retrieve the layout of the header */
6731 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6732 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6734 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6736 else
6737 lphdi->cxy = lpColumn->cx;
6740 if (lpColumn->mask & LVCF_TEXT)
6742 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6743 lphdi->fmt |= HDF_STRING;
6744 lphdi->pszText = lpColumn->pszText;
6745 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6748 if (lpColumn->mask & LVCF_IMAGE)
6750 lphdi->mask |= HDI_IMAGE;
6751 lphdi->iImage = lpColumn->iImage;
6754 if (lpColumn->mask & LVCF_ORDER)
6756 lphdi->mask |= HDI_ORDER;
6757 lphdi->iOrder = lpColumn->iOrder;
6762 /***
6763 * DESCRIPTION:
6764 * Inserts a new column.
6766 * PARAMETER(S):
6767 * [I] infoPtr : valid pointer to the listview structure
6768 * [I] nColumn : column index
6769 * [I] lpColumn : column information
6770 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6772 * RETURN:
6773 * SUCCESS : new column index
6774 * FAILURE : -1
6776 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6777 const LVCOLUMNW *lpColumn, BOOL isW)
6779 COLUMN_INFO *lpColumnInfo;
6780 INT nNewColumn;
6781 HDITEMW hdi;
6783 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6785 if (!lpColumn || nColumn < 0) return -1;
6786 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6788 ZeroMemory(&hdi, sizeof(HDITEMW));
6789 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6792 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6793 * (can be seen in SPY) otherwise column never gets added.
6795 if (!(lpColumn->mask & LVCF_WIDTH)) {
6796 hdi.mask |= HDI_WIDTH;
6797 hdi.cxy = 10;
6801 * when the iSubItem is available Windows copies it to the header lParam. It seems
6802 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6804 if (lpColumn->mask & LVCF_SUBITEM)
6806 hdi.mask |= HDI_LPARAM;
6807 hdi.lParam = lpColumn->iSubItem;
6810 /* insert item in header control */
6811 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6812 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6813 (WPARAM)nColumn, (LPARAM)&hdi);
6814 if (nNewColumn == -1) return -1;
6815 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6817 /* create our own column info */
6818 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6819 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6821 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6822 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6824 /* now we have to actually adjust the data */
6825 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6827 SUBITEM_INFO *lpSubItem;
6828 HDPA hdpaSubItems;
6829 INT nItem, i;
6831 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6833 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6834 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6836 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6837 if (lpSubItem->iSubItem >= nNewColumn)
6838 lpSubItem->iSubItem++;
6843 /* make space for the new column */
6844 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6845 LISTVIEW_UpdateItemSize(infoPtr);
6847 return nNewColumn;
6849 fail:
6850 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6851 if (lpColumnInfo)
6853 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6854 Free(lpColumnInfo);
6856 return -1;
6859 /***
6860 * DESCRIPTION:
6861 * Sets the attributes of a header item.
6863 * PARAMETER(S):
6864 * [I] infoPtr : valid pointer to the listview structure
6865 * [I] nColumn : column index
6866 * [I] lpColumn : column attributes
6867 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6869 * RETURN:
6870 * SUCCESS : TRUE
6871 * FAILURE : FALSE
6873 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6874 const LVCOLUMNW *lpColumn, BOOL isW)
6876 HDITEMW hdi, hdiget;
6877 BOOL bResult;
6879 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6881 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6883 ZeroMemory(&hdi, sizeof(HDITEMW));
6884 if (lpColumn->mask & LVCF_FMT)
6886 hdi.mask |= HDI_FORMAT;
6887 hdiget.mask = HDI_FORMAT;
6888 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6889 hdi.fmt = hdiget.fmt & HDF_STRING;
6891 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6893 /* set header item attributes */
6894 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6895 if (!bResult) return FALSE;
6897 if (lpColumn->mask & LVCF_FMT)
6899 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6900 int oldFmt = lpColumnInfo->fmt;
6902 lpColumnInfo->fmt = lpColumn->fmt;
6903 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6905 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6906 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6910 return TRUE;
6913 /***
6914 * DESCRIPTION:
6915 * Sets the column order array
6917 * PARAMETERS:
6918 * [I] infoPtr : valid pointer to the listview structure
6919 * [I] iCount : number of elements in column order array
6920 * [I] lpiArray : pointer to column order array
6922 * RETURN:
6923 * SUCCESS : TRUE
6924 * FAILURE : FALSE
6926 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6928 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6930 if (!lpiArray)
6931 return FALSE;
6933 return TRUE;
6937 /***
6938 * DESCRIPTION:
6939 * Sets the width of a column
6941 * PARAMETERS:
6942 * [I] infoPtr : valid pointer to the listview structure
6943 * [I] nColumn : column index
6944 * [I] cx : column width
6946 * RETURN:
6947 * SUCCESS : TRUE
6948 * FAILURE : FALSE
6950 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6952 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6953 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6954 INT max_cx = 0;
6955 HDITEMW hdi;
6957 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6959 /* set column width only if in report or list mode */
6960 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6962 /* take care of invalid cx values */
6963 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6964 else if (uView == LVS_LIST && cx < 1) return FALSE;
6966 /* resize all columns if in LVS_LIST mode */
6967 if(uView == LVS_LIST)
6969 infoPtr->nItemWidth = cx;
6970 LISTVIEW_InvalidateList(infoPtr);
6971 return TRUE;
6974 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6976 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6978 INT nLabelWidth;
6979 LVITEMW lvItem;
6981 lvItem.mask = LVIF_TEXT;
6982 lvItem.iItem = 0;
6983 lvItem.iSubItem = nColumn;
6984 lvItem.pszText = szDispText;
6985 lvItem.cchTextMax = DISP_TEXT_SIZE;
6986 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6988 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6989 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6990 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6992 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6993 max_cx += infoPtr->iconSize.cx;
6994 max_cx += TRAILING_LABEL_PADDING;
6997 /* autosize based on listview items width */
6998 if(cx == LVSCW_AUTOSIZE)
6999 cx = max_cx;
7000 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7002 /* if iCol is the last column make it fill the remainder of the controls width */
7003 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7005 RECT rcHeader;
7006 POINT Origin;
7008 LISTVIEW_GetOrigin(infoPtr, &Origin);
7009 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7011 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7013 else
7015 /* Despite what the MS docs say, if this is not the last
7016 column, then MS resizes the column to the width of the
7017 largest text string in the column, including headers
7018 and items. This is different from LVSCW_AUTOSIZE in that
7019 LVSCW_AUTOSIZE ignores the header string length. */
7020 cx = 0;
7022 /* retrieve header text */
7023 hdi.mask = HDI_TEXT;
7024 hdi.cchTextMax = DISP_TEXT_SIZE;
7025 hdi.pszText = szDispText;
7026 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7028 HDC hdc = GetDC(infoPtr->hwndSelf);
7029 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7030 SIZE size;
7032 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7033 cx = size.cx + TRAILING_HEADER_PADDING;
7034 /* FIXME: Take into account the header image, if one is present */
7035 SelectObject(hdc, old_font);
7036 ReleaseDC(infoPtr->hwndSelf, hdc);
7038 cx = max (cx, max_cx);
7042 if (cx < 0) return FALSE;
7044 /* call header to update the column change */
7045 hdi.mask = HDI_WIDTH;
7046 hdi.cxy = cx;
7047 TRACE("hdi.cxy=%d\n", hdi.cxy);
7048 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7051 /***
7052 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7055 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7057 HDC hdc_wnd, hdc;
7058 HBITMAP hbm_im, hbm_mask, hbm_orig;
7059 RECT rc;
7060 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7061 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7062 HIMAGELIST himl;
7064 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7065 ILC_COLOR | ILC_MASK, 2, 2);
7066 hdc_wnd = GetDC(infoPtr->hwndSelf);
7067 hdc = CreateCompatibleDC(hdc_wnd);
7068 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7069 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7070 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7072 rc.left = rc.top = 0;
7073 rc.right = GetSystemMetrics(SM_CXSMICON);
7074 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7076 hbm_orig = SelectObject(hdc, hbm_mask);
7077 FillRect(hdc, &rc, hbr_white);
7078 InflateRect(&rc, -3, -3);
7079 FillRect(hdc, &rc, hbr_black);
7081 SelectObject(hdc, hbm_im);
7082 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7083 SelectObject(hdc, hbm_orig);
7084 ImageList_Add(himl, hbm_im, hbm_mask);
7086 SelectObject(hdc, hbm_im);
7087 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7088 SelectObject(hdc, hbm_orig);
7089 ImageList_Add(himl, hbm_im, hbm_mask);
7091 DeleteObject(hbm_mask);
7092 DeleteObject(hbm_im);
7093 DeleteDC(hdc);
7095 return himl;
7098 /***
7099 * DESCRIPTION:
7100 * Sets the extended listview style.
7102 * PARAMETERS:
7103 * [I] infoPtr : valid pointer to the listview structure
7104 * [I] dwMask : mask
7105 * [I] dwStyle : style
7107 * RETURN:
7108 * SUCCESS : previous style
7109 * FAILURE : 0
7111 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7113 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7115 /* set new style */
7116 if (dwMask)
7117 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7118 else
7119 infoPtr->dwLvExStyle = dwExStyle;
7121 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7123 HIMAGELIST himl = 0;
7124 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7126 LVITEMW item;
7127 item.mask = LVIF_STATE;
7128 item.stateMask = LVIS_STATEIMAGEMASK;
7129 item.state = INDEXTOSTATEIMAGEMASK(1);
7130 LISTVIEW_SetItemState(infoPtr, -1, &item);
7132 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7134 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7137 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7139 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7140 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7141 dwStyle |= HDS_DRAGDROP;
7142 else
7143 dwStyle &= ~HDS_DRAGDROP;
7144 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7147 /* GRIDLINES adds decoration at top so changes sizes */
7148 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7150 LISTVIEW_UpdateSize(infoPtr);
7154 LISTVIEW_InvalidateList(infoPtr);
7155 return dwOldExStyle;
7158 /***
7159 * DESCRIPTION:
7160 * Sets the new hot cursor used during hot tracking and hover selection.
7162 * PARAMETER(S):
7163 * [I] infoPtr : valid pointer to the listview structure
7164 * [I] hCursor : the new hot cursor handle
7166 * RETURN:
7167 * Returns the previous hot cursor
7169 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7171 HCURSOR oldCursor = infoPtr->hHotCursor;
7173 infoPtr->hHotCursor = hCursor;
7175 return oldCursor;
7179 /***
7180 * DESCRIPTION:
7181 * Sets the hot item index.
7183 * PARAMETERS:
7184 * [I] infoPtr : valid pointer to the listview structure
7185 * [I] iIndex : index
7187 * RETURN:
7188 * SUCCESS : previous hot item index
7189 * FAILURE : -1 (no hot item)
7191 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7193 INT iOldIndex = infoPtr->nHotItem;
7195 infoPtr->nHotItem = iIndex;
7197 return iOldIndex;
7201 /***
7202 * DESCRIPTION:
7203 * Sets the amount of time the cursor must hover over an item before it is selected.
7205 * PARAMETER(S):
7206 * [I] infoPtr : valid pointer to the listview structure
7207 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7209 * RETURN:
7210 * Returns the previous hover time
7212 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7214 DWORD oldHoverTime = infoPtr->dwHoverTime;
7216 infoPtr->dwHoverTime = dwHoverTime;
7218 return oldHoverTime;
7221 /***
7222 * DESCRIPTION:
7223 * Sets spacing for icons of LVS_ICON style.
7225 * PARAMETER(S):
7226 * [I] infoPtr : valid pointer to the listview structure
7227 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7228 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7230 * RETURN:
7231 * MAKELONG(oldcx, oldcy)
7233 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7235 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7236 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7238 TRACE("requested=(%d,%d)\n", cx, cy);
7240 /* this is supported only for LVS_ICON style */
7241 if (uView != LVS_ICON) return oldspacing;
7243 /* set to defaults, if instructed to */
7244 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7245 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7247 /* if 0 then compute width
7248 * FIXME: Should scan each item and determine max width of
7249 * icon or label, then make that the width */
7250 if (cx == 0)
7251 cx = infoPtr->iconSpacing.cx;
7253 /* if 0 then compute height */
7254 if (cy == 0)
7255 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7256 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7259 infoPtr->iconSpacing.cx = cx;
7260 infoPtr->iconSpacing.cy = cy;
7262 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7263 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7264 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7265 infoPtr->ntmHeight);
7267 /* these depend on the iconSpacing */
7268 LISTVIEW_UpdateItemSize(infoPtr);
7270 return oldspacing;
7273 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7275 INT cx, cy;
7277 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7279 size->cx = cx;
7280 size->cy = cy;
7282 else
7284 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7285 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7289 /***
7290 * DESCRIPTION:
7291 * Sets image lists.
7293 * PARAMETER(S):
7294 * [I] infoPtr : valid pointer to the listview structure
7295 * [I] nType : image list type
7296 * [I] himl : image list handle
7298 * RETURN:
7299 * SUCCESS : old image list
7300 * FAILURE : NULL
7302 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7304 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7305 INT oldHeight = infoPtr->nItemHeight;
7306 HIMAGELIST himlOld = 0;
7308 TRACE("(nType=%d, himl=%p\n", nType, himl);
7310 switch (nType)
7312 case LVSIL_NORMAL:
7313 himlOld = infoPtr->himlNormal;
7314 infoPtr->himlNormal = himl;
7315 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7316 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7317 break;
7319 case LVSIL_SMALL:
7320 himlOld = infoPtr->himlSmall;
7321 infoPtr->himlSmall = himl;
7322 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7323 break;
7325 case LVSIL_STATE:
7326 himlOld = infoPtr->himlState;
7327 infoPtr->himlState = himl;
7328 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7329 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7330 break;
7332 default:
7333 ERR("Unknown icon type=%d\n", nType);
7334 return NULL;
7337 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7338 if (infoPtr->nItemHeight != oldHeight)
7339 LISTVIEW_UpdateScroll(infoPtr);
7341 return himlOld;
7344 /***
7345 * DESCRIPTION:
7346 * Preallocates memory (does *not* set the actual count of items !)
7348 * PARAMETER(S):
7349 * [I] infoPtr : valid pointer to the listview structure
7350 * [I] nItems : item count (projected number of items to allocate)
7351 * [I] dwFlags : update flags
7353 * RETURN:
7354 * SUCCESS : TRUE
7355 * FAILURE : FALSE
7357 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7359 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7361 if (infoPtr->dwStyle & LVS_OWNERDATA)
7363 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7364 INT nOldCount = infoPtr->nItemCount;
7366 if (nItems < nOldCount)
7368 RANGE range = { nItems, nOldCount };
7369 ranges_del(infoPtr->selectionRanges, range);
7370 if (infoPtr->nFocusedItem >= nItems)
7372 infoPtr->nFocusedItem = -1;
7373 SetRectEmpty(&infoPtr->rcFocus);
7377 infoPtr->nItemCount = nItems;
7378 LISTVIEW_UpdateScroll(infoPtr);
7380 /* the flags are valid only in ownerdata report and list modes */
7381 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7383 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7384 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7386 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7387 LISTVIEW_InvalidateList(infoPtr);
7388 else
7390 INT nFrom, nTo;
7391 POINT Origin;
7392 RECT rcErase;
7394 LISTVIEW_GetOrigin(infoPtr, &Origin);
7395 nFrom = min(nOldCount, nItems);
7396 nTo = max(nOldCount, nItems);
7398 if (uView == LVS_REPORT)
7400 rcErase.left = 0;
7401 rcErase.top = nFrom * infoPtr->nItemHeight;
7402 rcErase.right = infoPtr->nItemWidth;
7403 rcErase.bottom = nTo * infoPtr->nItemHeight;
7404 OffsetRect(&rcErase, Origin.x, Origin.y);
7405 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7406 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7408 else /* LVS_LIST */
7410 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7412 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7413 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7414 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7415 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7416 OffsetRect(&rcErase, Origin.x, Origin.y);
7417 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7418 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7420 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7421 rcErase.top = 0;
7422 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7423 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7424 OffsetRect(&rcErase, Origin.x, Origin.y);
7425 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7426 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7430 else
7432 /* According to MSDN for non-LVS_OWNERDATA this is just
7433 * a performance issue. The control allocates its internal
7434 * data structures for the number of items specified. It
7435 * cuts down on the number of memory allocations. Therefore
7436 * we will just issue a WARN here
7438 WARN("for non-ownerdata performance option not implemented.\n");
7441 return TRUE;
7444 /***
7445 * DESCRIPTION:
7446 * Sets the position of an item.
7448 * PARAMETER(S):
7449 * [I] infoPtr : valid pointer to the listview structure
7450 * [I] nItem : item index
7451 * [I] pt : coordinate
7453 * RETURN:
7454 * SUCCESS : TRUE
7455 * FAILURE : FALSE
7457 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7459 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7460 POINT Origin;
7462 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7464 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7465 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7467 LISTVIEW_GetOrigin(infoPtr, &Origin);
7469 /* This point value seems to be an undocumented feature.
7470 * The best guess is that it means either at the origin,
7471 * or at true beginning of the list. I will assume the origin. */
7472 if ((pt.x == -1) && (pt.y == -1))
7473 pt = Origin;
7475 if (uView == LVS_ICON)
7477 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7478 pt.y -= ICON_TOP_PADDING;
7480 pt.x -= Origin.x;
7481 pt.y -= Origin.y;
7483 infoPtr->bAutoarrange = FALSE;
7485 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7488 /***
7489 * DESCRIPTION:
7490 * Sets the state of one or many items.
7492 * PARAMETER(S):
7493 * [I] infoPtr : valid pointer to the listview structure
7494 * [I] nItem : item index
7495 * [I] lpLVItem : item or subitem info
7497 * RETURN:
7498 * SUCCESS : TRUE
7499 * FAILURE : FALSE
7501 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7503 BOOL bResult = TRUE;
7504 LVITEMW lvItem;
7506 lvItem.iItem = nItem;
7507 lvItem.iSubItem = 0;
7508 lvItem.mask = LVIF_STATE;
7509 lvItem.state = lpLVItem->state;
7510 lvItem.stateMask = lpLVItem->stateMask;
7511 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7513 if (nItem == -1)
7515 /* apply to all items */
7516 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7517 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7519 else
7520 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7523 * Update selection mark
7525 * Investigation on windows 2k showed that selection mark was updated
7526 * whenever a new selection was made, but if the selected item was
7527 * unselected it was not updated.
7529 * we are probably still not 100% accurate, but this at least sets the
7530 * proper selection mark when it is needed
7533 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7534 (infoPtr->nSelectionMark == -1))
7536 int i;
7537 for (i = 0; i < infoPtr->nItemCount; i++)
7539 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7541 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7543 infoPtr->nSelectionMark = i;
7544 break;
7547 else if (ranges_contain(infoPtr->selectionRanges, i))
7549 infoPtr->nSelectionMark = i;
7550 break;
7555 return bResult;
7558 /***
7559 * DESCRIPTION:
7560 * Sets the text of an item or subitem.
7562 * PARAMETER(S):
7563 * [I] hwnd : window handle
7564 * [I] nItem : item index
7565 * [I] lpLVItem : item or subitem info
7566 * [I] isW : TRUE if input is Unicode
7568 * RETURN:
7569 * SUCCESS : TRUE
7570 * FAILURE : FALSE
7572 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7574 LVITEMW lvItem;
7576 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7578 lvItem.iItem = nItem;
7579 lvItem.iSubItem = lpLVItem->iSubItem;
7580 lvItem.mask = LVIF_TEXT;
7581 lvItem.pszText = lpLVItem->pszText;
7582 lvItem.cchTextMax = lpLVItem->cchTextMax;
7584 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7586 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7589 /***
7590 * DESCRIPTION:
7591 * Set item index that marks the start of a multiple selection.
7593 * PARAMETER(S):
7594 * [I] infoPtr : valid pointer to the listview structure
7595 * [I] nIndex : index
7597 * RETURN:
7598 * Index number or -1 if there is no selection mark.
7600 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7602 INT nOldIndex = infoPtr->nSelectionMark;
7604 TRACE("(nIndex=%d)\n", nIndex);
7606 infoPtr->nSelectionMark = nIndex;
7608 return nOldIndex;
7611 /***
7612 * DESCRIPTION:
7613 * Sets the text background color.
7615 * PARAMETER(S):
7616 * [I] infoPtr : valid pointer to the listview structure
7617 * [I] clrTextBk : text background color
7619 * RETURN:
7620 * SUCCESS : TRUE
7621 * FAILURE : FALSE
7623 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7625 TRACE("(clrTextBk=%x)\n", clrTextBk);
7627 if (infoPtr->clrTextBk != clrTextBk)
7629 infoPtr->clrTextBk = clrTextBk;
7630 LISTVIEW_InvalidateList(infoPtr);
7633 return TRUE;
7636 /***
7637 * DESCRIPTION:
7638 * Sets the text foreground color.
7640 * PARAMETER(S):
7641 * [I] infoPtr : valid pointer to the listview structure
7642 * [I] clrText : text color
7644 * RETURN:
7645 * SUCCESS : TRUE
7646 * FAILURE : FALSE
7648 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7650 TRACE("(clrText=%x)\n", clrText);
7652 if (infoPtr->clrText != clrText)
7654 infoPtr->clrText = clrText;
7655 LISTVIEW_InvalidateList(infoPtr);
7658 return TRUE;
7661 /***
7662 * DESCRIPTION:
7663 * Determines which listview item is located at the specified position.
7665 * PARAMETER(S):
7666 * [I] infoPtr : valid pointer to the listview structure
7667 * [I] hwndNewToolTip : handle to new ToolTip
7669 * RETURN:
7670 * old tool tip
7672 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7674 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7675 infoPtr->hwndToolTip = hwndNewToolTip;
7676 return hwndOldToolTip;
7680 * DESCRIPTION:
7681 * sets the Unicode character format flag for the control
7682 * PARAMETER(S):
7683 * [I] infoPtr :valid pointer to the listview structure
7684 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7686 * RETURN:
7687 * Old Unicode Format
7689 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7691 BOOL rc = infoPtr->notifyFormat;
7692 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7693 return rc;
7696 /* LISTVIEW_SetWorkAreas */
7698 /***
7699 * DESCRIPTION:
7700 * Callback internally used by LISTVIEW_SortItems()
7702 * PARAMETER(S):
7703 * [I] first : pointer to first ITEM_INFO to compare
7704 * [I] second : pointer to second ITEM_INFO to compare
7705 * [I] lParam : HWND of control
7707 * RETURN:
7708 * if first comes before second : negative
7709 * if first comes after second : positive
7710 * if first and second are equivalent : zero
7712 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7714 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7715 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7716 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7718 /* Forward the call to the client defined callback */
7719 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7722 /***
7723 * DESCRIPTION:
7724 * Sorts the listview items.
7726 * PARAMETER(S):
7727 * [I] infoPtr : valid pointer to the listview structure
7728 * [I] pfnCompare : application-defined value
7729 * [I] lParamSort : pointer to comparison callback
7731 * RETURN:
7732 * SUCCESS : TRUE
7733 * FAILURE : FALSE
7735 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7737 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7738 HDPA hdpaSubItems;
7739 ITEM_INFO *lpItem;
7740 LPVOID selectionMarkItem;
7741 LVITEMW item;
7742 int i;
7744 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7746 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7748 if (!pfnCompare) return FALSE;
7749 if (!infoPtr->hdpaItems) return FALSE;
7751 /* if there are 0 or 1 items, there is no need to sort */
7752 if (infoPtr->nItemCount < 2) return TRUE;
7754 if (infoPtr->nFocusedItem >= 0)
7756 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7757 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7758 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7760 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7761 /* clear the lpItem->state for non-selected ones */
7762 /* remove the selection ranges */
7764 infoPtr->pfnCompare = pfnCompare;
7765 infoPtr->lParamSort = lParamSort;
7766 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7768 /* Adjust selections and indices so that they are the way they should
7769 * be after the sort (otherwise, the list items move around, but
7770 * whatever is at the item's previous original position will be
7771 * selected instead)
7773 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7774 for (i=0; i < infoPtr->nItemCount; i++)
7776 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7777 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7779 if (lpItem->state & LVIS_SELECTED)
7781 item.state = LVIS_SELECTED;
7782 item.stateMask = LVIS_SELECTED;
7783 LISTVIEW_SetItemState(infoPtr, i, &item);
7785 if (lpItem->state & LVIS_FOCUSED)
7787 infoPtr->nFocusedItem = i;
7788 lpItem->state &= ~LVIS_FOCUSED;
7791 if (selectionMarkItem != NULL)
7792 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7793 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7795 /* refresh the display */
7796 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7797 LISTVIEW_InvalidateList(infoPtr);
7799 return TRUE;
7802 /***
7803 * DESCRIPTION:
7804 * Update theme handle after a theme change.
7806 * PARAMETER(S):
7807 * [I] infoPtr : valid pointer to the listview structure
7809 * RETURN:
7810 * SUCCESS : 0
7811 * FAILURE : something else
7813 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7815 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7816 CloseThemeData(theme);
7817 OpenThemeData(infoPtr->hwndSelf, themeClass);
7818 return 0;
7821 /***
7822 * DESCRIPTION:
7823 * Updates an items or rearranges the listview control.
7825 * PARAMETER(S):
7826 * [I] infoPtr : valid pointer to the listview structure
7827 * [I] nItem : item index
7829 * RETURN:
7830 * SUCCESS : TRUE
7831 * FAILURE : FALSE
7833 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7835 TRACE("(nItem=%d)\n", nItem);
7837 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7839 /* rearrange with default alignment style */
7840 if (is_autoarrange(infoPtr))
7841 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7842 else
7843 LISTVIEW_InvalidateItem(infoPtr, nItem);
7845 return TRUE;
7848 /***
7849 * DESCRIPTION:
7850 * Draw the track line at the place defined in the infoPtr structure.
7851 * The line is drawn with a XOR pen so drawing the line for the second time
7852 * in the same place erases the line.
7854 * PARAMETER(S):
7855 * [I] infoPtr : valid pointer to the listview structure
7857 * RETURN:
7858 * SUCCESS : TRUE
7859 * FAILURE : FALSE
7861 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7863 HPEN hOldPen;
7864 HDC hdc;
7865 INT oldROP;
7867 if (infoPtr->xTrackLine == -1)
7868 return FALSE;
7870 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7871 return FALSE;
7872 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7873 oldROP = SetROP2(hdc, R2_XORPEN);
7874 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7875 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7876 SetROP2(hdc, oldROP);
7877 SelectObject(hdc, hOldPen);
7878 ReleaseDC(infoPtr->hwndSelf, hdc);
7879 return TRUE;
7882 /***
7883 * DESCRIPTION:
7884 * Called when an edit control should be displayed. This function is called after
7885 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7887 * PARAMETER(S):
7888 * [I] hwnd : Handle to the listview
7889 * [I] uMsg : WM_TIMER (ignored)
7890 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7891 * [I] dwTimer : The elapsed time (ignored)
7893 * RETURN:
7894 * None.
7896 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7898 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7899 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7901 KillTimer(hwnd, idEvent);
7902 editItem->fEnabled = FALSE;
7903 /* check if the item is still selected */
7904 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7905 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7908 /***
7909 * DESCRIPTION:
7910 * Creates the listview control - the WM_NCCREATE phase.
7912 * PARAMETER(S):
7913 * [I] hwnd : window handle
7914 * [I] lpcs : the create parameters
7916 * RETURN:
7917 * Success: TRUE
7918 * Failure: FALSE
7920 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7922 LISTVIEW_INFO *infoPtr;
7923 LOGFONTW logFont;
7925 TRACE("(lpcs=%p)\n", lpcs);
7927 /* initialize info pointer */
7928 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7929 if (!infoPtr) return FALSE;
7931 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7933 infoPtr->hwndSelf = hwnd;
7934 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7935 /* determine the type of structures to use */
7936 infoPtr->hwndNotify = lpcs->hwndParent;
7937 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7939 /* initialize color information */
7940 infoPtr->clrBk = CLR_NONE;
7941 infoPtr->clrText = CLR_DEFAULT;
7942 infoPtr->clrTextBk = CLR_DEFAULT;
7943 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7945 /* set default values */
7946 infoPtr->nFocusedItem = -1;
7947 infoPtr->nSelectionMark = -1;
7948 infoPtr->nHotItem = -1;
7949 infoPtr->bRedraw = TRUE;
7950 infoPtr->bNoItemMetrics = TRUE;
7951 infoPtr->bDoChangeNotify = TRUE;
7952 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7953 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7954 infoPtr->nEditLabelItem = -1;
7955 infoPtr->dwHoverTime = -1; /* default system hover time */
7956 infoPtr->nMeasureItemHeight = 0;
7957 infoPtr->xTrackLine = -1; /* no track line */
7958 infoPtr->itemEdit.fEnabled = FALSE;
7960 /* get default font (icon title) */
7961 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7962 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7963 infoPtr->hFont = infoPtr->hDefaultFont;
7964 LISTVIEW_SaveTextMetrics(infoPtr);
7966 /* allocate memory for the data structure */
7967 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7968 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7969 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7970 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7971 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7972 return TRUE;
7974 fail:
7975 DestroyWindow(infoPtr->hwndHeader);
7976 ranges_destroy(infoPtr->selectionRanges);
7977 DPA_Destroy(infoPtr->hdpaItems);
7978 DPA_Destroy(infoPtr->hdpaPosX);
7979 DPA_Destroy(infoPtr->hdpaPosY);
7980 DPA_Destroy(infoPtr->hdpaColumns);
7981 Free(infoPtr);
7982 return FALSE;
7985 /***
7986 * DESCRIPTION:
7987 * Creates the listview control - the WM_CREATE phase. Most of the data is
7988 * already set up in LISTVIEW_NCCreate
7990 * PARAMETER(S):
7991 * [I] hwnd : window handle
7992 * [I] lpcs : the create parameters
7994 * RETURN:
7995 * Success: 0
7996 * Failure: -1
7998 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8000 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8001 UINT uView = lpcs->style & LVS_TYPEMASK;
8002 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
8004 TRACE("(lpcs=%p)\n", lpcs);
8006 infoPtr->dwStyle = lpcs->style;
8007 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8008 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8010 /* setup creation flags */
8011 dFlags |= (LVS_NOSORTHEADER & lpcs->style) ? 0 : HDS_BUTTONS;
8012 dFlags |= (LVS_NOCOLUMNHEADER & lpcs->style) ? HDS_HIDDEN : 0;
8014 /* create header */
8015 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
8016 0, 0, 0, 0, hwnd, NULL,
8017 lpcs->hInstance, NULL);
8018 if (!infoPtr->hwndHeader) return -1;
8020 /* set header unicode format */
8021 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
8023 /* set header font */
8024 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8026 /* init item size to avoid division by 0 */
8027 LISTVIEW_UpdateItemSize (infoPtr);
8029 if (uView == LVS_REPORT)
8031 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8033 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8035 LISTVIEW_UpdateSize(infoPtr);
8036 LISTVIEW_UpdateScroll(infoPtr);
8039 OpenThemeData(hwnd, themeClass);
8041 /* initialize the icon sizes */
8042 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8043 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8044 return 0;
8047 /***
8048 * DESCRIPTION:
8049 * Destroys the listview control.
8051 * PARAMETER(S):
8052 * [I] infoPtr : valid pointer to the listview structure
8054 * RETURN:
8055 * Success: 0
8056 * Failure: -1
8058 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8060 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8061 CloseThemeData(theme);
8062 return 0;
8065 /***
8066 * DESCRIPTION:
8067 * Enables the listview control.
8069 * PARAMETER(S):
8070 * [I] infoPtr : valid pointer to the listview structure
8071 * [I] bEnable : specifies whether to enable or disable the window
8073 * RETURN:
8074 * SUCCESS : TRUE
8075 * FAILURE : FALSE
8077 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8079 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8080 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8081 return TRUE;
8084 /***
8085 * DESCRIPTION:
8086 * Erases the background of the listview control.
8088 * PARAMETER(S):
8089 * [I] infoPtr : valid pointer to the listview structure
8090 * [I] hdc : device context handle
8092 * RETURN:
8093 * SUCCESS : TRUE
8094 * FAILURE : FALSE
8096 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8098 RECT rc;
8100 TRACE("(hdc=%p)\n", hdc);
8102 if (!GetClipBox(hdc, &rc)) return FALSE;
8104 /* for double buffered controls we need to do this during refresh */
8105 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8107 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8111 /***
8112 * DESCRIPTION:
8113 * Helper function for LISTVIEW_[HV]Scroll *only*.
8114 * Performs vertical/horizontal scrolling by a give amount.
8116 * PARAMETER(S):
8117 * [I] infoPtr : valid pointer to the listview structure
8118 * [I] dx : amount of horizontal scroll
8119 * [I] dy : amount of vertical scroll
8121 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8123 /* now we can scroll the list */
8124 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8125 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8126 /* if we have focus, adjust rect */
8127 OffsetRect(&infoPtr->rcFocus, dx, dy);
8128 UpdateWindow(infoPtr->hwndSelf);
8131 /***
8132 * DESCRIPTION:
8133 * Performs vertical scrolling.
8135 * PARAMETER(S):
8136 * [I] infoPtr : valid pointer to the listview structure
8137 * [I] nScrollCode : scroll code
8138 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8139 * [I] hScrollWnd : scrollbar control window handle
8141 * RETURN:
8142 * Zero
8144 * NOTES:
8145 * SB_LINEUP/SB_LINEDOWN:
8146 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8147 * for LVS_REPORT is 1 line
8148 * for LVS_LIST cannot occur
8151 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8152 INT nScrollDiff, HWND hScrollWnd)
8154 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8155 INT nOldScrollPos, nNewScrollPos;
8156 SCROLLINFO scrollInfo;
8157 BOOL is_an_icon;
8159 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8160 debugscrollcode(nScrollCode), nScrollDiff);
8162 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8164 scrollInfo.cbSize = sizeof(SCROLLINFO);
8165 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8167 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8169 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8171 nOldScrollPos = scrollInfo.nPos;
8172 switch (nScrollCode)
8174 case SB_INTERNAL:
8175 break;
8177 case SB_LINEUP:
8178 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8179 break;
8181 case SB_LINEDOWN:
8182 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8183 break;
8185 case SB_PAGEUP:
8186 nScrollDiff = -scrollInfo.nPage;
8187 break;
8189 case SB_PAGEDOWN:
8190 nScrollDiff = scrollInfo.nPage;
8191 break;
8193 case SB_THUMBPOSITION:
8194 case SB_THUMBTRACK:
8195 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8196 break;
8198 default:
8199 nScrollDiff = 0;
8202 /* quit right away if pos isn't changing */
8203 if (nScrollDiff == 0) return 0;
8205 /* calculate new position, and handle overflows */
8206 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8207 if (nScrollDiff > 0) {
8208 if (nNewScrollPos < nOldScrollPos ||
8209 nNewScrollPos > scrollInfo.nMax)
8210 nNewScrollPos = scrollInfo.nMax;
8211 } else {
8212 if (nNewScrollPos > nOldScrollPos ||
8213 nNewScrollPos < scrollInfo.nMin)
8214 nNewScrollPos = scrollInfo.nMin;
8217 /* set the new position, and reread in case it changed */
8218 scrollInfo.fMask = SIF_POS;
8219 scrollInfo.nPos = nNewScrollPos;
8220 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8222 /* carry on only if it really changed */
8223 if (nNewScrollPos == nOldScrollPos) return 0;
8225 /* now adjust to client coordinates */
8226 nScrollDiff = nOldScrollPos - nNewScrollPos;
8227 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8229 /* and scroll the window */
8230 scroll_list(infoPtr, 0, nScrollDiff);
8232 return 0;
8235 /***
8236 * DESCRIPTION:
8237 * Performs horizontal scrolling.
8239 * PARAMETER(S):
8240 * [I] infoPtr : valid pointer to the listview structure
8241 * [I] nScrollCode : scroll code
8242 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8243 * [I] hScrollWnd : scrollbar control window handle
8245 * RETURN:
8246 * Zero
8248 * NOTES:
8249 * SB_LINELEFT/SB_LINERIGHT:
8250 * for LVS_ICON, LVS_SMALLICON 1 pixel
8251 * for LVS_REPORT is 1 pixel
8252 * for LVS_LIST is 1 column --> which is a 1 because the
8253 * scroll is based on columns not pixels
8256 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8257 INT nScrollDiff, HWND hScrollWnd)
8259 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8260 INT nOldScrollPos, nNewScrollPos;
8261 SCROLLINFO scrollInfo;
8263 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8264 debugscrollcode(nScrollCode), nScrollDiff);
8266 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8268 scrollInfo.cbSize = sizeof(SCROLLINFO);
8269 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8271 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8273 nOldScrollPos = scrollInfo.nPos;
8275 switch (nScrollCode)
8277 case SB_INTERNAL:
8278 break;
8280 case SB_LINELEFT:
8281 nScrollDiff = -1;
8282 break;
8284 case SB_LINERIGHT:
8285 nScrollDiff = 1;
8286 break;
8288 case SB_PAGELEFT:
8289 nScrollDiff = -scrollInfo.nPage;
8290 break;
8292 case SB_PAGERIGHT:
8293 nScrollDiff = scrollInfo.nPage;
8294 break;
8296 case SB_THUMBPOSITION:
8297 case SB_THUMBTRACK:
8298 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8299 break;
8301 default:
8302 nScrollDiff = 0;
8305 /* quit right away if pos isn't changing */
8306 if (nScrollDiff == 0) return 0;
8308 /* calculate new position, and handle overflows */
8309 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8310 if (nScrollDiff > 0) {
8311 if (nNewScrollPos < nOldScrollPos ||
8312 nNewScrollPos > scrollInfo.nMax)
8313 nNewScrollPos = scrollInfo.nMax;
8314 } else {
8315 if (nNewScrollPos > nOldScrollPos ||
8316 nNewScrollPos < scrollInfo.nMin)
8317 nNewScrollPos = scrollInfo.nMin;
8320 /* set the new position, and reread in case it changed */
8321 scrollInfo.fMask = SIF_POS;
8322 scrollInfo.nPos = nNewScrollPos;
8323 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8325 /* carry on only if it really changed */
8326 if (nNewScrollPos == nOldScrollPos) return 0;
8328 if(uView == LVS_REPORT)
8329 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8331 /* now adjust to client coordinates */
8332 nScrollDiff = nOldScrollPos - nNewScrollPos;
8333 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8335 /* and scroll the window */
8336 scroll_list(infoPtr, nScrollDiff, 0);
8338 return 0;
8341 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8343 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8344 INT gcWheelDelta = 0;
8345 INT pulScrollLines = 3;
8346 SCROLLINFO scrollInfo;
8348 TRACE("(wheelDelta=%d)\n", wheelDelta);
8350 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8351 gcWheelDelta -= wheelDelta;
8353 scrollInfo.cbSize = sizeof(SCROLLINFO);
8354 scrollInfo.fMask = SIF_POS;
8356 switch(uView)
8358 case LVS_ICON:
8359 case LVS_SMALLICON:
8361 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8362 * should be fixed in the future.
8364 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8365 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8366 break;
8368 case LVS_REPORT:
8369 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8371 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8372 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8373 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8375 break;
8377 case LVS_LIST:
8378 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8379 break;
8381 return 0;
8384 /***
8385 * DESCRIPTION:
8386 * ???
8388 * PARAMETER(S):
8389 * [I] infoPtr : valid pointer to the listview structure
8390 * [I] nVirtualKey : virtual key
8391 * [I] lKeyData : key data
8393 * RETURN:
8394 * Zero
8396 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8398 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8399 HWND hwndSelf = infoPtr->hwndSelf;
8400 INT nItem = -1;
8401 NMLVKEYDOWN nmKeyDown;
8403 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8405 /* send LVN_KEYDOWN notification */
8406 nmKeyDown.wVKey = nVirtualKey;
8407 nmKeyDown.flags = 0;
8408 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8409 if (!IsWindow(hwndSelf))
8410 return 0;
8412 switch (nVirtualKey)
8414 case VK_SPACE:
8415 nItem = infoPtr->nFocusedItem;
8416 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8417 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8418 break;
8420 case VK_RETURN:
8421 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8423 if (!notify(infoPtr, NM_RETURN)) return 0;
8424 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8426 break;
8428 case VK_HOME:
8429 if (infoPtr->nItemCount > 0)
8430 nItem = 0;
8431 break;
8433 case VK_END:
8434 if (infoPtr->nItemCount > 0)
8435 nItem = infoPtr->nItemCount - 1;
8436 break;
8438 case VK_LEFT:
8439 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8440 break;
8442 case VK_UP:
8443 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8444 break;
8446 case VK_RIGHT:
8447 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8448 break;
8450 case VK_DOWN:
8451 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8452 break;
8454 case VK_PRIOR:
8455 if (uView == LVS_REPORT)
8457 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8458 if (infoPtr->nFocusedItem == topidx)
8459 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8460 else
8461 nItem = topidx;
8463 else
8464 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8465 * LISTVIEW_GetCountPerRow(infoPtr);
8466 if(nItem < 0) nItem = 0;
8467 break;
8469 case VK_NEXT:
8470 if (uView == LVS_REPORT)
8472 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8473 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8474 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8475 nItem = infoPtr->nFocusedItem + cnt - 1;
8476 else
8477 nItem = topidx + cnt - 1;
8479 else
8480 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8481 * LISTVIEW_GetCountPerRow(infoPtr);
8482 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8483 break;
8486 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8487 LISTVIEW_KeySelection(infoPtr, nItem);
8489 return 0;
8492 /***
8493 * DESCRIPTION:
8494 * Kills the focus.
8496 * PARAMETER(S):
8497 * [I] infoPtr : valid pointer to the listview structure
8499 * RETURN:
8500 * Zero
8502 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8504 TRACE("()\n");
8506 /* if we did not have the focus, there's nothing to do */
8507 if (!infoPtr->bFocus) return 0;
8509 /* send NM_KILLFOCUS notification */
8510 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8512 /* if we have a focus rectagle, get rid of it */
8513 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8515 /* set window focus flag */
8516 infoPtr->bFocus = FALSE;
8518 /* invalidate the selected items before resetting focus flag */
8519 LISTVIEW_InvalidateSelectedItems(infoPtr);
8521 return 0;
8524 /***
8525 * DESCRIPTION:
8526 * Processes double click messages (left mouse button).
8528 * PARAMETER(S):
8529 * [I] infoPtr : valid pointer to the listview structure
8530 * [I] wKey : key flag
8531 * [I] x,y : mouse coordinate
8533 * RETURN:
8534 * Zero
8536 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8538 LVHITTESTINFO htInfo;
8540 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8542 /* Cancel the item edition if any */
8543 if (infoPtr->itemEdit.fEnabled)
8545 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8546 infoPtr->itemEdit.fEnabled = FALSE;
8549 /* send NM_RELEASEDCAPTURE notification */
8550 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8552 htInfo.pt.x = x;
8553 htInfo.pt.y = y;
8555 /* send NM_DBLCLK notification */
8556 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8557 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8559 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8560 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8562 return 0;
8565 /***
8566 * DESCRIPTION:
8567 * Processes mouse down messages (left mouse button).
8569 * PARAMETERS:
8570 * infoPtr [I ] valid pointer to the listview structure
8571 * wKey [I ] key flag
8572 * x,y [I ] mouse coordinate
8574 * RETURN:
8575 * Zero
8577 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8579 LVHITTESTINFO lvHitTestInfo;
8580 static BOOL bGroupSelect = TRUE;
8581 BOOL bReceivedFocus = FALSE;
8582 POINT pt = { x, y };
8583 INT nItem;
8585 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8587 /* send NM_RELEASEDCAPTURE notification */
8588 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8590 if (!infoPtr->bFocus)
8591 bReceivedFocus = TRUE;
8593 /* set left button down flag and record the click position */
8594 infoPtr->bLButtonDown = TRUE;
8595 infoPtr->ptClickPos = pt;
8597 lvHitTestInfo.pt.x = x;
8598 lvHitTestInfo.pt.y = y;
8600 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8601 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8602 infoPtr->nEditLabelItem = -1;
8603 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8605 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8607 toggle_checkbox_state(infoPtr, nItem);
8608 return 0;
8611 if (infoPtr->dwStyle & LVS_SINGLESEL)
8613 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8614 infoPtr->nEditLabelItem = nItem;
8615 else
8616 LISTVIEW_SetSelection(infoPtr, nItem);
8618 else
8620 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8622 if (bGroupSelect)
8624 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8625 LISTVIEW_SetItemFocus(infoPtr, nItem);
8626 infoPtr->nSelectionMark = nItem;
8628 else
8630 LVITEMW item;
8632 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8633 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8635 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8636 infoPtr->nSelectionMark = nItem;
8639 else if (wKey & MK_CONTROL)
8641 LVITEMW item;
8643 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8645 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8646 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8647 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8648 infoPtr->nSelectionMark = nItem;
8650 else if (wKey & MK_SHIFT)
8652 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8654 else
8656 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8657 infoPtr->nEditLabelItem = nItem;
8659 /* set selection (clears other pre-existing selections) */
8660 LISTVIEW_SetSelection(infoPtr, nItem);
8664 else
8666 /* remove all selections */
8667 LISTVIEW_DeselectAll(infoPtr);
8668 ReleaseCapture();
8671 if (bReceivedFocus)
8672 infoPtr->nEditLabelItem = -1;
8674 return 0;
8677 /***
8678 * DESCRIPTION:
8679 * Processes mouse up messages (left mouse button).
8681 * PARAMETERS:
8682 * infoPtr [I ] valid pointer to the listview structure
8683 * wKey [I ] key flag
8684 * x,y [I ] mouse coordinate
8686 * RETURN:
8687 * Zero
8689 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8691 LVHITTESTINFO lvHitTestInfo;
8693 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8695 if (!infoPtr->bLButtonDown) return 0;
8697 lvHitTestInfo.pt.x = x;
8698 lvHitTestInfo.pt.y = y;
8700 /* send NM_CLICK notification */
8701 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8702 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8704 /* set left button flag */
8705 infoPtr->bLButtonDown = FALSE;
8707 /* if we clicked on a selected item, edit the label */
8708 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8710 /* we want to make sure the user doesn't want to do a double click. So we will
8711 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8713 infoPtr->itemEdit.fEnabled = TRUE;
8714 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8715 SetTimer(infoPtr->hwndSelf,
8716 (UINT_PTR)&infoPtr->itemEdit,
8717 GetDoubleClickTime(),
8718 LISTVIEW_DelayedEditItem);
8721 if (!infoPtr->bFocus)
8722 SetFocus(infoPtr->hwndSelf);
8724 return 0;
8727 /***
8728 * DESCRIPTION:
8729 * Destroys the listview control (called after WM_DESTROY).
8731 * PARAMETER(S):
8732 * [I] infoPtr : valid pointer to the listview structure
8734 * RETURN:
8735 * Zero
8737 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8739 TRACE("()\n");
8741 /* delete all items */
8742 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8744 /* destroy data structure */
8745 DPA_Destroy(infoPtr->hdpaItems);
8746 DPA_Destroy(infoPtr->hdpaPosX);
8747 DPA_Destroy(infoPtr->hdpaPosY);
8748 DPA_Destroy(infoPtr->hdpaColumns);
8749 ranges_destroy(infoPtr->selectionRanges);
8751 /* destroy image lists */
8752 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8754 if (infoPtr->himlNormal)
8755 ImageList_Destroy(infoPtr->himlNormal);
8756 if (infoPtr->himlSmall)
8757 ImageList_Destroy(infoPtr->himlSmall);
8758 if (infoPtr->himlState)
8759 ImageList_Destroy(infoPtr->himlState);
8762 /* destroy font, bkgnd brush */
8763 infoPtr->hFont = 0;
8764 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8765 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8767 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8769 /* free listview info pointer*/
8770 Free(infoPtr);
8772 return 0;
8775 /***
8776 * DESCRIPTION:
8777 * Handles notifications from header.
8779 * PARAMETER(S):
8780 * [I] infoPtr : valid pointer to the listview structure
8781 * [I] nCtrlId : control identifier
8782 * [I] lpnmh : notification information
8784 * RETURN:
8785 * Zero
8787 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8789 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8790 HWND hwndSelf = infoPtr->hwndSelf;
8792 TRACE("(lpnmh=%p)\n", lpnmh);
8794 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8796 switch (lpnmh->hdr.code)
8798 case HDN_TRACKW:
8799 case HDN_TRACKA:
8801 COLUMN_INFO *lpColumnInfo;
8802 POINT ptOrigin;
8803 INT x;
8805 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8806 break;
8808 /* remove the old line (if any) */
8809 LISTVIEW_DrawTrackLine(infoPtr);
8811 /* compute & draw the new line */
8812 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8813 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8814 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8815 infoPtr->xTrackLine = x + ptOrigin.x;
8816 LISTVIEW_DrawTrackLine(infoPtr);
8817 break;
8820 case HDN_ENDTRACKA:
8821 case HDN_ENDTRACKW:
8822 /* remove the track line (if any) */
8823 LISTVIEW_DrawTrackLine(infoPtr);
8824 infoPtr->xTrackLine = -1;
8825 break;
8827 case HDN_ENDDRAG:
8828 FIXME("Changing column order not implemented\n");
8829 return TRUE;
8831 case HDN_ITEMCHANGINGW:
8832 case HDN_ITEMCHANGINGA:
8833 return notify_forward_header(infoPtr, lpnmh);
8835 case HDN_ITEMCHANGEDW:
8836 case HDN_ITEMCHANGEDA:
8838 COLUMN_INFO *lpColumnInfo;
8839 INT dx, cxy;
8841 notify_forward_header(infoPtr, lpnmh);
8842 if (!IsWindow(hwndSelf))
8843 break;
8845 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8847 HDITEMW hdi;
8849 hdi.mask = HDI_WIDTH;
8850 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8851 cxy = hdi.cxy;
8853 else
8854 cxy = lpnmh->pitem->cxy;
8856 /* determine how much we change since the last know position */
8857 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8858 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8859 if (dx != 0)
8861 lpColumnInfo->rcHeader.right += dx;
8862 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8863 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8864 else
8866 /* only needs to update the scrolls */
8867 infoPtr->nItemWidth += dx;
8868 LISTVIEW_UpdateScroll(infoPtr);
8870 LISTVIEW_UpdateItemSize(infoPtr);
8871 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8873 POINT ptOrigin;
8874 RECT rcCol = lpColumnInfo->rcHeader;
8876 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8877 OffsetRect(&rcCol, ptOrigin.x, 0);
8879 rcCol.top = infoPtr->rcList.top;
8880 rcCol.bottom = infoPtr->rcList.bottom;
8882 /* resizing left-aligned columns leaves most of the left side untouched */
8883 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8885 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8886 if (dx > 0)
8887 nMaxDirty += dx;
8888 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8891 /* when shrinking the last column clear the now unused field */
8892 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8893 rcCol.right -= dx;
8895 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8899 break;
8901 case HDN_ITEMCLICKW:
8902 case HDN_ITEMCLICKA:
8904 /* Handle sorting by Header Column */
8905 NMLISTVIEW nmlv;
8907 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8908 nmlv.iItem = -1;
8909 nmlv.iSubItem = lpnmh->iItem;
8910 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8912 break;
8914 case HDN_DIVIDERDBLCLICKW:
8915 case HDN_DIVIDERDBLCLICKA:
8916 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8917 break;
8920 return 0;
8923 /***
8924 * DESCRIPTION:
8925 * Paint non-client area of control.
8927 * PARAMETER(S):
8928 * [I] infoPtr : valid pointer to the listview structureof the sender
8929 * [I] region : update region
8931 * RETURN:
8932 * TRUE - frame was painted
8933 * FALSE - call default window proc
8935 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8937 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8938 HDC dc;
8939 RECT r;
8940 HRGN cliprgn;
8941 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8942 cyEdge = GetSystemMetrics (SM_CYEDGE);
8944 if (!theme) return FALSE;
8946 GetWindowRect(infoPtr->hwndSelf, &r);
8948 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8949 r.right - cxEdge, r.bottom - cyEdge);
8950 if (region != (HRGN)1)
8951 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8952 OffsetRect(&r, -r.left, -r.top);
8954 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8955 OffsetRect(&r, -r.left, -r.top);
8957 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8958 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8959 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8960 ReleaseDC(infoPtr->hwndSelf, dc);
8962 /* Call default proc to get the scrollbars etc. painted */
8963 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8965 return TRUE;
8968 /***
8969 * DESCRIPTION:
8970 * Determines the type of structure to use.
8972 * PARAMETER(S):
8973 * [I] infoPtr : valid pointer to the listview structureof the sender
8974 * [I] hwndFrom : listview window handle
8975 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8977 * RETURN:
8978 * Zero
8980 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8982 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8984 if (nCommand == NF_REQUERY)
8985 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8987 return infoPtr->notifyFormat;
8990 /***
8991 * DESCRIPTION:
8992 * Paints/Repaints the listview control.
8994 * PARAMETER(S):
8995 * [I] infoPtr : valid pointer to the listview structure
8996 * [I] hdc : device context handle
8998 * RETURN:
8999 * Zero
9001 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9003 TRACE("(hdc=%p)\n", hdc);
9005 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9007 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9009 infoPtr->bNoItemMetrics = FALSE;
9010 LISTVIEW_UpdateItemSize(infoPtr);
9011 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9012 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9013 LISTVIEW_UpdateScroll(infoPtr);
9016 UpdateWindow(infoPtr->hwndHeader);
9018 if (hdc)
9019 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9020 else
9022 PAINTSTRUCT ps;
9024 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9025 if (!hdc) return 1;
9026 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9027 EndPaint(infoPtr->hwndSelf, &ps);
9030 return 0;
9034 /***
9035 * DESCRIPTION:
9036 * Paints/Repaints the listview control.
9038 * PARAMETER(S):
9039 * [I] infoPtr : valid pointer to the listview structure
9040 * [I] hdc : device context handle
9041 * [I] options : drawing options
9043 * RETURN:
9044 * Zero
9046 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9048 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9050 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9051 return 0;
9053 if (options & PRF_ERASEBKGND)
9054 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9056 if (options & PRF_CLIENT)
9057 LISTVIEW_Paint(infoPtr, hdc);
9059 return 0;
9063 /***
9064 * DESCRIPTION:
9065 * Processes double click messages (right mouse button).
9067 * PARAMETER(S):
9068 * [I] infoPtr : valid pointer to the listview structure
9069 * [I] wKey : key flag
9070 * [I] x,y : mouse coordinate
9072 * RETURN:
9073 * Zero
9075 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9077 LVHITTESTINFO lvHitTestInfo;
9079 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9081 /* send NM_RELEASEDCAPTURE notification */
9082 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9084 /* send NM_RDBLCLK notification */
9085 lvHitTestInfo.pt.x = x;
9086 lvHitTestInfo.pt.y = y;
9087 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9088 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9090 return 0;
9093 /***
9094 * DESCRIPTION:
9095 * Processes mouse down messages (right mouse button).
9097 * PARAMETER(S):
9098 * [I] infoPtr : valid pointer to the listview structure
9099 * [I] wKey : key flag
9100 * [I] x,y : mouse coordinate
9102 * RETURN:
9103 * Zero
9105 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9107 LVHITTESTINFO lvHitTestInfo;
9108 INT nItem;
9110 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9112 /* send NM_RELEASEDCAPTURE notification */
9113 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9115 /* make sure the listview control window has the focus */
9116 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9118 /* set right button down flag */
9119 infoPtr->bRButtonDown = TRUE;
9121 /* determine the index of the selected item */
9122 lvHitTestInfo.pt.x = x;
9123 lvHitTestInfo.pt.y = y;
9124 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9126 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9128 LISTVIEW_SetItemFocus(infoPtr, nItem);
9129 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9130 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9131 LISTVIEW_SetSelection(infoPtr, nItem);
9133 else
9135 LISTVIEW_DeselectAll(infoPtr);
9138 return 0;
9141 /***
9142 * DESCRIPTION:
9143 * Processes mouse up messages (right mouse button).
9145 * PARAMETER(S):
9146 * [I] infoPtr : valid pointer to the listview structure
9147 * [I] wKey : key flag
9148 * [I] x,y : mouse coordinate
9150 * RETURN:
9151 * Zero
9153 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9155 LVHITTESTINFO lvHitTestInfo;
9156 POINT pt;
9158 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9160 if (!infoPtr->bRButtonDown) return 0;
9162 /* set button flag */
9163 infoPtr->bRButtonDown = FALSE;
9165 /* Send NM_RClICK notification */
9166 lvHitTestInfo.pt.x = x;
9167 lvHitTestInfo.pt.y = y;
9168 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9169 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9171 /* Change to screen coordinate for WM_CONTEXTMENU */
9172 pt = lvHitTestInfo.pt;
9173 ClientToScreen(infoPtr->hwndSelf, &pt);
9175 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9176 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9177 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9179 return 0;
9183 /***
9184 * DESCRIPTION:
9185 * Sets the cursor.
9187 * PARAMETER(S):
9188 * [I] infoPtr : valid pointer to the listview structure
9189 * [I] hwnd : window handle of window containing the cursor
9190 * [I] nHittest : hit-test code
9191 * [I] wMouseMsg : ideintifier of the mouse message
9193 * RETURN:
9194 * TRUE if cursor is set
9195 * FALSE otherwise
9197 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9199 LVHITTESTINFO lvHitTestInfo;
9201 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9203 if(!infoPtr->hHotCursor) return FALSE;
9205 GetCursorPos(&lvHitTestInfo.pt);
9206 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9208 SetCursor(infoPtr->hHotCursor);
9210 return TRUE;
9213 /***
9214 * DESCRIPTION:
9215 * Sets the focus.
9217 * PARAMETER(S):
9218 * [I] infoPtr : valid pointer to the listview structure
9219 * [I] hwndLoseFocus : handle of previously focused window
9221 * RETURN:
9222 * Zero
9224 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9226 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9228 /* if we have the focus already, there's nothing to do */
9229 if (infoPtr->bFocus) return 0;
9231 /* send NM_SETFOCUS notification */
9232 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9234 /* set window focus flag */
9235 infoPtr->bFocus = TRUE;
9237 /* put the focus rect back on */
9238 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9240 /* redraw all visible selected items */
9241 LISTVIEW_InvalidateSelectedItems(infoPtr);
9243 return 0;
9246 /***
9247 * DESCRIPTION:
9248 * Sets the font.
9250 * PARAMETER(S):
9251 * [I] infoPtr : valid pointer to the listview structure
9252 * [I] fRedraw : font handle
9253 * [I] fRedraw : redraw flag
9255 * RETURN:
9256 * Zero
9258 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9260 HFONT oldFont = infoPtr->hFont;
9262 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9264 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9265 if (infoPtr->hFont == oldFont) return 0;
9267 LISTVIEW_SaveTextMetrics(infoPtr);
9269 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9271 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9272 LISTVIEW_UpdateSize(infoPtr);
9273 LISTVIEW_UpdateScroll(infoPtr);
9276 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9278 return 0;
9281 /***
9282 * DESCRIPTION:
9283 * Message handling for WM_SETREDRAW.
9284 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9286 * PARAMETER(S):
9287 * [I] infoPtr : valid pointer to the listview structure
9288 * [I] bRedraw: state of redraw flag
9290 * RETURN:
9291 * DefWinProc return value
9293 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9295 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9297 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9298 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9300 infoPtr->bRedraw = bRedraw;
9302 if(!bRedraw) return 0;
9304 if (is_autoarrange(infoPtr))
9305 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9306 LISTVIEW_UpdateScroll(infoPtr);
9308 /* despite what the WM_SETREDRAW docs says, apps expect us
9309 * to invalidate the listview here... stupid! */
9310 LISTVIEW_InvalidateList(infoPtr);
9312 return 0;
9315 /***
9316 * DESCRIPTION:
9317 * Resizes the listview control. This function processes WM_SIZE
9318 * messages. At this time, the width and height are not used.
9320 * PARAMETER(S):
9321 * [I] infoPtr : valid pointer to the listview structure
9322 * [I] Width : new width
9323 * [I] Height : new height
9325 * RETURN:
9326 * Zero
9328 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9330 RECT rcOld = infoPtr->rcList;
9332 TRACE("(width=%d, height=%d)\n", Width, Height);
9334 LISTVIEW_UpdateSize(infoPtr);
9335 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9337 /* do not bother with display related stuff if we're not redrawing */
9338 if (!is_redrawing(infoPtr)) return 0;
9340 if (is_autoarrange(infoPtr))
9341 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9343 LISTVIEW_UpdateScroll(infoPtr);
9345 /* refresh all only for lists whose height changed significantly */
9346 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9347 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9348 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9349 LISTVIEW_InvalidateList(infoPtr);
9351 return 0;
9354 /***
9355 * DESCRIPTION:
9356 * Sets the size information.
9358 * PARAMETER(S):
9359 * [I] infoPtr : valid pointer to the listview structure
9361 * RETURN:
9362 * None
9364 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9366 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9368 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9370 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9372 if (uView == LVS_LIST)
9374 /* Apparently the "LIST" style is supposed to have the same
9375 * number of items in a column even if there is no scroll bar.
9376 * Since if a scroll bar already exists then the bottom is already
9377 * reduced, only reduce if the scroll bar does not currently exist.
9378 * The "2" is there to mimic the native control. I think it may be
9379 * related to either padding or edges. (GLA 7/2002)
9381 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9382 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9383 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9385 else if (uView == LVS_REPORT)
9387 HDLAYOUT hl;
9388 WINDOWPOS wp;
9390 hl.prc = &infoPtr->rcList;
9391 hl.pwpos = &wp;
9392 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9393 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9394 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9395 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9396 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9397 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9399 infoPtr->rcList.top = max(wp.cy, 0);
9400 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9403 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9406 /***
9407 * DESCRIPTION:
9408 * Processes WM_STYLECHANGED messages.
9410 * PARAMETER(S):
9411 * [I] infoPtr : valid pointer to the listview structure
9412 * [I] wStyleType : window style type (normal or extended)
9413 * [I] lpss : window style information
9415 * RETURN:
9416 * Zero
9418 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9419 const STYLESTRUCT *lpss)
9421 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9422 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9423 UINT style;
9425 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9426 wStyleType, lpss->styleOld, lpss->styleNew);
9428 if (wStyleType != GWL_STYLE) return 0;
9430 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9431 /* what if LVS_OWNERDATA changed? */
9432 /* or LVS_SINGLESEL */
9433 /* or LVS_SORT{AS,DES}CENDING */
9435 infoPtr->dwStyle = lpss->styleNew;
9437 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9438 ((lpss->styleNew & WS_HSCROLL) == 0))
9439 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9441 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9442 ((lpss->styleNew & WS_VSCROLL) == 0))
9443 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9445 if (uNewView != uOldView)
9447 SIZE oldIconSize = infoPtr->iconSize;
9448 HIMAGELIST himl;
9450 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9451 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9453 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9454 SetRectEmpty(&infoPtr->rcFocus);
9456 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9457 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9459 if (uNewView == LVS_ICON)
9461 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9463 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9464 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9465 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9468 else if (uNewView == LVS_REPORT)
9470 HDLAYOUT hl;
9471 WINDOWPOS wp;
9473 hl.prc = &infoPtr->rcList;
9474 hl.pwpos = &wp;
9475 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9476 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9477 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9478 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9481 LISTVIEW_UpdateItemSize(infoPtr);
9484 if (uNewView == LVS_REPORT)
9486 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9488 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9490 /* Turn off the header control */
9491 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9492 TRACE("Hide header control, was 0x%08x\n", style);
9493 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9494 } else {
9495 /* Turn on the header control */
9496 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9498 TRACE("Show header control, was 0x%08x\n", style);
9499 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9505 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9506 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9507 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9509 /* update the size of the client area */
9510 LISTVIEW_UpdateSize(infoPtr);
9512 /* add scrollbars if needed */
9513 LISTVIEW_UpdateScroll(infoPtr);
9515 /* invalidate client area + erase background */
9516 LISTVIEW_InvalidateList(infoPtr);
9518 return 0;
9521 /***
9522 * DESCRIPTION:
9523 * Window procedure of the listview control.
9526 static LRESULT WINAPI
9527 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9529 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9531 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9533 if (!infoPtr && (uMsg != WM_NCCREATE))
9534 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9536 switch (uMsg)
9538 case LVM_APPROXIMATEVIEWRECT:
9539 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9540 LOWORD(lParam), HIWORD(lParam));
9541 case LVM_ARRANGE:
9542 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9544 /* case LVM_CANCELEDITLABEL: */
9546 case LVM_CREATEDRAGIMAGE:
9547 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9549 case LVM_DELETEALLITEMS:
9550 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9552 case LVM_DELETECOLUMN:
9553 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9555 case LVM_DELETEITEM:
9556 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9558 case LVM_EDITLABELW:
9559 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9561 case LVM_EDITLABELA:
9562 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9564 /* case LVM_ENABLEGROUPVIEW: */
9566 case LVM_ENSUREVISIBLE:
9567 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9569 case LVM_FINDITEMW:
9570 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9572 case LVM_FINDITEMA:
9573 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9575 case LVM_GETBKCOLOR:
9576 return infoPtr->clrBk;
9578 /* case LVM_GETBKIMAGE: */
9580 case LVM_GETCALLBACKMASK:
9581 return infoPtr->uCallbackMask;
9583 case LVM_GETCOLUMNA:
9584 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9586 case LVM_GETCOLUMNW:
9587 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9589 case LVM_GETCOLUMNORDERARRAY:
9590 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9592 case LVM_GETCOLUMNWIDTH:
9593 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9595 case LVM_GETCOUNTPERPAGE:
9596 return LISTVIEW_GetCountPerPage(infoPtr);
9598 case LVM_GETEDITCONTROL:
9599 return (LRESULT)infoPtr->hwndEdit;
9601 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9602 return infoPtr->dwLvExStyle;
9604 /* case LVM_GETGROUPINFO: */
9606 /* case LVM_GETGROUPMETRICS: */
9608 case LVM_GETHEADER:
9609 return (LRESULT)infoPtr->hwndHeader;
9611 case LVM_GETHOTCURSOR:
9612 return (LRESULT)infoPtr->hHotCursor;
9614 case LVM_GETHOTITEM:
9615 return infoPtr->nHotItem;
9617 case LVM_GETHOVERTIME:
9618 return infoPtr->dwHoverTime;
9620 case LVM_GETIMAGELIST:
9621 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9623 /* case LVM_GETINSERTMARK: */
9625 /* case LVM_GETINSERTMARKCOLOR: */
9627 /* case LVM_GETINSERTMARKRECT: */
9629 case LVM_GETISEARCHSTRINGA:
9630 case LVM_GETISEARCHSTRINGW:
9631 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9632 return FALSE;
9634 case LVM_GETITEMA:
9635 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9637 case LVM_GETITEMW:
9638 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9640 case LVM_GETITEMCOUNT:
9641 return infoPtr->nItemCount;
9643 case LVM_GETITEMPOSITION:
9644 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9646 case LVM_GETITEMRECT:
9647 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9649 case LVM_GETITEMSPACING:
9650 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9652 case LVM_GETITEMSTATE:
9653 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9655 case LVM_GETITEMTEXTA:
9656 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9658 case LVM_GETITEMTEXTW:
9659 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9661 case LVM_GETNEXTITEM:
9662 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9664 case LVM_GETNUMBEROFWORKAREAS:
9665 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9666 return 1;
9668 case LVM_GETORIGIN:
9669 if (!lParam) return FALSE;
9670 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9671 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9672 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9673 return TRUE;
9675 /* case LVM_GETOUTLINECOLOR: */
9677 /* case LVM_GETSELECTEDCOLUMN: */
9679 case LVM_GETSELECTEDCOUNT:
9680 return LISTVIEW_GetSelectedCount(infoPtr);
9682 case LVM_GETSELECTIONMARK:
9683 return infoPtr->nSelectionMark;
9685 case LVM_GETSTRINGWIDTHA:
9686 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9688 case LVM_GETSTRINGWIDTHW:
9689 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9691 case LVM_GETSUBITEMRECT:
9692 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9694 case LVM_GETTEXTBKCOLOR:
9695 return infoPtr->clrTextBk;
9697 case LVM_GETTEXTCOLOR:
9698 return infoPtr->clrText;
9700 /* case LVM_GETTILEINFO: */
9702 /* case LVM_GETTILEVIEWINFO: */
9704 case LVM_GETTOOLTIPS:
9705 if( !infoPtr->hwndToolTip )
9706 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9707 return (LRESULT)infoPtr->hwndToolTip;
9709 case LVM_GETTOPINDEX:
9710 return LISTVIEW_GetTopIndex(infoPtr);
9712 /*case LVM_GETUNICODEFORMAT:
9713 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9714 return FALSE;*/
9716 /* case LVM_GETVIEW: */
9718 case LVM_GETVIEWRECT:
9719 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9721 case LVM_GETWORKAREAS:
9722 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9723 return FALSE;
9725 /* case LVM_HASGROUP: */
9727 case LVM_HITTEST:
9728 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9730 case LVM_INSERTCOLUMNA:
9731 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9733 case LVM_INSERTCOLUMNW:
9734 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9736 /* case LVM_INSERTGROUP: */
9738 /* case LVM_INSERTGROUPSORTED: */
9740 case LVM_INSERTITEMA:
9741 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9743 case LVM_INSERTITEMW:
9744 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9746 /* case LVM_INSERTMARKHITTEST: */
9748 /* case LVM_ISGROUPVIEWENABLED: */
9750 /* case LVM_MAPIDTOINDEX: */
9752 /* case LVM_MAPINDEXTOID: */
9754 /* case LVM_MOVEGROUP: */
9756 /* case LVM_MOVEITEMTOGROUP: */
9758 case LVM_REDRAWITEMS:
9759 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9761 /* case LVM_REMOVEALLGROUPS: */
9763 /* case LVM_REMOVEGROUP: */
9765 case LVM_SCROLL:
9766 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9768 case LVM_SETBKCOLOR:
9769 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9771 /* case LVM_SETBKIMAGE: */
9773 case LVM_SETCALLBACKMASK:
9774 infoPtr->uCallbackMask = (UINT)wParam;
9775 return TRUE;
9777 case LVM_SETCOLUMNA:
9778 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9780 case LVM_SETCOLUMNW:
9781 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9783 case LVM_SETCOLUMNORDERARRAY:
9784 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9786 case LVM_SETCOLUMNWIDTH:
9787 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9789 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9790 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9792 /* case LVM_SETGROUPINFO: */
9794 /* case LVM_SETGROUPMETRICS: */
9796 case LVM_SETHOTCURSOR:
9797 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9799 case LVM_SETHOTITEM:
9800 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9802 case LVM_SETHOVERTIME:
9803 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9805 case LVM_SETICONSPACING:
9806 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9808 case LVM_SETIMAGELIST:
9809 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9811 /* case LVM_SETINFOTIP: */
9813 /* case LVM_SETINSERTMARK: */
9815 /* case LVM_SETINSERTMARKCOLOR: */
9817 case LVM_SETITEMA:
9818 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9820 case LVM_SETITEMW:
9821 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9823 case LVM_SETITEMCOUNT:
9824 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9826 case LVM_SETITEMPOSITION:
9828 POINT pt;
9829 pt.x = (short)LOWORD(lParam);
9830 pt.y = (short)HIWORD(lParam);
9831 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9834 case LVM_SETITEMPOSITION32:
9835 if (lParam == 0) return FALSE;
9836 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9838 case LVM_SETITEMSTATE:
9839 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9841 case LVM_SETITEMTEXTA:
9842 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9844 case LVM_SETITEMTEXTW:
9845 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9847 /* case LVM_SETOUTLINECOLOR: */
9849 /* case LVM_SETSELECTEDCOLUMN: */
9851 case LVM_SETSELECTIONMARK:
9852 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9854 case LVM_SETTEXTBKCOLOR:
9855 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9857 case LVM_SETTEXTCOLOR:
9858 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9860 /* case LVM_SETTILEINFO: */
9862 /* case LVM_SETTILEVIEWINFO: */
9864 /* case LVM_SETTILEWIDTH: */
9866 case LVM_SETTOOLTIPS:
9867 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9869 case LVM_SETUNICODEFORMAT:
9870 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9872 /* case LVM_SETVIEW: */
9874 /* case LVM_SETWORKAREAS: */
9876 /* case LVM_SORTGROUPS: */
9878 case LVM_SORTITEMS:
9879 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9881 /* LVM_SORTITEMSEX: */
9883 case LVM_SUBITEMHITTEST:
9884 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9886 case LVM_UPDATE:
9887 return LISTVIEW_Update(infoPtr, (INT)wParam);
9889 case WM_CHAR:
9890 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9892 case WM_COMMAND:
9893 return LISTVIEW_Command(infoPtr, wParam, lParam);
9895 case WM_NCCREATE:
9896 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9898 case WM_CREATE:
9899 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9901 case WM_DESTROY:
9902 return LISTVIEW_Destroy(infoPtr);
9904 case WM_ENABLE:
9905 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9907 case WM_ERASEBKGND:
9908 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9910 case WM_GETDLGCODE:
9911 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9913 case WM_GETFONT:
9914 return (LRESULT)infoPtr->hFont;
9916 case WM_HSCROLL:
9917 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9919 case WM_KEYDOWN:
9920 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9922 case WM_KILLFOCUS:
9923 return LISTVIEW_KillFocus(infoPtr);
9925 case WM_LBUTTONDBLCLK:
9926 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9928 case WM_LBUTTONDOWN:
9929 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9931 case WM_LBUTTONUP:
9932 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9934 case WM_MOUSEMOVE:
9935 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9937 case WM_MOUSEHOVER:
9938 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9940 case WM_NCDESTROY:
9941 return LISTVIEW_NCDestroy(infoPtr);
9943 case WM_NCPAINT:
9944 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9945 return 0;
9946 goto fwd_msg;
9948 case WM_NOTIFY:
9949 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9950 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9951 else return 0;
9953 case WM_NOTIFYFORMAT:
9954 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9956 case WM_PRINTCLIENT:
9957 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9959 case WM_PAINT:
9960 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9962 case WM_RBUTTONDBLCLK:
9963 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9965 case WM_RBUTTONDOWN:
9966 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9968 case WM_RBUTTONUP:
9969 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9971 case WM_SETCURSOR:
9972 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9973 return TRUE;
9974 goto fwd_msg;
9976 case WM_SETFOCUS:
9977 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9979 case WM_SETFONT:
9980 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9982 case WM_SETREDRAW:
9983 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9985 case WM_SIZE:
9986 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9988 case WM_STYLECHANGED:
9989 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9991 case WM_SYSCOLORCHANGE:
9992 COMCTL32_RefreshSysColors();
9993 return 0;
9995 /* case WM_TIMER: */
9996 case WM_THEMECHANGED:
9997 return LISTVIEW_ThemeChanged(infoPtr);
9999 case WM_VSCROLL:
10000 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10002 case WM_MOUSEWHEEL:
10003 if (wParam & (MK_SHIFT | MK_CONTROL))
10004 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10005 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10007 case WM_WINDOWPOSCHANGED:
10008 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10010 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10011 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10012 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10014 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10016 MEASUREITEMSTRUCT mis;
10017 mis.CtlType = ODT_LISTVIEW;
10018 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10019 mis.itemID = -1;
10020 mis.itemWidth = 0;
10021 mis.itemData = 0;
10022 mis.itemHeight= infoPtr->nItemHeight;
10023 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10024 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10025 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10028 LISTVIEW_UpdateSize(infoPtr);
10029 LISTVIEW_UpdateScroll(infoPtr);
10031 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10033 /* case WM_WININICHANGE: */
10035 default:
10036 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10037 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10039 fwd_msg:
10040 /* call default window procedure */
10041 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10046 /***
10047 * DESCRIPTION:
10048 * Registers the window class.
10050 * PARAMETER(S):
10051 * None
10053 * RETURN:
10054 * None
10056 void LISTVIEW_Register(void)
10058 WNDCLASSW wndClass;
10060 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10061 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10062 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10063 wndClass.cbClsExtra = 0;
10064 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10065 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10066 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10067 wndClass.lpszClassName = WC_LISTVIEWW;
10068 RegisterClassW(&wndClass);
10071 /***
10072 * DESCRIPTION:
10073 * Unregisters the window class.
10075 * PARAMETER(S):
10076 * None
10078 * RETURN:
10079 * None
10081 void LISTVIEW_Unregister(void)
10083 UnregisterClassW(WC_LISTVIEWW, NULL);
10086 /***
10087 * DESCRIPTION:
10088 * Handle any WM_COMMAND messages
10090 * PARAMETER(S):
10091 * [I] infoPtr : valid pointer to the listview structure
10092 * [I] wParam : the first message parameter
10093 * [I] lParam : the second message parameter
10095 * RETURN:
10096 * Zero.
10098 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10100 switch (HIWORD(wParam))
10102 case EN_UPDATE:
10105 * Adjust the edit window size
10107 WCHAR buffer[1024];
10108 HDC hdc = GetDC(infoPtr->hwndEdit);
10109 HFONT hFont, hOldFont = 0;
10110 RECT rect;
10111 SIZE sz;
10113 if (!infoPtr->hwndEdit || !hdc) return 0;
10114 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10115 GetWindowRect(infoPtr->hwndEdit, &rect);
10117 /* Select font to get the right dimension of the string */
10118 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10119 if(hFont != 0)
10121 hOldFont = SelectObject(hdc, hFont);
10124 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10126 TEXTMETRICW textMetric;
10128 /* Add Extra spacing for the next character */
10129 GetTextMetricsW(hdc, &textMetric);
10130 sz.cx += (textMetric.tmMaxCharWidth * 2);
10132 SetWindowPos (
10133 infoPtr->hwndEdit,
10134 HWND_TOP,
10137 sz.cx,
10138 rect.bottom - rect.top,
10139 SWP_DRAWFRAME|SWP_NOMOVE);
10141 if(hFont != 0)
10142 SelectObject(hdc, hOldFont);
10144 ReleaseDC(infoPtr->hwndEdit, hdc);
10146 break;
10149 default:
10150 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10153 return 0;
10157 /***
10158 * DESCRIPTION:
10159 * Subclassed edit control windproc function
10161 * PARAMETER(S):
10162 * [I] hwnd : the edit window handle
10163 * [I] uMsg : the message that is to be processed
10164 * [I] wParam : first message parameter
10165 * [I] lParam : second message parameter
10166 * [I] isW : TRUE if input is Unicode
10168 * RETURN:
10169 * Zero.
10171 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10173 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10174 BOOL cancel = FALSE;
10176 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10177 hwnd, uMsg, wParam, lParam, isW);
10179 switch (uMsg)
10181 case WM_GETDLGCODE:
10182 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10184 case WM_KILLFOCUS:
10185 break;
10187 case WM_DESTROY:
10189 WNDPROC editProc = infoPtr->EditWndProc;
10190 infoPtr->EditWndProc = 0;
10191 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10192 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10195 case WM_KEYDOWN:
10196 if (VK_ESCAPE == (INT)wParam)
10198 cancel = TRUE;
10199 break;
10201 else if (VK_RETURN == (INT)wParam)
10202 break;
10204 default:
10205 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10208 /* kill the edit */
10209 if (infoPtr->hwndEdit)
10211 LPWSTR buffer = NULL;
10213 infoPtr->hwndEdit = 0;
10214 if (!cancel)
10216 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10218 if (len)
10220 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10222 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10223 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10227 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10229 Free(buffer);
10232 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10233 return 0;
10236 /***
10237 * DESCRIPTION:
10238 * Subclassed edit control Unicode windproc function
10240 * PARAMETER(S):
10241 * [I] hwnd : the edit window handle
10242 * [I] uMsg : the message that is to be processed
10243 * [I] wParam : first message parameter
10244 * [I] lParam : second message parameter
10246 * RETURN:
10248 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10250 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10253 /***
10254 * DESCRIPTION:
10255 * Subclassed edit control ANSI windproc function
10257 * PARAMETER(S):
10258 * [I] hwnd : the edit window handle
10259 * [I] uMsg : the message that is to be processed
10260 * [I] wParam : first message parameter
10261 * [I] lParam : second message parameter
10263 * RETURN:
10265 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10267 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10270 /***
10271 * DESCRIPTION:
10272 * Creates a subclassed edit control
10274 * PARAMETER(S):
10275 * [I] infoPtr : valid pointer to the listview structure
10276 * [I] text : initial text for the edit
10277 * [I] style : the window style
10278 * [I] isW : TRUE if input is Unicode
10280 * RETURN:
10282 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10283 INT x, INT y, INT width, INT height, BOOL isW)
10285 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10286 HWND hedit;
10287 SIZE sz;
10288 HDC hdc;
10289 HDC hOldFont=0;
10290 TEXTMETRICW textMetric;
10291 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10293 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10295 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10296 hdc = GetDC(infoPtr->hwndSelf);
10298 /* Select the font to get appropriate metric dimensions */
10299 if(infoPtr->hFont != 0)
10300 hOldFont = SelectObject(hdc, infoPtr->hFont);
10302 /*Get String Length in pixels */
10303 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10305 /*Add Extra spacing for the next character */
10306 GetTextMetricsW(hdc, &textMetric);
10307 sz.cx += (textMetric.tmMaxCharWidth * 2);
10309 if(infoPtr->hFont != 0)
10310 SelectObject(hdc, hOldFont);
10312 ReleaseDC(infoPtr->hwndSelf, hdc);
10313 if (isW)
10314 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10315 else
10316 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10318 if (!hedit) return 0;
10320 infoPtr->EditWndProc = (WNDPROC)
10321 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10322 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10324 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10326 return hedit;