push 8edcbf8579c1d48dd3fb4c679acf4b3012a9efac
[wine/hacks.git] / dlls / comctl32 / listview.c
blob70555e850ac2a9c286a136d3bdfdf91726ae64d2
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 BOOL bDragging;
261 POINT ptClickPos; /* point where the user clicked */
262 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
263 INT nItemHeight;
264 INT nItemWidth;
265 RANGES selectionRanges;
266 INT nSelectionMark;
267 INT nHotItem;
268 SHORT notifyFormat;
269 HWND hwndNotify;
270 RECT rcList; /* This rectangle is really the window
271 * client rectangle possibly reduced by the
272 * horizontal scroll bar and/or header - see
273 * LISTVIEW_UpdateSize. This rectangle offset
274 * by the LISTVIEW_GetOrigin value is in
275 * client coordinates */
276 SIZE iconSize;
277 SIZE iconSpacing;
278 SIZE iconStateSize;
279 UINT uCallbackMask;
280 HWND hwndHeader;
281 HCURSOR hHotCursor;
282 HFONT hDefaultFont;
283 HFONT hFont;
284 INT ntmHeight; /* Some cached metrics of the font used */
285 INT ntmMaxCharWidth; /* by the listview to draw items */
286 INT nEllipsisWidth;
287 BOOL bRedraw; /* Turns on/off repaints & invalidations */
288 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
289 BOOL bFocus;
290 BOOL bDoChangeNotify; /* send change notification messages? */
291 INT nFocusedItem;
292 RECT rcFocus;
293 DWORD dwStyle; /* the cached window GWL_STYLE */
294 DWORD dwLvExStyle; /* extended listview style */
295 INT nItemCount; /* the number of items in the list */
296 HDPA hdpaItems; /* array ITEM_INFO pointers */
297 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
298 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
299 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
300 POINT currIconPos; /* this is the position next icon will be placed */
301 PFNLVCOMPARE pfnCompare;
302 LPARAM lParamSort;
303 HWND hwndEdit;
304 WNDPROC EditWndProc;
305 INT nEditLabelItem;
306 DWORD dwHoverTime;
307 HWND hwndToolTip;
309 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
311 DWORD lastKeyPressTimestamp;
312 WPARAM charCode;
313 INT nSearchParamLength;
314 WCHAR szSearchParam[ MAX_PATH ];
315 BOOL bIsDrawing;
316 INT nMeasureItemHeight;
317 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
318 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
319 } LISTVIEW_INFO;
322 * constants
324 /* How many we debug buffer to allocate */
325 #define DEBUG_BUFFERS 20
326 /* The size of a single debug bbuffer */
327 #define DEBUG_BUFFER_SIZE 256
329 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
330 #define SB_INTERNAL -1
332 /* maximum size of a label */
333 #define DISP_TEXT_SIZE 512
335 /* padding for items in list and small icon display modes */
336 #define WIDTH_PADDING 12
338 /* padding for items in list, report and small icon display modes */
339 #define HEIGHT_PADDING 1
341 /* offset of items in report display mode */
342 #define REPORT_MARGINX 2
344 /* padding for icon in large icon display mode
345 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
346 * that HITTEST will see.
347 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
348 * ICON_TOP_PADDING - sum of the two above.
349 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
350 * LABEL_HOR_PADDING - between text and sides of box
351 * LABEL_VERT_PADDING - between bottom of text and end of box
353 * ICON_LR_PADDING - additional width above icon size.
354 * ICON_LR_HALF - half of the above value
356 #define ICON_TOP_PADDING_NOTHITABLE 2
357 #define ICON_TOP_PADDING_HITABLE 2
358 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
359 #define ICON_BOTTOM_PADDING 4
360 #define LABEL_HOR_PADDING 5
361 #define LABEL_VERT_PADDING 7
362 #define ICON_LR_PADDING 16
363 #define ICON_LR_HALF (ICON_LR_PADDING/2)
365 /* default label width for items in list and small icon display modes */
366 #define DEFAULT_LABEL_WIDTH 40
368 /* default column width for items in list display mode */
369 #define DEFAULT_COLUMN_WIDTH 128
371 /* Size of "line" scroll for V & H scrolls */
372 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
374 /* Padding between image and label */
375 #define IMAGE_PADDING 2
377 /* Padding behind the label */
378 #define TRAILING_LABEL_PADDING 12
379 #define TRAILING_HEADER_PADDING 11
381 /* Border for the icon caption */
382 #define CAPTION_BORDER 2
384 /* Standard DrawText flags */
385 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
387 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
389 /* Image index from state */
390 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
392 /* The time in milliseconds to reset the search in the list */
393 #define KEY_DELAY 450
395 /* Dump the LISTVIEW_INFO structure to the debug channel */
396 #define LISTVIEW_DUMP(iP) do { \
397 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
398 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
399 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
400 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
401 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
402 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
403 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
404 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
405 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
406 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
407 } while(0)
409 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
412 * forward declarations
414 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
415 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
416 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
417 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
418 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
419 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
420 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
421 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
422 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
423 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
424 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
425 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
426 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
427 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
428 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
429 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
430 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
431 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
432 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
433 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
434 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
435 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
436 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
437 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
438 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
439 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
440 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
442 /******** Text handling functions *************************************/
444 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
445 * text string. The string may be ANSI or Unicode, in which case
446 * the boolean isW tells us the type of the string.
448 * The name of the function tell what type of strings it expects:
449 * W: Unicode, T: ANSI/Unicode - function of isW
452 static inline BOOL is_textW(LPCWSTR text)
454 return text != NULL && text != LPSTR_TEXTCALLBACKW;
457 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
459 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
460 return is_textW(text);
463 static inline int textlenT(LPCWSTR text, BOOL isW)
465 return !is_textT(text, isW) ? 0 :
466 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
469 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
471 if (isDestW)
472 if (isSrcW) lstrcpynW(dest, src, max);
473 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
474 else
475 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
476 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
479 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
481 LPWSTR wstr = (LPWSTR)text;
483 if (!isW && is_textT(text, isW))
485 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
486 wstr = Alloc(len * sizeof(WCHAR));
487 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
489 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
490 return wstr;
493 static inline void textfreeT(LPWSTR wstr, BOOL isW)
495 if (!isW && is_textT(wstr, isW)) Free (wstr);
499 * dest is a pointer to a Unicode string
500 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
502 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
504 BOOL bResult = TRUE;
506 if (src == LPSTR_TEXTCALLBACKW)
508 if (is_textW(*dest)) Free(*dest);
509 *dest = LPSTR_TEXTCALLBACKW;
511 else
513 LPWSTR pszText = textdupTtoW(src, isW);
514 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
515 bResult = Str_SetPtrW(dest, pszText);
516 textfreeT(pszText, isW);
518 return bResult;
522 * compares a Unicode to a Unicode/ANSI text string
524 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
526 if (!aw) return bt ? -1 : 0;
527 if (!bt) return aw ? 1 : 0;
528 if (aw == LPSTR_TEXTCALLBACKW)
529 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
530 if (bt != LPSTR_TEXTCALLBACKW)
532 LPWSTR bw = textdupTtoW(bt, isW);
533 int r = bw ? lstrcmpW(aw, bw) : 1;
534 textfreeT(bw, isW);
535 return r;
538 return 1;
541 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
543 int res;
545 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
546 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
547 return res ? res - sizeof(WCHAR) : res;
550 /******** Debugging functions *****************************************/
552 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
554 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
555 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
558 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
560 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
561 n = min(textlenT(text, isW), n);
562 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
565 static char* debug_getbuf(void)
567 static int index = 0;
568 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
569 return buffers[index++ % DEBUG_BUFFERS];
572 static inline const char* debugrange(const RANGE *lprng)
574 if (!lprng) return "(null)";
575 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
578 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
580 char* buf = debug_getbuf(), *text = buf;
581 int len, size = DEBUG_BUFFER_SIZE;
583 if (pScrollInfo == NULL) return "(null)";
584 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
585 if (len == -1) goto end; buf += len; size -= len;
586 if (pScrollInfo->fMask & SIF_RANGE)
587 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
588 else len = 0;
589 if (len == -1) goto end; buf += len; size -= len;
590 if (pScrollInfo->fMask & SIF_PAGE)
591 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
592 else len = 0;
593 if (len == -1) goto end; buf += len; size -= len;
594 if (pScrollInfo->fMask & SIF_POS)
595 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
596 else len = 0;
597 if (len == -1) goto end; buf += len; size -= len;
598 if (pScrollInfo->fMask & SIF_TRACKPOS)
599 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
600 else len = 0;
601 if (len == -1) goto end; buf += len; size -= len;
602 goto undo;
603 end:
604 buf = text + strlen(text);
605 undo:
606 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
607 return text;
610 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
612 if (!plvnm) return "(null)";
613 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
614 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
615 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
616 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
619 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
621 char* buf = debug_getbuf(), *text = buf;
622 int len, size = DEBUG_BUFFER_SIZE;
624 if (lpLVItem == NULL) return "(null)";
625 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_STATE)
628 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
629 else len = 0;
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_TEXT)
632 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
633 else len = 0;
634 if (len == -1) goto end; buf += len; size -= len;
635 if (lpLVItem->mask & LVIF_IMAGE)
636 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
637 else len = 0;
638 if (len == -1) goto end; buf += len; size -= len;
639 if (lpLVItem->mask & LVIF_PARAM)
640 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
641 else len = 0;
642 if (len == -1) goto end; buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_INDENT)
644 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
645 else len = 0;
646 if (len == -1) goto end; buf += len; size -= len;
647 goto undo;
648 end:
649 buf = text + strlen(text);
650 undo:
651 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
652 return text;
655 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
657 char* buf = debug_getbuf(), *text = buf;
658 int len, size = DEBUG_BUFFER_SIZE;
660 if (lpColumn == NULL) return "(null)";
661 len = snprintf(buf, size, "{");
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_SUBITEM)
664 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
665 else len = 0;
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_FMT)
668 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
669 else len = 0;
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_WIDTH)
672 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
673 else len = 0;
674 if (len == -1) goto end; buf += len; size -= len;
675 if (lpColumn->mask & LVCF_TEXT)
676 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
677 else len = 0;
678 if (len == -1) goto end; buf += len; size -= len;
679 if (lpColumn->mask & LVCF_IMAGE)
680 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
681 else len = 0;
682 if (len == -1) goto end; buf += len; size -= len;
683 if (lpColumn->mask & LVCF_ORDER)
684 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
685 else len = 0;
686 if (len == -1) goto end; buf += len; size -= len;
687 goto undo;
688 end:
689 buf = text + strlen(text);
690 undo:
691 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
692 return text;
695 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
697 if (!lpht) return "(null)";
699 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
700 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
703 /* Return the corresponding text for a given scroll value */
704 static inline LPCSTR debugscrollcode(int nScrollCode)
706 switch(nScrollCode)
708 case SB_LINELEFT: return "SB_LINELEFT";
709 case SB_LINERIGHT: return "SB_LINERIGHT";
710 case SB_PAGELEFT: return "SB_PAGELEFT";
711 case SB_PAGERIGHT: return "SB_PAGERIGHT";
712 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
713 case SB_THUMBTRACK: return "SB_THUMBTRACK";
714 case SB_ENDSCROLL: return "SB_ENDSCROLL";
715 case SB_INTERNAL: return "SB_INTERNAL";
716 default: return "unknown";
721 /******** Notification functions i************************************/
723 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
725 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
726 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
729 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
731 LRESULT result;
733 TRACE("(code=%d)\n", code);
735 pnmh->hwndFrom = infoPtr->hwndSelf;
736 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
737 pnmh->code = code;
738 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
740 TRACE(" <= %ld\n", result);
742 return result;
745 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
747 NMHDR nmh;
748 HWND hwnd = infoPtr->hwndSelf;
749 notify_hdr(infoPtr, code, &nmh);
750 return IsWindow(hwnd);
753 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
755 NMITEMACTIVATE nmia;
756 LVITEMW item;
758 if (htInfo) {
759 nmia.uNewState = 0;
760 nmia.uOldState = 0;
761 nmia.uChanged = 0;
762 nmia.uKeyFlags = 0;
764 item.mask = LVIF_PARAM|LVIF_STATE;
765 item.iItem = htInfo->iItem;
766 item.iSubItem = 0;
767 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
768 nmia.lParam = item.lParam;
769 nmia.uOldState = item.state;
770 nmia.uNewState = item.state | LVIS_ACTIVATING;
771 nmia.uChanged = LVIF_STATE;
774 nmia.iItem = htInfo->iItem;
775 nmia.iSubItem = htInfo->iSubItem;
776 nmia.ptAction = htInfo->pt;
778 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
779 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
780 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
782 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
785 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
787 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
788 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
791 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
793 NMLISTVIEW nmlv;
794 LVITEMW item;
795 HWND hwnd = infoPtr->hwndSelf;
797 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
798 ZeroMemory(&nmlv, sizeof(nmlv));
799 nmlv.iItem = lvht->iItem;
800 nmlv.iSubItem = lvht->iSubItem;
801 nmlv.ptAction = lvht->pt;
802 item.mask = LVIF_PARAM;
803 item.iItem = lvht->iItem;
804 item.iSubItem = 0;
805 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
806 notify_listview(infoPtr, code, &nmlv);
807 return IsWindow(hwnd);
810 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
812 NMLISTVIEW nmlv;
813 LVITEMW item;
814 HWND hwnd = infoPtr->hwndSelf;
816 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
817 nmlv.iItem = nItem;
818 item.mask = LVIF_PARAM;
819 item.iItem = nItem;
820 item.iSubItem = 0;
821 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
822 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
823 return IsWindow(hwnd);
826 static int get_ansi_notification(UINT unicodeNotificationCode)
828 switch (unicodeNotificationCode)
830 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
831 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
832 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
833 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
834 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
835 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
837 ERR("unknown notification %x\n", unicodeNotificationCode);
838 assert(FALSE);
839 return 0;
843 Send notification. depends on dispinfoW having same
844 structure as dispinfoA.
845 infoPtr : listview struct
846 notificationCode : *Unicode* notification code
847 pdi : dispinfo structure (can be unicode or ansi)
848 isW : TRUE if dispinfo is Unicode
850 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
852 BOOL bResult = FALSE;
853 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
854 INT cchTempBufMax = 0, savCchTextMax = 0;
855 UINT realNotifCode;
856 LPWSTR pszTempBuf = NULL, savPszText = NULL;
858 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
860 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
861 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
864 if (convertToAnsi || convertToUnicode)
866 if (notificationCode != LVN_GETDISPINFOW)
868 cchTempBufMax = convertToUnicode ?
869 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
870 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
872 else
874 cchTempBufMax = pdi->item.cchTextMax;
875 *pdi->item.pszText = 0; /* make sure we don't process garbage */
878 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
879 if (!pszTempBuf) return FALSE;
881 if (convertToUnicode)
882 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
883 pszTempBuf, cchTempBufMax);
884 else
885 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
886 cchTempBufMax, NULL, NULL);
888 savCchTextMax = pdi->item.cchTextMax;
889 savPszText = pdi->item.pszText;
890 pdi->item.pszText = pszTempBuf;
891 pdi->item.cchTextMax = cchTempBufMax;
894 if (infoPtr->notifyFormat == NFR_ANSI)
895 realNotifCode = get_ansi_notification(notificationCode);
896 else
897 realNotifCode = notificationCode;
898 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
899 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
901 if (convertToUnicode || convertToAnsi)
903 if (convertToUnicode) /* note : pointer can be changed by app ! */
904 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
905 savCchTextMax, NULL, NULL);
906 else
907 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
908 savPszText, savCchTextMax);
909 pdi->item.pszText = savPszText; /* restores our buffer */
910 pdi->item.cchTextMax = savCchTextMax;
911 Free (pszTempBuf);
913 return bResult;
916 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
917 const RECT *rcBounds, const LVITEMW *lplvItem)
919 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
920 lpnmlvcd->nmcd.hdc = hdc;
921 lpnmlvcd->nmcd.rc = *rcBounds;
922 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
923 lpnmlvcd->clrText = infoPtr->clrText;
924 if (!lplvItem) return;
925 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
926 lpnmlvcd->iSubItem = lplvItem->iSubItem;
927 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
928 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
929 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
930 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
933 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
935 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
936 DWORD result;
938 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
939 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
940 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
941 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
942 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
943 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
944 return result;
947 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
949 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
950 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
951 if (lpnmlvcd->clrText == CLR_DEFAULT)
952 lpnmlvcd->clrText = comctl32_color.clrWindowText;
954 /* apparently, for selected items, we have to override the returned values */
955 if (!SubItem)
957 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
959 if (infoPtr->bFocus)
961 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
962 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
964 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
966 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
967 lpnmlvcd->clrText = comctl32_color.clrBtnText;
972 /* Set the text attributes */
973 if (lpnmlvcd->clrTextBk != CLR_NONE)
975 SetBkMode(hdc, OPAQUE);
976 SetBkColor(hdc,lpnmlvcd->clrTextBk);
978 else
979 SetBkMode(hdc, TRANSPARENT);
980 SetTextColor(hdc, lpnmlvcd->clrText);
983 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
985 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
988 /******** Item iterator functions **********************************/
990 static RANGES ranges_create(int count);
991 static void ranges_destroy(RANGES ranges);
992 static BOOL ranges_add(RANGES ranges, RANGE range);
993 static BOOL ranges_del(RANGES ranges, RANGE range);
994 static void ranges_dump(RANGES ranges);
996 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
998 RANGE range = { nItem, nItem + 1 };
1000 return ranges_add(ranges, range);
1003 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1005 RANGE range = { nItem, nItem + 1 };
1007 return ranges_del(ranges, range);
1010 /***
1011 * ITERATOR DOCUMENTATION
1013 * The iterator functions allow for easy, and convenient iteration
1014 * over items of interest in the list. Typically, you create a
1015 * iterator, use it, and destroy it, as such:
1016 * ITERATOR i;
1018 * iterator_xxxitems(&i, ...);
1019 * while (iterator_{prev,next}(&i)
1021 * //code which uses i.nItem
1023 * iterator_destroy(&i);
1025 * where xxx is either: framed, or visible.
1026 * Note that it is important that the code destroys the iterator
1027 * after it's done with it, as the creation of the iterator may
1028 * allocate memory, which thus needs to be freed.
1030 * You can iterate both forwards, and backwards through the list,
1031 * by using iterator_next or iterator_prev respectively.
1033 * Lower numbered items are draw on top of higher number items in
1034 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1035 * items may overlap). So, to test items, you should use
1036 * iterator_next
1037 * which lists the items top to bottom (in Z-order).
1038 * For drawing items, you should use
1039 * iterator_prev
1040 * which lists the items bottom to top (in Z-order).
1041 * If you keep iterating over the items after the end-of-items
1042 * marker (-1) is returned, the iterator will start from the
1043 * beginning. Typically, you don't need to test for -1,
1044 * because iterator_{next,prev} will return TRUE if more items
1045 * are to be iterated over, or FALSE otherwise.
1047 * Note: the iterator is defined to be bidirectional. That is,
1048 * any number of prev followed by any number of next, or
1049 * five versa, should leave the iterator at the same item:
1050 * prev * n, next * n = next * n, prev * n
1052 * The iterator has a notion of an out-of-order, special item,
1053 * which sits at the start of the list. This is used in
1054 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1055 * which needs to be first, as it may overlap other items.
1057 * The code is a bit messy because we have:
1058 * - a special item to deal with
1059 * - simple range, or composite range
1060 * - empty range.
1061 * If you find bugs, or want to add features, please make sure you
1062 * always check/modify *both* iterator_prev, and iterator_next.
1065 /****
1066 * This function iterates through the items in increasing order,
1067 * but prefixed by the special item, then -1. That is:
1068 * special, 1, 2, 3, ..., n, -1.
1069 * Each item is listed only once.
1071 static inline BOOL iterator_next(ITERATOR* i)
1073 if (i->nItem == -1)
1075 i->nItem = i->nSpecial;
1076 if (i->nItem != -1) return TRUE;
1078 if (i->nItem == i->nSpecial)
1080 if (i->ranges) i->index = 0;
1081 goto pickarange;
1084 i->nItem++;
1085 testitem:
1086 if (i->nItem == i->nSpecial) i->nItem++;
1087 if (i->nItem < i->range.upper) return TRUE;
1089 pickarange:
1090 if (i->ranges)
1092 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1093 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1094 else goto end;
1096 else if (i->nItem >= i->range.upper) goto end;
1098 i->nItem = i->range.lower;
1099 if (i->nItem >= 0) goto testitem;
1100 end:
1101 i->nItem = -1;
1102 return FALSE;
1105 /****
1106 * This function iterates through the items in decreasing order,
1107 * followed by the special item, then -1. That is:
1108 * n, n-1, ..., 3, 2, 1, special, -1.
1109 * Each item is listed only once.
1111 static inline BOOL iterator_prev(ITERATOR* i)
1113 BOOL start = FALSE;
1115 if (i->nItem == -1)
1117 start = TRUE;
1118 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1119 goto pickarange;
1121 if (i->nItem == i->nSpecial)
1123 i->nItem = -1;
1124 return FALSE;
1127 testitem:
1128 i->nItem--;
1129 if (i->nItem == i->nSpecial) i->nItem--;
1130 if (i->nItem >= i->range.lower) return TRUE;
1132 pickarange:
1133 if (i->ranges)
1135 if (i->index > 0)
1136 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1137 else goto end;
1139 else if (!start && i->nItem < i->range.lower) goto end;
1141 i->nItem = i->range.upper;
1142 if (i->nItem > 0) goto testitem;
1143 end:
1144 return (i->nItem = i->nSpecial) != -1;
1147 static RANGE iterator_range(const ITERATOR *i)
1149 RANGE range;
1151 if (!i->ranges) return i->range;
1153 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1155 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1156 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1158 else range.lower = range.upper = 0;
1160 return range;
1163 /***
1164 * Releases resources associated with this ierator.
1166 static inline void iterator_destroy(const ITERATOR *i)
1168 ranges_destroy(i->ranges);
1171 /***
1172 * Create an empty iterator.
1174 static inline BOOL iterator_empty(ITERATOR* i)
1176 ZeroMemory(i, sizeof(*i));
1177 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1178 return TRUE;
1181 /***
1182 * Create an iterator over a range.
1184 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1186 iterator_empty(i);
1187 i->range = range;
1188 return TRUE;
1191 /***
1192 * Create an iterator over a bunch of ranges.
1193 * Please note that the iterator will take ownership of the ranges,
1194 * and will free them upon destruction.
1196 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1198 iterator_empty(i);
1199 i->ranges = ranges;
1200 return TRUE;
1203 /***
1204 * Creates an iterator over the items which intersect lprc.
1206 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1208 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1209 RECT frame = *lprc, rcItem, rcTemp;
1210 POINT Origin;
1212 /* in case we fail, we want to return an empty iterator */
1213 if (!iterator_empty(i)) return FALSE;
1215 LISTVIEW_GetOrigin(infoPtr, &Origin);
1217 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1218 OffsetRect(&frame, -Origin.x, -Origin.y);
1220 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1222 INT nItem;
1224 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1226 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1227 if (IntersectRect(&rcTemp, &rcItem, lprc))
1228 i->nSpecial = infoPtr->nFocusedItem;
1230 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1231 /* to do better here, we need to have PosX, and PosY sorted */
1232 TRACE("building icon ranges:\n");
1233 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1235 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1236 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1237 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1238 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1239 if (IntersectRect(&rcTemp, &rcItem, &frame))
1240 ranges_additem(i->ranges, nItem);
1242 return TRUE;
1244 else if (uView == LVS_REPORT)
1246 RANGE range;
1248 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1249 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1251 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1252 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1253 if (range.upper <= range.lower) return TRUE;
1254 if (!iterator_rangeitems(i, range)) return FALSE;
1255 TRACE(" report=%s\n", debugrange(&i->range));
1257 else
1259 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1260 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1261 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1262 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1263 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1264 INT lower = nFirstCol * nPerCol + nFirstRow;
1265 RANGE item_range;
1266 INT nCol;
1268 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1269 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1271 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1273 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1274 TRACE("building list ranges:\n");
1275 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1277 item_range.lower = nCol * nPerCol + nFirstRow;
1278 if(item_range.lower >= infoPtr->nItemCount) break;
1279 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1280 TRACE(" list=%s\n", debugrange(&item_range));
1281 ranges_add(i->ranges, item_range);
1285 return TRUE;
1288 /***
1289 * Creates an iterator over the items which intersect the visible region of hdc.
1291 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1293 POINT Origin, Position;
1294 RECT rcItem, rcClip;
1295 INT rgntype;
1297 rgntype = GetClipBox(hdc, &rcClip);
1298 if (rgntype == NULLREGION) return iterator_empty(i);
1299 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1300 if (rgntype == SIMPLEREGION) return TRUE;
1302 /* first deal with the special item */
1303 if (i->nSpecial != -1)
1305 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1306 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1309 /* if we can't deal with the region, we'll just go with the simple range */
1310 LISTVIEW_GetOrigin(infoPtr, &Origin);
1311 TRACE("building visible range:\n");
1312 if (!i->ranges && i->range.lower < i->range.upper)
1314 if (!(i->ranges = ranges_create(50))) return TRUE;
1315 if (!ranges_add(i->ranges, i->range))
1317 ranges_destroy(i->ranges);
1318 i->ranges = 0;
1319 return TRUE;
1323 /* now delete the invisible items from the list */
1324 while(iterator_next(i))
1326 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1327 rcItem.left = Position.x + Origin.x;
1328 rcItem.top = Position.y + Origin.y;
1329 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1330 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1331 if (!RectVisible(hdc, &rcItem))
1332 ranges_delitem(i->ranges, i->nItem);
1334 /* the iterator should restart on the next iterator_next */
1335 TRACE("done\n");
1337 return TRUE;
1340 /******** Misc helper functions ************************************/
1342 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1343 WPARAM wParam, LPARAM lParam, BOOL isW)
1345 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1346 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1349 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1351 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1353 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1354 (uView == LVS_ICON || uView == LVS_SMALLICON);
1357 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1359 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1360 if(state == 1 || state == 2)
1362 LVITEMW lvitem;
1363 state ^= 3;
1364 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1365 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1366 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1370 /******** Internal API functions ************************************/
1372 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1374 static COLUMN_INFO mainItem;
1376 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1377 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1378 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1381 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1383 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1386 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1388 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1391 /* Listview invalidation functions: use _only_ these functions to invalidate */
1393 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1395 return infoPtr->bRedraw;
1398 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1400 if(!is_redrawing(infoPtr)) return;
1401 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1402 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1405 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1407 RECT rcBox;
1409 if(!is_redrawing(infoPtr)) return;
1410 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1411 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1414 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1416 POINT Origin, Position;
1417 RECT rcBox;
1419 if(!is_redrawing(infoPtr)) return;
1420 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1421 LISTVIEW_GetOrigin(infoPtr, &Origin);
1422 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1423 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1424 rcBox.top = 0;
1425 rcBox.bottom = infoPtr->nItemHeight;
1426 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1427 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1430 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1432 LISTVIEW_InvalidateRect(infoPtr, NULL);
1435 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1437 RECT rcCol;
1439 if(!is_redrawing(infoPtr)) return;
1440 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1441 rcCol.top = infoPtr->rcList.top;
1442 rcCol.bottom = infoPtr->rcList.bottom;
1443 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1446 /***
1447 * DESCRIPTION:
1448 * Retrieves the number of items that can fit vertically in the client area.
1450 * PARAMETER(S):
1451 * [I] infoPtr : valid pointer to the listview structure
1453 * RETURN:
1454 * Number of items per row.
1456 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1458 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1460 return max(nListWidth/infoPtr->nItemWidth, 1);
1463 /***
1464 * DESCRIPTION:
1465 * Retrieves the number of items that can fit horizontally in the client
1466 * area.
1468 * PARAMETER(S):
1469 * [I] infoPtr : valid pointer to the listview structure
1471 * RETURN:
1472 * Number of items per column.
1474 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1476 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1478 return max(nListHeight / infoPtr->nItemHeight, 1);
1482 /*************************************************************************
1483 * LISTVIEW_ProcessLetterKeys
1485 * Processes keyboard messages generated by pressing the letter keys
1486 * on the keyboard.
1487 * What this does is perform a case insensitive search from the
1488 * current position with the following quirks:
1489 * - If two chars or more are pressed in quick succession we search
1490 * for the corresponding string (e.g. 'abc').
1491 * - If there is a delay we wipe away the current search string and
1492 * restart with just that char.
1493 * - If the user keeps pressing the same character, whether slowly or
1494 * fast, so that the search string is entirely composed of this
1495 * character ('aaaaa' for instance), then we search for first item
1496 * that starting with that character.
1497 * - If the user types the above character in quick succession, then
1498 * we must also search for the corresponding string ('aaaaa'), and
1499 * go to that string if there is a match.
1501 * PARAMETERS
1502 * [I] hwnd : handle to the window
1503 * [I] charCode : the character code, the actual character
1504 * [I] keyData : key data
1506 * RETURNS
1508 * Zero.
1510 * BUGS
1512 * - The current implementation has a list of characters it will
1513 * accept and it ignores everything else. In particular it will
1514 * ignore accentuated characters which seems to match what
1515 * Windows does. But I'm not sure it makes sense to follow
1516 * Windows there.
1517 * - We don't sound a beep when the search fails.
1519 * SEE ALSO
1521 * TREEVIEW_ProcessLetterKeys
1523 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1525 INT nItem;
1526 INT endidx,idx;
1527 LVITEMW item;
1528 WCHAR buffer[MAX_PATH];
1529 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1531 /* simple parameter checking */
1532 if (!charCode || !keyData) return 0;
1534 /* only allow the valid WM_CHARs through */
1535 if (!isalnum(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 charCode != '/' && charCode != '?' && charCode != '>' &&
1544 charCode != '<' && charCode != ',' && charCode != '~')
1545 return 0;
1547 /* if there's one item or less, there is no where to go */
1548 if (infoPtr->nItemCount <= 1) return 0;
1550 /* update the search parameters */
1551 infoPtr->lastKeyPressTimestamp = GetTickCount();
1552 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1553 if (infoPtr->nSearchParamLength < MAX_PATH)
1554 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1555 if (infoPtr->charCode != charCode)
1556 infoPtr->charCode = charCode = 0;
1557 } else {
1558 infoPtr->charCode=charCode;
1559 infoPtr->szSearchParam[0]=charCode;
1560 infoPtr->nSearchParamLength=1;
1561 /* Redundant with the 1 char string */
1562 charCode=0;
1565 /* and search from the current position */
1566 nItem=-1;
1567 if (infoPtr->nFocusedItem >= 0) {
1568 endidx=infoPtr->nFocusedItem;
1569 idx=endidx;
1570 /* if looking for single character match,
1571 * then we must always move forward
1573 if (infoPtr->nSearchParamLength == 1)
1574 idx++;
1575 } else {
1576 endidx=infoPtr->nItemCount;
1577 idx=0;
1579 do {
1580 if (idx == infoPtr->nItemCount) {
1581 if (endidx == infoPtr->nItemCount || endidx == 0)
1582 break;
1583 idx=0;
1586 /* get item */
1587 item.mask = LVIF_TEXT;
1588 item.iItem = idx;
1589 item.iSubItem = 0;
1590 item.pszText = buffer;
1591 item.cchTextMax = MAX_PATH;
1592 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1594 /* check for a match */
1595 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1596 nItem=idx;
1597 break;
1598 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1599 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1600 /* This would work but we must keep looking for a longer match */
1601 nItem=idx;
1603 idx++;
1604 } while (idx != endidx);
1606 if (nItem != -1)
1607 LISTVIEW_KeySelection(infoPtr, nItem);
1609 return 0;
1612 /*************************************************************************
1613 * LISTVIEW_UpdateHeaderSize [Internal]
1615 * Function to resize the header control
1617 * PARAMS
1618 * [I] hwnd : handle to a window
1619 * [I] nNewScrollPos : scroll pos to set
1621 * RETURNS
1622 * None.
1624 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1626 RECT winRect;
1627 POINT point[2];
1629 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1631 GetWindowRect(infoPtr->hwndHeader, &winRect);
1632 point[0].x = winRect.left;
1633 point[0].y = winRect.top;
1634 point[1].x = winRect.right;
1635 point[1].y = winRect.bottom;
1637 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1638 point[0].x = -nNewScrollPos;
1639 point[1].x += nNewScrollPos;
1641 SetWindowPos(infoPtr->hwndHeader,0,
1642 point[0].x,point[0].y,point[1].x,point[1].y,
1643 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1644 SWP_NOZORDER | SWP_NOACTIVATE);
1647 /***
1648 * DESCRIPTION:
1649 * Update the scrollbars. This functions should be called whenever
1650 * the content, size or view changes.
1652 * PARAMETER(S):
1653 * [I] infoPtr : valid pointer to the listview structure
1655 * RETURN:
1656 * None
1658 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1660 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1661 SCROLLINFO horzInfo, vertInfo;
1662 INT dx, dy;
1664 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1666 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1667 horzInfo.cbSize = sizeof(SCROLLINFO);
1668 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1670 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1671 if (uView == LVS_LIST)
1673 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1674 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1676 /* scroll by at least one column per page */
1677 if(horzInfo.nPage < infoPtr->nItemWidth)
1678 horzInfo.nPage = infoPtr->nItemWidth;
1680 horzInfo.nPage /= infoPtr->nItemWidth;
1682 else if (uView == LVS_REPORT)
1684 horzInfo.nMax = infoPtr->nItemWidth;
1686 else /* LVS_ICON, or LVS_SMALLICON */
1688 RECT rcView;
1690 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1693 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1694 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1695 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1696 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1697 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1699 /* Setting the horizontal scroll can change the listview size
1700 * (and potentially everything else) so we need to recompute
1701 * everything again for the vertical scroll
1704 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1705 vertInfo.cbSize = sizeof(SCROLLINFO);
1706 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1708 if (uView == LVS_REPORT)
1710 vertInfo.nMax = infoPtr->nItemCount;
1712 /* scroll by at least one page */
1713 if(vertInfo.nPage < infoPtr->nItemHeight)
1714 vertInfo.nPage = infoPtr->nItemHeight;
1716 vertInfo.nPage /= infoPtr->nItemHeight;
1718 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1720 RECT rcView;
1722 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1725 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1726 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1727 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1728 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1729 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1731 /* Change of the range may have changed the scroll pos. If so move the content */
1732 if (dx != 0 || dy != 0)
1734 RECT listRect;
1735 listRect = infoPtr->rcList;
1736 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1737 SW_ERASE | SW_INVALIDATE);
1740 /* Update the Header Control */
1741 if (uView == LVS_REPORT)
1743 horzInfo.fMask = SIF_POS;
1744 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1745 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1750 /***
1751 * DESCRIPTION:
1752 * Shows/hides the focus rectangle.
1754 * PARAMETER(S):
1755 * [I] infoPtr : valid pointer to the listview structure
1756 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1758 * RETURN:
1759 * None
1761 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1763 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1764 HDC hdc;
1766 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1768 if (infoPtr->nFocusedItem < 0) return;
1770 /* we need some gymnastics in ICON mode to handle large items */
1771 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1773 RECT rcBox;
1775 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1776 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1778 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1779 return;
1783 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1785 /* for some reason, owner draw should work only in report mode */
1786 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1788 DRAWITEMSTRUCT dis;
1789 LVITEMW item;
1791 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1792 HFONT hOldFont = SelectObject(hdc, hFont);
1794 item.iItem = infoPtr->nFocusedItem;
1795 item.iSubItem = 0;
1796 item.mask = LVIF_PARAM;
1797 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1799 ZeroMemory(&dis, sizeof(dis));
1800 dis.CtlType = ODT_LISTVIEW;
1801 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1802 dis.itemID = item.iItem;
1803 dis.itemAction = ODA_FOCUS;
1804 if (fShow) dis.itemState |= ODS_FOCUS;
1805 dis.hwndItem = infoPtr->hwndSelf;
1806 dis.hDC = hdc;
1807 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1808 dis.itemData = item.lParam;
1810 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1812 SelectObject(hdc, hOldFont);
1814 else
1816 DrawFocusRect(hdc, &infoPtr->rcFocus);
1818 done:
1819 ReleaseDC(infoPtr->hwndSelf, hdc);
1822 /***
1823 * Invalidates all visible selected items.
1825 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1827 ITERATOR i;
1829 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1830 while(iterator_next(&i))
1832 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1833 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1835 iterator_destroy(&i);
1839 /***
1840 * DESCRIPTION: [INTERNAL]
1841 * Computes an item's (left,top) corner, relative to rcView.
1842 * That is, the position has NOT been made relative to the Origin.
1843 * This is deliberate, to avoid computing the Origin over, and
1844 * over again, when this function is called in a loop. Instead,
1845 * one can factor the computation of the Origin before the loop,
1846 * and offset the value returned by this function, on every iteration.
1848 * PARAMETER(S):
1849 * [I] infoPtr : valid pointer to the listview structure
1850 * [I] nItem : item number
1851 * [O] lpptOrig : item top, left corner
1853 * RETURN:
1854 * None.
1856 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1858 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1860 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1862 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1864 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1865 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1867 else if (uView == LVS_LIST)
1869 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1870 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1871 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1873 else /* LVS_REPORT */
1875 lpptPosition->x = 0;
1876 lpptPosition->y = nItem * infoPtr->nItemHeight;
1880 /***
1881 * DESCRIPTION: [INTERNAL]
1882 * Compute the rectangles of an item. This is to localize all
1883 * the computations in one place. If you are not interested in some
1884 * of these values, simply pass in a NULL -- the function is smart
1885 * enough to compute only what's necessary. The function computes
1886 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1887 * one, the BOX rectangle. This rectangle is very cheap to compute,
1888 * and is guaranteed to contain all the other rectangles. Computing
1889 * the ICON rect is also cheap, but all the others are potentially
1890 * expensive. This gives an easy and effective optimization when
1891 * searching (like point inclusion, or rectangle intersection):
1892 * first test against the BOX, and if TRUE, test against the desired
1893 * rectangle.
1894 * If the function does not have all the necessary information
1895 * to computed the requested rectangles, will crash with a
1896 * failed assertion. This is done so we catch all programming
1897 * errors, given that the function is called only from our code.
1899 * We have the following 'special' meanings for a few fields:
1900 * * If LVIS_FOCUSED is set, we assume the item has the focus
1901 * This is important in ICON mode, where it might get a larger
1902 * then usual rectangle
1904 * Please note that subitem support works only in REPORT mode.
1906 * PARAMETER(S):
1907 * [I] infoPtr : valid pointer to the listview structure
1908 * [I] lpLVItem : item to compute the measures for
1909 * [O] lprcBox : ptr to Box rectangle
1910 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1911 * [0] lprcSelectBox : ptr to select box rectangle
1912 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1913 * [O] lprcIcon : ptr to Icon rectangle
1914 * Same as LVM_GETITEMRECT with LVIR_ICON
1915 * [O] lprcStateIcon: ptr to State Icon rectangle
1916 * [O] lprcLabel : ptr to Label rectangle
1917 * Same as LVM_GETITEMRECT with LVIR_LABEL
1919 * RETURN:
1920 * None.
1922 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1923 LPRECT lprcBox, LPRECT lprcSelectBox,
1924 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1926 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1927 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1928 RECT Box, SelectBox, Icon, Label;
1929 COLUMN_INFO *lpColumnInfo = NULL;
1930 SIZE labelSize = { 0, 0 };
1932 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1934 /* Be smart and try to figure out the minimum we have to do */
1935 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1936 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1938 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1939 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1941 if (lprcSelectBox) doSelectBox = TRUE;
1942 if (lprcLabel) doLabel = TRUE;
1943 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1944 if (doSelectBox)
1946 doIcon = TRUE;
1947 doLabel = TRUE;
1950 /************************************************************/
1951 /* compute the box rectangle (it should be cheap to do) */
1952 /************************************************************/
1953 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1954 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1956 if (lpLVItem->iSubItem)
1958 Box = lpColumnInfo->rcHeader;
1960 else
1962 Box.left = 0;
1963 Box.right = infoPtr->nItemWidth;
1965 Box.top = 0;
1966 Box.bottom = infoPtr->nItemHeight;
1968 /******************************************************************/
1969 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1970 /******************************************************************/
1971 if (doIcon)
1973 LONG state_width = 0;
1975 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1976 state_width = infoPtr->iconStateSize.cx;
1978 if (uView == LVS_ICON)
1980 Icon.left = Box.left + state_width;
1981 if (infoPtr->himlNormal)
1982 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1983 Icon.top = Box.top + ICON_TOP_PADDING;
1984 Icon.right = Icon.left;
1985 Icon.bottom = Icon.top;
1986 if (infoPtr->himlNormal)
1988 Icon.right += infoPtr->iconSize.cx;
1989 Icon.bottom += infoPtr->iconSize.cy;
1992 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1994 Icon.left = Box.left + state_width;
1996 if (uView == LVS_REPORT)
1997 Icon.left += REPORT_MARGINX;
1999 Icon.top = Box.top;
2000 Icon.right = Icon.left;
2001 if (infoPtr->himlSmall &&
2002 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2003 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2004 Icon.right += infoPtr->iconSize.cx;
2005 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2007 if(lprcIcon) *lprcIcon = Icon;
2008 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2010 /* TODO: is this correct? */
2011 if (lprcStateIcon)
2013 lprcStateIcon->left = Icon.left - state_width;
2014 lprcStateIcon->right = Icon.left;
2015 lprcStateIcon->top = Icon.top;
2016 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2017 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2020 else Icon.right = 0;
2022 /************************************************************/
2023 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2024 /************************************************************/
2025 if (doLabel)
2027 /* calculate how far to the right can the label stretch */
2028 Label.right = Box.right;
2029 if (uView == LVS_REPORT)
2031 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2034 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2036 labelSize.cx = infoPtr->nItemWidth;
2037 labelSize.cy = infoPtr->nItemHeight;
2038 goto calc_label;
2041 /* we need the text in non owner draw mode */
2042 assert(lpLVItem->mask & LVIF_TEXT);
2043 if (is_textT(lpLVItem->pszText, TRUE))
2045 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2046 HDC hdc = GetDC(infoPtr->hwndSelf);
2047 HFONT hOldFont = SelectObject(hdc, hFont);
2048 UINT uFormat;
2049 RECT rcText;
2051 /* compute rough rectangle where the label will go */
2052 SetRectEmpty(&rcText);
2053 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2054 rcText.bottom = infoPtr->nItemHeight;
2055 if (uView == LVS_ICON)
2056 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2058 /* now figure out the flags */
2059 if (uView == LVS_ICON)
2060 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2061 else
2062 uFormat = LV_SL_DT_FLAGS;
2064 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2066 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2067 labelSize.cy = rcText.bottom - rcText.top;
2069 SelectObject(hdc, hOldFont);
2070 ReleaseDC(infoPtr->hwndSelf, hdc);
2073 calc_label:
2074 if (uView == LVS_ICON)
2076 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2077 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2078 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2079 Label.right = Label.left + labelSize.cx;
2080 Label.bottom = Label.top + infoPtr->nItemHeight;
2081 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2083 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2084 labelSize.cy /= infoPtr->ntmHeight;
2085 labelSize.cy = max(labelSize.cy, 1);
2086 labelSize.cy *= infoPtr->ntmHeight;
2088 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2090 else if (uView == LVS_REPORT)
2092 Label.left = Icon.right;
2093 Label.top = Box.top;
2094 Label.right = lpColumnInfo->rcHeader.right;
2095 Label.bottom = Label.top + infoPtr->nItemHeight;
2097 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2099 Label.left = Icon.right;
2100 Label.top = Box.top;
2101 Label.right = min(Label.left + labelSize.cx, Label.right);
2102 Label.bottom = Label.top + infoPtr->nItemHeight;
2105 if (lprcLabel) *lprcLabel = Label;
2106 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2109 /************************************************************/
2110 /* compute STATEICON bounding box */
2111 /************************************************************/
2112 if (doSelectBox)
2114 if (uView == LVS_REPORT)
2116 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2117 SelectBox.top = Box.top;
2118 SelectBox.bottom = Box.bottom;
2119 if (lpLVItem->iSubItem == 0)
2121 /* we need the indent in report mode */
2122 assert(lpLVItem->mask & LVIF_INDENT);
2123 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2125 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2127 else
2129 UnionRect(&SelectBox, &Icon, &Label);
2131 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2132 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2135 /* Fix the Box if necessary */
2136 if (lprcBox)
2138 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2139 else *lprcBox = Box;
2141 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2144 /***
2145 * DESCRIPTION: [INTERNAL]
2147 * PARAMETER(S):
2148 * [I] infoPtr : valid pointer to the listview structure
2149 * [I] nItem : item number
2150 * [O] lprcBox : ptr to Box rectangle
2152 * RETURN:
2153 * None.
2155 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2157 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2158 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2159 POINT Position, Origin;
2160 LVITEMW lvItem;
2162 LISTVIEW_GetOrigin(infoPtr, &Origin);
2163 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2165 /* Be smart and try to figure out the minimum we have to do */
2166 lvItem.mask = 0;
2167 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2168 lvItem.mask |= LVIF_TEXT;
2169 lvItem.iItem = nItem;
2170 lvItem.iSubItem = 0;
2171 lvItem.pszText = szDispText;
2172 lvItem.cchTextMax = DISP_TEXT_SIZE;
2173 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2174 if (uView == LVS_ICON)
2176 lvItem.mask |= LVIF_STATE;
2177 lvItem.stateMask = LVIS_FOCUSED;
2178 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2180 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2182 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2186 /***
2187 * DESCRIPTION:
2188 * Returns the current icon position, and advances it along the top.
2189 * The returned position is not offset by Origin.
2191 * PARAMETER(S):
2192 * [I] infoPtr : valid pointer to the listview structure
2193 * [O] lpPos : will get the current icon position
2195 * RETURN:
2196 * None
2198 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2200 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2202 *lpPos = infoPtr->currIconPos;
2204 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2205 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2207 infoPtr->currIconPos.x = 0;
2208 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2212 /***
2213 * DESCRIPTION:
2214 * Returns the current icon position, and advances it down the left edge.
2215 * The returned position is not offset by Origin.
2217 * PARAMETER(S):
2218 * [I] infoPtr : valid pointer to the listview structure
2219 * [O] lpPos : will get the current icon position
2221 * RETURN:
2222 * None
2224 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2226 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2228 *lpPos = infoPtr->currIconPos;
2230 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2231 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2233 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2234 infoPtr->currIconPos.y = 0;
2238 /***
2239 * DESCRIPTION:
2240 * Moves an icon to the specified position.
2241 * It takes care of invalidating the item, etc.
2243 * PARAMETER(S):
2244 * [I] infoPtr : valid pointer to the listview structure
2245 * [I] nItem : the item to move
2246 * [I] lpPos : the new icon position
2247 * [I] isNew : flags the item as being new
2249 * RETURN:
2250 * Success: TRUE
2251 * Failure: FALSE
2253 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2255 POINT old;
2257 if (!isNew)
2259 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2260 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2262 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2263 LISTVIEW_InvalidateItem(infoPtr, nItem);
2266 /* Allocating a POINTER for every item is too resource intensive,
2267 * so we'll keep the (x,y) in different arrays */
2268 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2269 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2271 LISTVIEW_InvalidateItem(infoPtr, nItem);
2273 return TRUE;
2276 /***
2277 * DESCRIPTION:
2278 * Arranges listview items in icon display mode.
2280 * PARAMETER(S):
2281 * [I] infoPtr : valid pointer to the listview structure
2282 * [I] nAlignCode : alignment code
2284 * RETURN:
2285 * SUCCESS : TRUE
2286 * FAILURE : FALSE
2288 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2290 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2291 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2292 POINT pos;
2293 INT i;
2295 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2297 TRACE("nAlignCode=%d\n", nAlignCode);
2299 if (nAlignCode == LVA_DEFAULT)
2301 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2302 else nAlignCode = LVA_ALIGNTOP;
2305 switch (nAlignCode)
2307 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2308 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2309 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2310 default: return FALSE;
2313 infoPtr->bAutoarrange = TRUE;
2314 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2315 for (i = 0; i < infoPtr->nItemCount; i++)
2317 next_pos(infoPtr, &pos);
2318 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2321 return TRUE;
2324 /***
2325 * DESCRIPTION:
2326 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2328 * PARAMETER(S):
2329 * [I] infoPtr : valid pointer to the listview structure
2330 * [O] lprcView : bounding rectangle
2332 * RETURN:
2333 * SUCCESS : TRUE
2334 * FAILURE : FALSE
2336 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2338 INT i, x, y;
2340 SetRectEmpty(lprcView);
2342 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2344 case LVS_ICON:
2345 case LVS_SMALLICON:
2346 for (i = 0; i < infoPtr->nItemCount; i++)
2348 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2349 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2350 lprcView->right = max(lprcView->right, x);
2351 lprcView->bottom = max(lprcView->bottom, y);
2353 if (infoPtr->nItemCount > 0)
2355 lprcView->right += infoPtr->nItemWidth;
2356 lprcView->bottom += infoPtr->nItemHeight;
2358 break;
2360 case LVS_LIST:
2361 y = LISTVIEW_GetCountPerColumn(infoPtr);
2362 x = infoPtr->nItemCount / y;
2363 if (infoPtr->nItemCount % y) x++;
2364 lprcView->right = x * infoPtr->nItemWidth;
2365 lprcView->bottom = y * infoPtr->nItemHeight;
2366 break;
2368 case LVS_REPORT:
2369 lprcView->right = infoPtr->nItemWidth;
2370 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2371 break;
2375 /***
2376 * DESCRIPTION:
2377 * Retrieves the bounding rectangle of all the items.
2379 * PARAMETER(S):
2380 * [I] infoPtr : valid pointer to the listview structure
2381 * [O] lprcView : bounding rectangle
2383 * RETURN:
2384 * SUCCESS : TRUE
2385 * FAILURE : FALSE
2387 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2389 POINT ptOrigin;
2391 TRACE("(lprcView=%p)\n", lprcView);
2393 if (!lprcView) return FALSE;
2395 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2396 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2397 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2399 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2401 return TRUE;
2404 /***
2405 * DESCRIPTION:
2406 * Retrieves the subitem pointer associated with the subitem index.
2408 * PARAMETER(S):
2409 * [I] hdpaSubItems : DPA handle for a specific item
2410 * [I] nSubItem : index of subitem
2412 * RETURN:
2413 * SUCCESS : subitem pointer
2414 * FAILURE : NULL
2416 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2418 SUBITEM_INFO *lpSubItem;
2419 INT i;
2421 /* we should binary search here if need be */
2422 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2424 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2425 if (lpSubItem->iSubItem == nSubItem)
2426 return lpSubItem;
2429 return NULL;
2433 /***
2434 * DESCRIPTION:
2435 * Calculates the desired item width.
2437 * PARAMETER(S):
2438 * [I] infoPtr : valid pointer to the listview structure
2440 * RETURN:
2441 * The desired item width.
2443 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2445 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2446 INT nItemWidth = 0;
2448 TRACE("uView=%d\n", uView);
2450 if (uView == LVS_ICON)
2451 nItemWidth = infoPtr->iconSpacing.cx;
2452 else if (uView == LVS_REPORT)
2454 RECT rcHeader;
2456 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2458 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2459 nItemWidth = rcHeader.right;
2462 else /* LVS_SMALLICON, or LVS_LIST */
2464 INT i;
2466 for (i = 0; i < infoPtr->nItemCount; i++)
2467 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2469 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2470 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2472 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2475 return max(nItemWidth, 1);
2478 /***
2479 * DESCRIPTION:
2480 * Calculates the desired item height.
2482 * PARAMETER(S):
2483 * [I] infoPtr : valid pointer to the listview structure
2485 * RETURN:
2486 * The desired item height.
2488 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2490 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2491 INT nItemHeight;
2493 TRACE("uView=%d\n", uView);
2495 if (uView == LVS_ICON)
2496 nItemHeight = infoPtr->iconSpacing.cy;
2497 else
2499 nItemHeight = infoPtr->ntmHeight;
2500 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2501 nItemHeight++;
2502 if (infoPtr->himlState)
2503 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2504 if (infoPtr->himlSmall)
2505 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2506 if (infoPtr->himlState || infoPtr->himlSmall)
2507 nItemHeight += HEIGHT_PADDING;
2508 if (infoPtr->nMeasureItemHeight > 0)
2509 nItemHeight = infoPtr->nMeasureItemHeight;
2512 return max(nItemHeight, 1);
2515 /***
2516 * DESCRIPTION:
2517 * Updates the width, and height of an item.
2519 * PARAMETER(S):
2520 * [I] infoPtr : valid pointer to the listview structure
2522 * RETURN:
2523 * None.
2525 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2527 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2528 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2532 /***
2533 * DESCRIPTION:
2534 * Retrieves and saves important text metrics info for the current
2535 * Listview font.
2537 * PARAMETER(S):
2538 * [I] infoPtr : valid pointer to the listview structure
2541 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2543 HDC hdc = GetDC(infoPtr->hwndSelf);
2544 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2545 HFONT hOldFont = SelectObject(hdc, hFont);
2546 TEXTMETRICW tm;
2547 SIZE sz;
2549 if (GetTextMetricsW(hdc, &tm))
2551 infoPtr->ntmHeight = tm.tmHeight;
2552 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2555 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2556 infoPtr->nEllipsisWidth = sz.cx;
2558 SelectObject(hdc, hOldFont);
2559 ReleaseDC(infoPtr->hwndSelf, hdc);
2561 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2564 /***
2565 * DESCRIPTION:
2566 * A compare function for ranges
2568 * PARAMETER(S)
2569 * [I] range1 : pointer to range 1;
2570 * [I] range2 : pointer to range 2;
2571 * [I] flags : flags
2573 * RETURNS:
2574 * > 0 : if range 1 > range 2
2575 * < 0 : if range 2 > range 1
2576 * = 0 : if range intersects range 2
2578 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2580 INT cmp;
2582 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2583 cmp = -1;
2584 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2585 cmp = 1;
2586 else
2587 cmp = 0;
2589 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2591 return cmp;
2594 #if DEBUG_RANGES
2595 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2596 #else
2597 #define ranges_check(ranges, desc) do { } while(0)
2598 #endif
2600 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2602 INT i;
2603 RANGE *prev, *curr;
2605 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2606 assert (ranges);
2607 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2608 ranges_dump(ranges);
2609 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2610 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2611 assert (prev->lower >= 0 && prev->lower < prev->upper);
2612 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2614 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2615 assert (prev->upper <= curr->lower);
2616 assert (curr->lower < curr->upper);
2617 prev = curr;
2619 TRACE("--- Done checking---\n");
2622 static RANGES ranges_create(int count)
2624 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2625 if (!ranges) return NULL;
2626 ranges->hdpa = DPA_Create(count);
2627 if (ranges->hdpa) return ranges;
2628 Free(ranges);
2629 return NULL;
2632 static void ranges_clear(RANGES ranges)
2634 INT i;
2636 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2637 Free(DPA_GetPtr(ranges->hdpa, i));
2638 DPA_DeleteAllPtrs(ranges->hdpa);
2642 static void ranges_destroy(RANGES ranges)
2644 if (!ranges) return;
2645 ranges_clear(ranges);
2646 DPA_Destroy(ranges->hdpa);
2647 Free(ranges);
2650 static RANGES ranges_clone(RANGES ranges)
2652 RANGES clone;
2653 INT i;
2655 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2657 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2659 RANGE *newrng = Alloc(sizeof(RANGE));
2660 if (!newrng) goto fail;
2661 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2662 DPA_SetPtr(clone->hdpa, i, newrng);
2664 return clone;
2666 fail:
2667 TRACE ("clone failed\n");
2668 ranges_destroy(clone);
2669 return NULL;
2672 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2674 INT i;
2676 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2677 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2679 return ranges;
2682 static void ranges_dump(RANGES ranges)
2684 INT i;
2686 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2687 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2690 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2692 RANGE srchrng = { nItem, nItem + 1 };
2694 TRACE("(nItem=%d)\n", nItem);
2695 ranges_check(ranges, "before contain");
2696 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2699 static INT ranges_itemcount(RANGES ranges)
2701 INT i, count = 0;
2703 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2705 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2706 count += sel->upper - sel->lower;
2709 return count;
2712 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2714 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2715 INT index;
2717 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2718 if (index == -1) return TRUE;
2720 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2722 chkrng = DPA_GetPtr(ranges->hdpa, index);
2723 if (chkrng->lower >= nItem)
2724 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2725 if (chkrng->upper > nItem)
2726 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2728 return TRUE;
2731 static BOOL ranges_add(RANGES ranges, RANGE range)
2733 RANGE srchrgn;
2734 INT index;
2736 TRACE("(%s)\n", debugrange(&range));
2737 ranges_check(ranges, "before add");
2739 /* try find overlapping regions first */
2740 srchrgn.lower = range.lower - 1;
2741 srchrgn.upper = range.upper + 1;
2742 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2744 if (index == -1)
2746 RANGE *newrgn;
2748 TRACE("Adding new range\n");
2750 /* create the brand new range to insert */
2751 newrgn = Alloc(sizeof(RANGE));
2752 if(!newrgn) goto fail;
2753 *newrgn = range;
2755 /* figure out where to insert it */
2756 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2757 TRACE("index=%d\n", index);
2758 if (index == -1) index = 0;
2760 /* and get it over with */
2761 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2763 Free(newrgn);
2764 goto fail;
2767 else
2769 RANGE *chkrgn, *mrgrgn;
2770 INT fromindex, mergeindex;
2772 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2773 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2775 chkrgn->lower = min(range.lower, chkrgn->lower);
2776 chkrgn->upper = max(range.upper, chkrgn->upper);
2778 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2780 /* merge now common ranges */
2781 fromindex = 0;
2782 srchrgn.lower = chkrgn->lower - 1;
2783 srchrgn.upper = chkrgn->upper + 1;
2787 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2788 if (mergeindex == -1) break;
2789 if (mergeindex == index)
2791 fromindex = index + 1;
2792 continue;
2795 TRACE("Merge with index %i\n", mergeindex);
2797 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2798 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2799 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2800 Free(mrgrgn);
2801 DPA_DeletePtr(ranges->hdpa, mergeindex);
2802 if (mergeindex < index) index --;
2803 } while(1);
2806 ranges_check(ranges, "after add");
2807 return TRUE;
2809 fail:
2810 ranges_check(ranges, "failed add");
2811 return FALSE;
2814 static BOOL ranges_del(RANGES ranges, RANGE range)
2816 RANGE *chkrgn;
2817 INT index;
2819 TRACE("(%s)\n", debugrange(&range));
2820 ranges_check(ranges, "before del");
2822 /* we don't use DPAS_SORTED here, since we need *
2823 * to find the first overlapping range */
2824 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2825 while(index != -1)
2827 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2829 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2831 /* case 1: Same range */
2832 if ( (chkrgn->upper == range.upper) &&
2833 (chkrgn->lower == range.lower) )
2835 DPA_DeletePtr(ranges->hdpa, index);
2836 break;
2838 /* case 2: engulf */
2839 else if ( (chkrgn->upper <= range.upper) &&
2840 (chkrgn->lower >= range.lower) )
2842 DPA_DeletePtr(ranges->hdpa, index);
2844 /* case 3: overlap upper */
2845 else if ( (chkrgn->upper <= range.upper) &&
2846 (chkrgn->lower < range.lower) )
2848 chkrgn->upper = range.lower;
2850 /* case 4: overlap lower */
2851 else if ( (chkrgn->upper > range.upper) &&
2852 (chkrgn->lower >= range.lower) )
2854 chkrgn->lower = range.upper;
2855 break;
2857 /* case 5: fully internal */
2858 else
2860 RANGE tmprgn = *chkrgn, *newrgn;
2862 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2863 newrgn->lower = chkrgn->lower;
2864 newrgn->upper = range.lower;
2865 chkrgn->lower = range.upper;
2866 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2868 Free(newrgn);
2869 goto fail;
2871 chkrgn = &tmprgn;
2872 break;
2875 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2878 ranges_check(ranges, "after del");
2879 return TRUE;
2881 fail:
2882 ranges_check(ranges, "failed del");
2883 return FALSE;
2886 /***
2887 * DESCRIPTION:
2888 * Removes all selection ranges
2890 * Parameters(s):
2891 * [I] infoPtr : valid pointer to the listview structure
2892 * [I] toSkip : item range to skip removing the selection
2894 * RETURNS:
2895 * SUCCESS : TRUE
2896 * FAILURE : TRUE
2898 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2900 LVITEMW lvItem;
2901 ITERATOR i;
2902 RANGES clone;
2904 TRACE("()\n");
2906 lvItem.state = 0;
2907 lvItem.stateMask = LVIS_SELECTED;
2909 /* need to clone the DPA because callbacks can change it */
2910 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2911 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2912 while(iterator_next(&i))
2913 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2914 /* note that the iterator destructor will free the cloned range */
2915 iterator_destroy(&i);
2917 return TRUE;
2920 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2922 RANGES toSkip;
2924 if (!(toSkip = ranges_create(1))) return FALSE;
2925 if (nItem != -1) ranges_additem(toSkip, nItem);
2926 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2927 ranges_destroy(toSkip);
2928 return TRUE;
2931 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2933 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2936 /***
2937 * DESCRIPTION:
2938 * Retrieves the number of items that are marked as selected.
2940 * PARAMETER(S):
2941 * [I] infoPtr : valid pointer to the listview structure
2943 * RETURN:
2944 * Number of items selected.
2946 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2948 INT nSelectedCount = 0;
2950 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2952 INT i;
2953 for (i = 0; i < infoPtr->nItemCount; i++)
2955 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2956 nSelectedCount++;
2959 else
2960 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2962 TRACE("nSelectedCount=%d\n", nSelectedCount);
2963 return nSelectedCount;
2966 /***
2967 * DESCRIPTION:
2968 * Manages the item focus.
2970 * PARAMETER(S):
2971 * [I] infoPtr : valid pointer to the listview structure
2972 * [I] nItem : item index
2974 * RETURN:
2975 * TRUE : focused item changed
2976 * FALSE : focused item has NOT changed
2978 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2980 INT oldFocus = infoPtr->nFocusedItem;
2981 LVITEMW lvItem;
2983 if (nItem == infoPtr->nFocusedItem) return FALSE;
2985 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2986 lvItem.stateMask = LVIS_FOCUSED;
2987 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2989 return oldFocus != infoPtr->nFocusedItem;
2992 /* Helper function for LISTVIEW_ShiftIndices *only* */
2993 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2995 if (nShiftItem < nItem) return nShiftItem;
2997 if (nShiftItem > nItem) return nShiftItem + direction;
2999 if (direction > 0) return nShiftItem + direction;
3001 return min(nShiftItem, infoPtr->nItemCount - 1);
3005 * DESCRIPTION:
3006 * Updates the various indices after an item has been inserted or deleted.
3008 * PARAMETER(S):
3009 * [I] infoPtr : valid pointer to the listview structure
3010 * [I] nItem : item index
3011 * [I] direction : Direction of shift, +1 or -1.
3013 * RETURN:
3014 * None
3016 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3018 INT nNewFocus;
3019 BOOL bOldChange;
3021 /* temporarily disable change notification while shifting items */
3022 bOldChange = infoPtr->bDoChangeNotify;
3023 infoPtr->bDoChangeNotify = FALSE;
3025 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3027 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3029 assert(abs(direction) == 1);
3031 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3033 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3034 if (nNewFocus != infoPtr->nFocusedItem)
3035 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3037 /* But we are not supposed to modify nHotItem! */
3039 infoPtr->bDoChangeNotify = bOldChange;
3044 * DESCRIPTION:
3045 * Adds a block of selections.
3047 * PARAMETER(S):
3048 * [I] infoPtr : valid pointer to the listview structure
3049 * [I] nItem : item index
3051 * RETURN:
3052 * Whether the window is still valid.
3054 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3056 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3057 INT nLast = max(infoPtr->nSelectionMark, nItem);
3058 HWND hwndSelf = infoPtr->hwndSelf;
3059 NMLVODSTATECHANGE nmlv;
3060 LVITEMW item;
3061 BOOL bOldChange;
3062 INT i;
3064 /* Temporarily disable change notification
3065 * If the control is LVS_OWNERDATA, we need to send
3066 * only one LVN_ODSTATECHANGED notification.
3067 * See MSDN documentation for LVN_ITEMCHANGED.
3069 bOldChange = infoPtr->bDoChangeNotify;
3070 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3072 if (nFirst == -1) nFirst = nItem;
3074 item.state = LVIS_SELECTED;
3075 item.stateMask = LVIS_SELECTED;
3077 for (i = nFirst; i <= nLast; i++)
3078 LISTVIEW_SetItemState(infoPtr,i,&item);
3080 ZeroMemory(&nmlv, sizeof(nmlv));
3081 nmlv.iFrom = nFirst;
3082 nmlv.iTo = nLast;
3083 nmlv.uNewState = 0;
3084 nmlv.uOldState = item.state;
3086 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3087 if (!IsWindow(hwndSelf))
3088 return FALSE;
3089 infoPtr->bDoChangeNotify = bOldChange;
3090 return TRUE;
3094 /***
3095 * DESCRIPTION:
3096 * Sets a single group selection.
3098 * PARAMETER(S):
3099 * [I] infoPtr : valid pointer to the listview structure
3100 * [I] nItem : item index
3102 * RETURN:
3103 * None
3105 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3107 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3108 RANGES selection;
3109 LVITEMW item;
3110 ITERATOR i;
3111 BOOL bOldChange;
3113 if (!(selection = ranges_create(100))) return;
3115 item.state = LVIS_SELECTED;
3116 item.stateMask = LVIS_SELECTED;
3118 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3120 if (infoPtr->nSelectionMark == -1)
3122 infoPtr->nSelectionMark = nItem;
3123 ranges_additem(selection, nItem);
3125 else
3127 RANGE sel;
3129 sel.lower = min(infoPtr->nSelectionMark, nItem);
3130 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3131 ranges_add(selection, sel);
3134 else
3136 RECT rcItem, rcSel, rcSelMark;
3137 POINT ptItem;
3139 rcItem.left = LVIR_BOUNDS;
3140 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3141 rcSelMark.left = LVIR_BOUNDS;
3142 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3143 UnionRect(&rcSel, &rcItem, &rcSelMark);
3144 iterator_frameditems(&i, infoPtr, &rcSel);
3145 while(iterator_next(&i))
3147 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3148 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3150 iterator_destroy(&i);
3153 bOldChange = infoPtr->bDoChangeNotify;
3154 infoPtr->bDoChangeNotify = FALSE;
3156 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3159 iterator_rangesitems(&i, selection);
3160 while(iterator_next(&i))
3161 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3162 /* this will also destroy the selection */
3163 iterator_destroy(&i);
3165 infoPtr->bDoChangeNotify = bOldChange;
3167 LISTVIEW_SetItemFocus(infoPtr, nItem);
3170 /***
3171 * DESCRIPTION:
3172 * Sets a single selection.
3174 * PARAMETER(S):
3175 * [I] infoPtr : valid pointer to the listview structure
3176 * [I] nItem : item index
3178 * RETURN:
3179 * None
3181 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3183 LVITEMW lvItem;
3185 TRACE("nItem=%d\n", nItem);
3187 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3189 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3190 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3191 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3193 infoPtr->nSelectionMark = nItem;
3196 /***
3197 * DESCRIPTION:
3198 * Set selection(s) with keyboard.
3200 * PARAMETER(S):
3201 * [I] infoPtr : valid pointer to the listview structure
3202 * [I] nItem : item index
3204 * RETURN:
3205 * SUCCESS : TRUE (needs to be repainted)
3206 * FAILURE : FALSE (nothing has changed)
3208 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3210 /* FIXME: pass in the state */
3211 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3212 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3213 BOOL bResult = FALSE;
3215 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3216 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3218 if (infoPtr->dwStyle & LVS_SINGLESEL)
3220 bResult = TRUE;
3221 LISTVIEW_SetSelection(infoPtr, nItem);
3223 else
3225 if (wShift)
3227 bResult = TRUE;
3228 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3230 else if (wCtrl)
3232 LVITEMW lvItem;
3233 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3234 lvItem.stateMask = LVIS_SELECTED;
3235 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3237 if (lvItem.state & LVIS_SELECTED)
3238 infoPtr->nSelectionMark = nItem;
3240 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3242 else
3244 bResult = TRUE;
3245 LISTVIEW_SetSelection(infoPtr, nItem);
3248 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3251 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3252 return bResult;
3255 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3257 LVHITTESTINFO lvHitTestInfo;
3259 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3260 lvHitTestInfo.pt.x = pt.x;
3261 lvHitTestInfo.pt.y = pt.y;
3263 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3265 lpLVItem->mask = LVIF_PARAM;
3266 lpLVItem->iItem = lvHitTestInfo.iItem;
3267 lpLVItem->iSubItem = 0;
3269 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3272 /***
3273 * DESCRIPTION:
3274 * Called when the mouse is being actively tracked and has hovered for a specified
3275 * amount of time
3277 * PARAMETER(S):
3278 * [I] infoPtr : valid pointer to the listview structure
3279 * [I] fwKeys : key indicator
3280 * [I] x,y : mouse position
3282 * RETURN:
3283 * 0 if the message was processed, non-zero if there was an error
3285 * INFO:
3286 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3287 * over the item for a certain period of time.
3290 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3292 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3294 LVITEMW item;
3295 POINT pt;
3297 pt.x = x;
3298 pt.y = y;
3300 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3301 LISTVIEW_SetSelection(infoPtr, item.iItem);
3304 return 0;
3307 /***
3308 * DESCRIPTION:
3309 * Called whenever WM_MOUSEMOVE is received.
3311 * PARAMETER(S):
3312 * [I] infoPtr : valid pointer to the listview structure
3313 * [I] fwKeys : key indicator
3314 * [I] x,y : mouse position
3316 * RETURN:
3317 * 0 if the message is processed, non-zero if there was an error
3319 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3321 TRACKMOUSEEVENT trackinfo;
3323 if (!(fwKeys & MK_LBUTTON))
3324 infoPtr->bLButtonDown = FALSE;
3326 if (infoPtr->bLButtonDown)
3328 MSG msg;
3329 BOOL skip = FALSE;
3330 /* Check to see if we got a WM_LBUTTONUP, and skip the DragDetect.
3331 * Otherwise, DragDetect will eat it.
3333 if (PeekMessageW(&msg, 0, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE))
3334 if (msg.message == WM_LBUTTONUP)
3335 skip = TRUE;
3337 if (!skip && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3339 LVHITTESTINFO lvHitTestInfo;
3340 NMLISTVIEW nmlv;
3342 lvHitTestInfo.pt = infoPtr->ptClickPos;
3343 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3345 ZeroMemory(&nmlv, sizeof(nmlv));
3346 nmlv.iItem = lvHitTestInfo.iItem;
3347 nmlv.ptAction = infoPtr->ptClickPos;
3349 if (!infoPtr->bDragging)
3351 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3352 infoPtr->bDragging = TRUE;
3355 return 0;
3358 else
3359 infoPtr->bLButtonDown = FALSE;
3361 /* see if we are supposed to be tracking mouse hovering */
3362 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3363 /* fill in the trackinfo struct */
3364 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3365 trackinfo.dwFlags = TME_QUERY;
3366 trackinfo.hwndTrack = infoPtr->hwndSelf;
3367 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3369 /* see if we are already tracking this hwnd */
3370 _TrackMouseEvent(&trackinfo);
3372 if(!(trackinfo.dwFlags & TME_HOVER)) {
3373 trackinfo.dwFlags = TME_HOVER;
3375 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3376 _TrackMouseEvent(&trackinfo);
3380 return 0;
3384 /***
3385 * Tests whether the item is assignable to a list with style lStyle
3387 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3389 if ( (lpLVItem->mask & LVIF_TEXT) &&
3390 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3391 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3393 return TRUE;
3397 /***
3398 * DESCRIPTION:
3399 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3401 * PARAMETER(S):
3402 * [I] infoPtr : valid pointer to the listview structure
3403 * [I] lpLVItem : valid pointer to new item attributes
3404 * [I] isNew : the item being set is being inserted
3405 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3406 * [O] bChanged : will be set to TRUE if the item really changed
3408 * RETURN:
3409 * SUCCESS : TRUE
3410 * FAILURE : FALSE
3412 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3414 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3415 ITEM_INFO *lpItem;
3416 NMLISTVIEW nmlv;
3417 UINT uChanged = 0;
3418 LVITEMW item;
3420 TRACE("()\n");
3422 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3424 if (lpLVItem->mask == 0) return TRUE;
3426 if (infoPtr->dwStyle & LVS_OWNERDATA)
3428 /* a virtual listview only stores selection and focus */
3429 if (lpLVItem->mask & ~LVIF_STATE)
3430 return FALSE;
3431 lpItem = NULL;
3433 else
3435 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3436 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3437 assert (lpItem);
3440 /* we need to get the lParam and state of the item */
3441 item.iItem = lpLVItem->iItem;
3442 item.iSubItem = lpLVItem->iSubItem;
3443 item.mask = LVIF_STATE | LVIF_PARAM;
3444 item.stateMask = ~0;
3445 item.state = 0;
3446 item.lParam = 0;
3447 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3449 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3450 /* determine what fields will change */
3451 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3452 uChanged |= LVIF_STATE;
3454 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3455 uChanged |= LVIF_IMAGE;
3457 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3458 uChanged |= LVIF_PARAM;
3460 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3461 uChanged |= LVIF_INDENT;
3463 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3464 uChanged |= LVIF_TEXT;
3466 TRACE("uChanged=0x%x\n", uChanged);
3467 if (!uChanged) return TRUE;
3468 *bChanged = TRUE;
3470 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3471 nmlv.iItem = lpLVItem->iItem;
3472 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3473 nmlv.uOldState = item.state;
3474 nmlv.uChanged = uChanged;
3475 nmlv.lParam = item.lParam;
3477 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3478 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3479 /* are enabled */
3480 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3482 HWND hwndSelf = infoPtr->hwndSelf;
3484 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3485 return FALSE;
3486 if (!IsWindow(hwndSelf))
3487 return FALSE;
3490 /* copy information */
3491 if (lpLVItem->mask & LVIF_TEXT)
3492 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3494 if (lpLVItem->mask & LVIF_IMAGE)
3495 lpItem->hdr.iImage = lpLVItem->iImage;
3497 if (lpLVItem->mask & LVIF_PARAM)
3498 lpItem->lParam = lpLVItem->lParam;
3500 if (lpLVItem->mask & LVIF_INDENT)
3501 lpItem->iIndent = lpLVItem->iIndent;
3503 if (uChanged & LVIF_STATE)
3505 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3507 lpItem->state &= ~lpLVItem->stateMask;
3508 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3510 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3512 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3513 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3515 else if (lpLVItem->stateMask & LVIS_SELECTED)
3516 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3518 /* if we are asked to change focus, and we manage it, do it */
3519 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3521 if (lpLVItem->state & LVIS_FOCUSED)
3523 LISTVIEW_SetItemFocus(infoPtr, -1);
3524 infoPtr->nFocusedItem = lpLVItem->iItem;
3525 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3527 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3528 infoPtr->nFocusedItem = -1;
3532 /* if we're inserting the item, we're done */
3533 if (isNew) return TRUE;
3535 /* send LVN_ITEMCHANGED notification */
3536 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3537 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3539 return TRUE;
3542 /***
3543 * DESCRIPTION:
3544 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3546 * PARAMETER(S):
3547 * [I] infoPtr : valid pointer to the listview structure
3548 * [I] lpLVItem : valid pointer to new subitem attributes
3549 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3550 * [O] bChanged : will be set to TRUE if the item really changed
3552 * RETURN:
3553 * SUCCESS : TRUE
3554 * FAILURE : FALSE
3556 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3558 HDPA hdpaSubItems;
3559 SUBITEM_INFO *lpSubItem;
3561 /* we do not support subitems for virtual listviews */
3562 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3564 /* set subitem only if column is present */
3565 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3567 /* First do some sanity checks */
3568 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3569 particularly useful. We currently do not actually do anything with
3570 the flag on subitems.
3572 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3573 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3575 /* get the subitem structure, and create it if not there */
3576 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3577 assert (hdpaSubItems);
3579 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3580 if (!lpSubItem)
3582 SUBITEM_INFO *tmpSubItem;
3583 INT i;
3585 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3586 if (!lpSubItem) return FALSE;
3587 /* we could binary search here, if need be...*/
3588 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3590 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3591 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3593 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3595 Free(lpSubItem);
3596 return FALSE;
3598 lpSubItem->iSubItem = lpLVItem->iSubItem;
3599 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3600 *bChanged = TRUE;
3603 if (lpLVItem->mask & LVIF_IMAGE)
3604 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3606 lpSubItem->hdr.iImage = lpLVItem->iImage;
3607 *bChanged = TRUE;
3610 if (lpLVItem->mask & LVIF_TEXT)
3611 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3613 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3614 *bChanged = TRUE;
3617 return TRUE;
3620 /***
3621 * DESCRIPTION:
3622 * Sets item attributes.
3624 * PARAMETER(S):
3625 * [I] infoPtr : valid pointer to the listview structure
3626 * [I] lpLVItem : new item attributes
3627 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3629 * RETURN:
3630 * SUCCESS : TRUE
3631 * FAILURE : FALSE
3633 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3635 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3636 HWND hwndSelf = infoPtr->hwndSelf;
3637 LPWSTR pszText = NULL;
3638 BOOL bResult, bChanged = FALSE;
3640 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3642 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3643 return FALSE;
3645 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3646 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3648 pszText = lpLVItem->pszText;
3649 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3652 /* actually set the fields */
3653 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3655 if (lpLVItem->iSubItem)
3656 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3657 else
3658 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3659 if (!IsWindow(hwndSelf))
3660 return FALSE;
3662 /* redraw item, if necessary */
3663 if (bChanged && !infoPtr->bIsDrawing)
3665 /* this little optimization eliminates some nasty flicker */
3666 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3667 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3668 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3669 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3670 else
3671 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3673 /* restore text */
3674 if (pszText)
3676 textfreeT(lpLVItem->pszText, isW);
3677 lpLVItem->pszText = pszText;
3680 return bResult;
3683 /***
3684 * DESCRIPTION:
3685 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3687 * PARAMETER(S):
3688 * [I] infoPtr : valid pointer to the listview structure
3690 * RETURN:
3691 * item index
3693 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3695 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3696 INT nItem = 0;
3697 SCROLLINFO scrollInfo;
3699 scrollInfo.cbSize = sizeof(SCROLLINFO);
3700 scrollInfo.fMask = SIF_POS;
3702 if (uView == LVS_LIST)
3704 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3705 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3707 else if (uView == LVS_REPORT)
3709 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3710 nItem = scrollInfo.nPos;
3712 else
3714 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3715 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3718 TRACE("nItem=%d\n", nItem);
3720 return nItem;
3724 /***
3725 * DESCRIPTION:
3726 * Erases the background of the given rectangle
3728 * PARAMETER(S):
3729 * [I] infoPtr : valid pointer to the listview structure
3730 * [I] hdc : device context handle
3731 * [I] lprcBox : clipping rectangle
3733 * RETURN:
3734 * Success: TRUE
3735 * Failure: FALSE
3737 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3739 if (!infoPtr->hBkBrush) return FALSE;
3741 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3743 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3746 /***
3747 * DESCRIPTION:
3748 * Draws an item.
3750 * PARAMETER(S):
3751 * [I] infoPtr : valid pointer to the listview structure
3752 * [I] hdc : device context handle
3753 * [I] nItem : item index
3754 * [I] nSubItem : subitem index
3755 * [I] pos : item position in client coordinates
3756 * [I] cdmode : custom draw mode
3758 * RETURN:
3759 * Success: TRUE
3760 * Failure: FALSE
3762 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3764 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3765 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3766 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3767 DWORD cdsubitemmode = CDRF_DODEFAULT;
3768 LPRECT lprcFocus;
3769 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3770 NMLVCUSTOMDRAW nmlvcd;
3771 HIMAGELIST himl;
3772 LVITEMW lvItem;
3773 HFONT hOldFont;
3775 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3777 /* get information needed for drawing the item */
3778 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3779 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3780 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3781 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3782 lvItem.iItem = nItem;
3783 lvItem.iSubItem = nSubItem;
3784 lvItem.state = 0;
3785 lvItem.lParam = 0;
3786 lvItem.cchTextMax = DISP_TEXT_SIZE;
3787 lvItem.pszText = szDispText;
3788 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3789 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3790 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3791 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3792 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3794 /* now check if we need to update the focus rectangle */
3795 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3797 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3798 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3799 OffsetRect(&rcBox, pos.x, pos.y);
3800 OffsetRect(&rcSelect, pos.x, pos.y);
3801 OffsetRect(&rcIcon, pos.x, pos.y);
3802 OffsetRect(&rcStateIcon, pos.x, pos.y);
3803 OffsetRect(&rcLabel, pos.x, pos.y);
3804 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3805 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3806 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3808 /* fill in the custom draw structure */
3809 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3811 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3812 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3813 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3814 if (cdmode & CDRF_NOTIFYITEMDRAW)
3815 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3816 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3817 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3818 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3819 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3821 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3822 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3824 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3825 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3826 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3827 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3829 /* in full row select, subitems, will just use main item's colors */
3830 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3831 nmlvcd.clrTextBk = CLR_NONE;
3833 /* state icons */
3834 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3836 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3837 if (uStateImage)
3839 TRACE("uStateImage=%d\n", uStateImage);
3840 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3841 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3845 /* small icons */
3846 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3847 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3849 TRACE("iImage=%d\n", lvItem.iImage);
3850 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3851 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3852 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3855 /* Don't bother painting item being edited */
3856 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3858 /* FIXME: temporary hack */
3859 rcSelect.left = rcLabel.left;
3861 /* draw the selection background, if we're drawing the main item */
3862 if (nSubItem == 0)
3864 /* in icon mode, the label rect is really what we want to draw the
3865 * background for */
3866 if (uView == LVS_ICON)
3867 rcSelect = rcLabel;
3869 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3870 rcSelect.right = rcBox.right;
3872 if (nmlvcd.clrTextBk != CLR_NONE)
3873 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3874 if(lprcFocus) *lprcFocus = rcSelect;
3877 /* figure out the text drawing flags */
3878 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3879 if (uView == LVS_ICON)
3880 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3881 else if (nSubItem)
3883 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3885 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3886 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3887 default: uFormat |= DT_LEFT;
3890 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3892 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3893 else rcLabel.left += LABEL_HOR_PADDING;
3895 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3897 /* for GRIDLINES reduce the bottom so the text formats correctly */
3898 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3899 rcLabel.bottom--;
3901 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3903 postpaint:
3904 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3905 notify_postpaint(infoPtr, &nmlvcd);
3906 if (cdsubitemmode & CDRF_NEWFONT)
3907 SelectObject(hdc, hOldFont);
3908 return TRUE;
3911 /***
3912 * DESCRIPTION:
3913 * Draws listview items when in owner draw mode.
3915 * PARAMETER(S):
3916 * [I] infoPtr : valid pointer to the listview structure
3917 * [I] hdc : device context handle
3919 * RETURN:
3920 * None
3922 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3924 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3925 DWORD cditemmode = CDRF_DODEFAULT;
3926 NMLVCUSTOMDRAW nmlvcd;
3927 POINT Origin, Position;
3928 DRAWITEMSTRUCT dis;
3929 LVITEMW item;
3931 TRACE("()\n");
3933 ZeroMemory(&dis, sizeof(dis));
3935 /* Get scroll info once before loop */
3936 LISTVIEW_GetOrigin(infoPtr, &Origin);
3938 /* iterate through the invalidated rows */
3939 while(iterator_next(i))
3941 item.iItem = i->nItem;
3942 item.iSubItem = 0;
3943 item.mask = LVIF_PARAM | LVIF_STATE;
3944 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3945 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3947 dis.CtlType = ODT_LISTVIEW;
3948 dis.CtlID = uID;
3949 dis.itemID = item.iItem;
3950 dis.itemAction = ODA_DRAWENTIRE;
3951 dis.itemState = 0;
3952 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3953 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3954 dis.hwndItem = infoPtr->hwndSelf;
3955 dis.hDC = hdc;
3956 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3957 dis.rcItem.left = Position.x + Origin.x;
3958 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3959 dis.rcItem.top = Position.y + Origin.y;
3960 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3961 dis.itemData = item.lParam;
3963 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3966 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3967 * structure for the rest. of the paint cycle
3969 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3970 if (cdmode & CDRF_NOTIFYITEMDRAW)
3971 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3973 if (!(cditemmode & CDRF_SKIPDEFAULT))
3975 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3976 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3979 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3980 notify_postpaint(infoPtr, &nmlvcd);
3984 /***
3985 * DESCRIPTION:
3986 * Draws listview items when in report display mode.
3988 * PARAMETER(S):
3989 * [I] infoPtr : valid pointer to the listview structure
3990 * [I] hdc : device context handle
3991 * [I] cdmode : custom draw mode
3993 * RETURN:
3994 * None
3996 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3998 INT rgntype;
3999 RECT rcClip, rcItem;
4000 POINT Origin, Position;
4001 RANGE colRange;
4002 ITERATOR j;
4004 TRACE("()\n");
4006 /* figure out what to draw */
4007 rgntype = GetClipBox(hdc, &rcClip);
4008 if (rgntype == NULLREGION) return;
4010 /* Get scroll info once before loop */
4011 LISTVIEW_GetOrigin(infoPtr, &Origin);
4013 /* narrow down the columns we need to paint */
4014 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4016 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4017 if (rcItem.right + Origin.x >= rcClip.left) break;
4019 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4021 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4022 if (rcItem.left + Origin.x < rcClip.right) break;
4024 iterator_rangeitems(&j, colRange);
4026 /* in full row select, we _have_ to draw the main item */
4027 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4028 j.nSpecial = 0;
4030 /* iterate through the invalidated rows */
4031 while(iterator_next(i))
4033 /* iterate through the invalidated columns */
4034 while(iterator_next(&j))
4036 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4037 Position.x += Origin.x;
4038 Position.y += Origin.y;
4040 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4042 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4043 rcItem.top = 0;
4044 rcItem.bottom = infoPtr->nItemHeight;
4045 OffsetRect(&rcItem, Position.x, Position.y);
4046 if (!RectVisible(hdc, &rcItem)) continue;
4049 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4052 iterator_destroy(&j);
4055 /***
4056 * DESCRIPTION:
4057 * Draws the gridlines if necessary when in report display mode.
4059 * PARAMETER(S):
4060 * [I] infoPtr : valid pointer to the listview structure
4061 * [I] hdc : device context handle
4063 * RETURN:
4064 * None
4066 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4068 INT rgntype;
4069 INT y, itemheight;
4070 HPEN hPen, hOldPen;
4071 RECT rcClip, rcItem;
4072 POINT Origin;
4073 RANGE colRange;
4074 ITERATOR j;
4076 TRACE("()\n");
4078 /* figure out what to draw */
4079 rgntype = GetClipBox(hdc, &rcClip);
4080 if (rgntype == NULLREGION) return;
4082 /* Get scroll info once before loop */
4083 LISTVIEW_GetOrigin(infoPtr, &Origin);
4085 /* narrow down the columns we need to paint */
4086 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4088 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4089 if (rcItem.right + Origin.x >= rcClip.left) break;
4091 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4093 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4094 if (rcItem.left + Origin.x < rcClip.right) break;
4096 iterator_rangeitems(&j, colRange);
4098 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4100 hOldPen = SelectObject ( hdc, hPen );
4102 /* draw the vertical lines for the columns */
4103 iterator_rangeitems(&j, colRange);
4104 while(iterator_next(&j))
4106 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4107 if (rcItem.left == 0) continue; /* skip first column */
4108 rcItem.left += Origin.x;
4109 rcItem.right += Origin.x;
4110 rcItem.top = infoPtr->rcList.top;
4111 rcItem.bottom = infoPtr->rcList.bottom;
4112 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4113 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4114 LineTo (hdc, rcItem.left, rcItem.bottom);
4116 iterator_destroy(&j);
4118 /* draw the horizontial lines for the rows */
4119 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4120 rcItem.left = infoPtr->rcList.left + Origin.x;
4121 rcItem.right = infoPtr->rcList.right + Origin.x;
4122 rcItem.bottom = rcItem.top = Origin.y - 1;
4123 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4124 LineTo(hdc, rcItem.right, rcItem.top);
4125 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4127 rcItem.bottom = rcItem.top = y;
4128 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4129 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4130 LineTo (hdc, rcItem.right, rcItem.top);
4133 SelectObject( hdc, hOldPen );
4134 DeleteObject( hPen );
4138 /***
4139 * DESCRIPTION:
4140 * Draws listview items when in list display mode.
4142 * PARAMETER(S):
4143 * [I] infoPtr : valid pointer to the listview structure
4144 * [I] hdc : device context handle
4145 * [I] cdmode : custom draw mode
4147 * RETURN:
4148 * None
4150 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4152 POINT Origin, Position;
4154 /* Get scroll info once before loop */
4155 LISTVIEW_GetOrigin(infoPtr, &Origin);
4157 while(iterator_prev(i))
4159 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4160 Position.x += Origin.x;
4161 Position.y += Origin.y;
4163 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4168 /***
4169 * DESCRIPTION:
4170 * Draws listview items.
4172 * PARAMETER(S):
4173 * [I] infoPtr : valid pointer to the listview structure
4174 * [I] hdc : device context handle
4175 * [I] prcErase : rect to be erased before refresh (may be NULL)
4177 * RETURN:
4178 * NoneX
4180 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4182 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4183 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4184 NMLVCUSTOMDRAW nmlvcd;
4185 HFONT hOldFont = 0;
4186 DWORD cdmode;
4187 INT oldBkMode = 0;
4188 RECT rcClient;
4189 ITERATOR i;
4190 HDC hdcOrig = hdc;
4191 HBITMAP hbmp = NULL;
4193 LISTVIEW_DUMP(infoPtr);
4195 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4196 TRACE("double buffering\n");
4198 hdc = CreateCompatibleDC(hdcOrig);
4199 if (!hdc) {
4200 ERR("Failed to create DC for backbuffer\n");
4201 return;
4203 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4204 infoPtr->rcList.bottom);
4205 if (!hbmp) {
4206 ERR("Failed to create bitmap for backbuffer\n");
4207 DeleteDC(hdc);
4208 return;
4211 SelectObject(hdc, hbmp);
4212 SelectObject(hdc, infoPtr->hFont);
4213 } else {
4214 /* Save dc values we're gonna trash while drawing
4215 * FIXME: Should be done in LISTVIEW_DrawItem() */
4216 hOldFont = SelectObject(hdc, infoPtr->hFont);
4217 oldBkMode = GetBkMode(hdc);
4218 oldBkColor = GetBkColor(hdc);
4219 oldTextColor = GetTextColor(hdc);
4222 infoPtr->bIsDrawing = TRUE;
4224 if (prcErase) {
4225 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4226 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4227 /* If no erasing was done (usually because RedrawWindow was called
4228 * with RDW_INVALIDATE only) we need to copy the old contents into
4229 * the backbuffer before continuing. */
4230 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4231 infoPtr->rcList.right - infoPtr->rcList.left,
4232 infoPtr->rcList.bottom - infoPtr->rcList.top,
4233 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4236 /* FIXME: Shouldn't need to do this */
4237 oldClrTextBk = infoPtr->clrTextBk;
4238 oldClrText = infoPtr->clrText;
4240 infoPtr->cditemmode = CDRF_DODEFAULT;
4242 GetClientRect(infoPtr->hwndSelf, &rcClient);
4243 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4244 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4245 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4246 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4248 /* Use these colors to draw the items */
4249 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4250 infoPtr->clrText = nmlvcd.clrText;
4252 /* nothing to draw */
4253 if(infoPtr->nItemCount == 0) goto enddraw;
4255 /* figure out what we need to draw */
4256 iterator_visibleitems(&i, infoPtr, hdc);
4258 /* send cache hint notification */
4259 if (infoPtr->dwStyle & LVS_OWNERDATA)
4261 RANGE range = iterator_range(&i);
4262 NMLVCACHEHINT nmlv;
4264 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4265 nmlv.iFrom = range.lower;
4266 nmlv.iTo = range.upper - 1;
4267 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4270 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4271 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4272 else
4274 if (uView == LVS_REPORT)
4275 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4276 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4277 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4279 /* if we have a focus rect, draw it */
4280 if (infoPtr->bFocus)
4281 DrawFocusRect(hdc, &infoPtr->rcFocus);
4283 iterator_destroy(&i);
4285 enddraw:
4286 /* For LVS_EX_GRIDLINES go and draw lines */
4287 /* This includes the case where there were *no* items */
4288 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4289 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4290 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4292 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4293 notify_postpaint(infoPtr, &nmlvcd);
4295 infoPtr->clrTextBk = oldClrTextBk;
4296 infoPtr->clrText = oldClrText;
4298 if(hbmp) {
4299 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4300 infoPtr->rcList.right - infoPtr->rcList.left,
4301 infoPtr->rcList.bottom - infoPtr->rcList.top,
4302 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4304 DeleteObject(hbmp);
4305 DeleteDC(hdc);
4306 } else {
4307 SelectObject(hdc, hOldFont);
4308 SetBkMode(hdc, oldBkMode);
4309 SetBkColor(hdc, oldBkColor);
4310 SetTextColor(hdc, oldTextColor);
4313 infoPtr->bIsDrawing = FALSE;
4317 /***
4318 * DESCRIPTION:
4319 * Calculates the approximate width and height of a given number of items.
4321 * PARAMETER(S):
4322 * [I] infoPtr : valid pointer to the listview structure
4323 * [I] nItemCount : number of items
4324 * [I] wWidth : width
4325 * [I] wHeight : height
4327 * RETURN:
4328 * Returns a DWORD. The width in the low word and the height in high word.
4330 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4331 WORD wWidth, WORD wHeight)
4333 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4334 INT nItemCountPerColumn = 1;
4335 INT nColumnCount = 0;
4336 DWORD dwViewRect = 0;
4338 if (nItemCount == -1)
4339 nItemCount = infoPtr->nItemCount;
4341 if (uView == LVS_LIST)
4343 if (wHeight == 0xFFFF)
4345 /* use current height */
4346 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4349 if (wHeight < infoPtr->nItemHeight)
4350 wHeight = infoPtr->nItemHeight;
4352 if (nItemCount > 0)
4354 if (infoPtr->nItemHeight > 0)
4356 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4357 if (nItemCountPerColumn == 0)
4358 nItemCountPerColumn = 1;
4360 if (nItemCount % nItemCountPerColumn != 0)
4361 nColumnCount = nItemCount / nItemCountPerColumn;
4362 else
4363 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4367 /* Microsoft padding magic */
4368 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4369 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4371 dwViewRect = MAKELONG(wWidth, wHeight);
4373 else if (uView == LVS_REPORT)
4375 RECT rcBox;
4377 if (infoPtr->nItemCount > 0)
4379 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4380 wWidth = rcBox.right - rcBox.left;
4381 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4383 else
4385 /* use current height and width */
4386 if (wHeight == 0xffff)
4387 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4388 if (wWidth == 0xffff)
4389 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4392 dwViewRect = MAKELONG(wWidth, wHeight);
4394 else if (uView == LVS_SMALLICON)
4395 FIXME("uView == LVS_SMALLICON: not implemented\n");
4396 else if (uView == LVS_ICON)
4397 FIXME("uView == LVS_ICON: not implemented\n");
4399 return dwViewRect;
4403 /***
4404 * DESCRIPTION:
4405 * Create a drag image list for the specified item.
4407 * PARAMETER(S):
4408 * [I] infoPtr : valid pointer to the listview structure
4409 * [I] iItem : index of item
4410 * [O] lppt : Upperr-left corner of the image
4412 * RETURN:
4413 * Returns a handle to the image list if successful, NULL otherwise.
4415 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4417 RECT rcItem;
4418 SIZE size;
4419 POINT pos;
4420 HDC hdc, hdcOrig;
4421 HBITMAP hbmp, hOldbmp;
4422 HIMAGELIST dragList = 0;
4423 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4425 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4426 return 0;
4428 rcItem.left = LVIR_BOUNDS;
4429 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4430 return 0;
4432 lppt->x = rcItem.left;
4433 lppt->y = rcItem.top;
4435 size.cx = rcItem.right - rcItem.left;
4436 size.cy = rcItem.bottom - rcItem.top;
4438 hdcOrig = GetDC(infoPtr->hwndSelf);
4439 hdc = CreateCompatibleDC(hdcOrig);
4440 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4441 hOldbmp = SelectObject(hdc, hbmp);
4443 rcItem.left = rcItem.top = 0;
4444 rcItem.right = size.cx;
4445 rcItem.bottom = size.cy;
4446 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4448 pos.x = pos.y = 0;
4449 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4451 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4452 SelectObject(hdc, hOldbmp);
4453 ImageList_Add(dragList, hbmp, 0);
4455 else
4456 SelectObject(hdc, hOldbmp);
4458 DeleteObject(hbmp);
4459 DeleteDC(hdc);
4460 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4462 TRACE("ret=%p\n", dragList);
4464 return dragList;
4468 /***
4469 * DESCRIPTION:
4470 * Removes all listview items and subitems.
4472 * PARAMETER(S):
4473 * [I] infoPtr : valid pointer to the listview structure
4475 * RETURN:
4476 * SUCCESS : TRUE
4477 * FAILURE : FALSE
4479 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4481 NMLISTVIEW nmlv;
4482 HDPA hdpaSubItems = NULL;
4483 BOOL bSuppress;
4484 ITEMHDR *hdrItem;
4485 INT i, j;
4487 TRACE("()\n");
4489 /* we do it directly, to avoid notifications */
4490 ranges_clear(infoPtr->selectionRanges);
4491 infoPtr->nSelectionMark = -1;
4492 infoPtr->nFocusedItem = -1;
4493 SetRectEmpty(&infoPtr->rcFocus);
4494 /* But we are supposed to leave nHotItem as is! */
4497 /* send LVN_DELETEALLITEMS notification */
4498 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4499 nmlv.iItem = -1;
4500 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4502 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4504 /* send LVN_DELETEITEM notification, if not suppressed */
4505 if (!bSuppress) notify_deleteitem(infoPtr, i);
4506 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4508 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4509 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4511 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4512 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4513 Free(hdrItem);
4515 DPA_Destroy(hdpaSubItems);
4516 DPA_DeletePtr(infoPtr->hdpaItems, i);
4518 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4519 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4520 infoPtr->nItemCount --;
4523 if (!destroy)
4525 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4526 LISTVIEW_UpdateScroll(infoPtr);
4528 LISTVIEW_InvalidateList(infoPtr);
4530 return TRUE;
4533 /***
4534 * DESCRIPTION:
4535 * Scrolls, and updates the columns, when a column is changing width.
4537 * PARAMETER(S):
4538 * [I] infoPtr : valid pointer to the listview structure
4539 * [I] nColumn : column to scroll
4540 * [I] dx : amount of scroll, in pixels
4542 * RETURN:
4543 * None.
4545 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4547 COLUMN_INFO *lpColumnInfo;
4548 RECT rcOld, rcCol;
4549 POINT ptOrigin;
4550 INT nCol;
4552 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4553 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4554 rcCol = lpColumnInfo->rcHeader;
4555 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4556 rcCol.left = rcCol.right;
4558 /* adjust the other columns */
4559 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4561 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4562 lpColumnInfo->rcHeader.left += dx;
4563 lpColumnInfo->rcHeader.right += dx;
4566 /* do not update screen if not in report mode */
4567 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4569 /* if we have a focus, we must first erase the focus rect */
4570 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4572 /* Need to reset the item width when inserting a new column */
4573 infoPtr->nItemWidth += dx;
4575 LISTVIEW_UpdateScroll(infoPtr);
4576 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4578 /* scroll to cover the deleted column, and invalidate for redraw */
4579 rcOld = infoPtr->rcList;
4580 rcOld.left = ptOrigin.x + rcCol.left + dx;
4581 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4583 /* we can restore focus now */
4584 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4587 /***
4588 * DESCRIPTION:
4589 * Removes a column from the listview control.
4591 * PARAMETER(S):
4592 * [I] infoPtr : valid pointer to the listview structure
4593 * [I] nColumn : column index
4595 * RETURN:
4596 * SUCCESS : TRUE
4597 * FAILURE : FALSE
4599 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4601 RECT rcCol;
4603 TRACE("nColumn=%d\n", nColumn);
4605 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4606 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4608 /* While the MSDN specifically says that column zero should not be deleted,
4609 what actually happens is that the column itself is deleted but no items or subitems
4610 are removed.
4613 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4615 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4616 return FALSE;
4618 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4619 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4621 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4623 SUBITEM_INFO *lpSubItem, *lpDelItem;
4624 HDPA hdpaSubItems;
4625 INT nItem, nSubItem, i;
4627 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4629 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4630 nSubItem = 0;
4631 lpDelItem = 0;
4632 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4634 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4635 if (lpSubItem->iSubItem == nColumn)
4637 nSubItem = i;
4638 lpDelItem = lpSubItem;
4640 else if (lpSubItem->iSubItem > nColumn)
4642 lpSubItem->iSubItem--;
4646 /* if we found our subitem, zapp it */
4647 if (nSubItem > 0)
4649 /* free string */
4650 if (is_textW(lpDelItem->hdr.pszText))
4651 Free(lpDelItem->hdr.pszText);
4653 /* free item */
4654 Free(lpDelItem);
4656 /* free dpa memory */
4657 DPA_DeletePtr(hdpaSubItems, nSubItem);
4662 /* update the other column info */
4663 LISTVIEW_UpdateItemSize(infoPtr);
4664 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4665 LISTVIEW_InvalidateList(infoPtr);
4666 else
4667 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4669 return TRUE;
4672 /***
4673 * DESCRIPTION:
4674 * Invalidates the listview after an item's insertion or deletion.
4676 * PARAMETER(S):
4677 * [I] infoPtr : valid pointer to the listview structure
4678 * [I] nItem : item index
4679 * [I] dir : -1 if deleting, 1 if inserting
4681 * RETURN:
4682 * None
4684 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4686 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4687 INT nPerCol, nItemCol, nItemRow;
4688 RECT rcScroll;
4689 POINT Origin;
4691 /* if we don't refresh, what's the point of scrolling? */
4692 if (!is_redrawing(infoPtr)) return;
4694 assert (abs(dir) == 1);
4696 /* arrange icons if autoarrange is on */
4697 if (is_autoarrange(infoPtr))
4699 BOOL arrange = TRUE;
4700 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4701 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4702 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4705 /* scrollbars need updating */
4706 LISTVIEW_UpdateScroll(infoPtr);
4708 /* figure out the item's position */
4709 if (uView == LVS_REPORT)
4710 nPerCol = infoPtr->nItemCount + 1;
4711 else if (uView == LVS_LIST)
4712 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4713 else /* LVS_ICON, or LVS_SMALLICON */
4714 return;
4716 nItemCol = nItem / nPerCol;
4717 nItemRow = nItem % nPerCol;
4718 LISTVIEW_GetOrigin(infoPtr, &Origin);
4720 /* move the items below up a slot */
4721 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4722 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4723 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4724 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4725 OffsetRect(&rcScroll, Origin.x, Origin.y);
4726 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4727 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4729 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4730 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4731 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4734 /* report has only that column, so we're done */
4735 if (uView == LVS_REPORT) return;
4737 /* now for LISTs, we have to deal with the columns to the right */
4738 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4739 rcScroll.top = 0;
4740 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4741 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4742 OffsetRect(&rcScroll, Origin.x, Origin.y);
4743 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4744 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4745 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4748 /***
4749 * DESCRIPTION:
4750 * Removes an item from the listview control.
4752 * PARAMETER(S):
4753 * [I] infoPtr : valid pointer to the listview structure
4754 * [I] nItem : item index
4756 * RETURN:
4757 * SUCCESS : TRUE
4758 * FAILURE : FALSE
4760 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4762 LVITEMW item;
4763 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4764 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4766 TRACE("(nItem=%d)\n", nItem);
4768 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4770 /* remove selection, and focus */
4771 item.state = 0;
4772 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4773 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4775 /* send LVN_DELETEITEM notification. */
4776 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4778 /* we need to do this here, because we'll be deleting stuff */
4779 if (is_icon)
4780 LISTVIEW_InvalidateItem(infoPtr, nItem);
4782 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4784 HDPA hdpaSubItems;
4785 ITEMHDR *hdrItem;
4786 INT i;
4788 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4789 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4791 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4792 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4793 Free(hdrItem);
4795 DPA_Destroy(hdpaSubItems);
4798 if (is_icon)
4800 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4801 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4804 infoPtr->nItemCount--;
4805 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4807 /* now is the invalidation fun */
4808 if (!is_icon)
4809 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4810 return TRUE;
4814 /***
4815 * DESCRIPTION:
4816 * Callback implementation for editlabel control
4818 * PARAMETER(S):
4819 * [I] infoPtr : valid pointer to the listview structure
4820 * [I] pszText : modified text
4821 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4823 * RETURN:
4824 * SUCCESS : TRUE
4825 * FAILURE : FALSE
4827 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4829 HWND hwndSelf = infoPtr->hwndSelf;
4830 NMLVDISPINFOW dispInfo;
4832 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4834 ZeroMemory(&dispInfo, sizeof(dispInfo));
4835 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4836 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4837 dispInfo.item.iSubItem = 0;
4838 dispInfo.item.stateMask = ~0;
4839 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4840 /* add the text from the edit in */
4841 dispInfo.item.mask |= LVIF_TEXT;
4842 dispInfo.item.pszText = pszText;
4843 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4845 /* Do we need to update the Item Text */
4846 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4847 if (!IsWindow(hwndSelf))
4848 return FALSE;
4849 if (!pszText) return TRUE;
4851 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4853 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4854 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4855 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4857 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4858 return TRUE;
4862 ZeroMemory(&dispInfo, sizeof(dispInfo));
4863 dispInfo.item.mask = LVIF_TEXT;
4864 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4865 dispInfo.item.iSubItem = 0;
4866 dispInfo.item.pszText = pszText;
4867 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4868 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4871 /***
4872 * DESCRIPTION:
4873 * Begin in place editing of specified list view item
4875 * PARAMETER(S):
4876 * [I] infoPtr : valid pointer to the listview structure
4877 * [I] nItem : item index
4878 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4880 * RETURN:
4881 * SUCCESS : TRUE
4882 * FAILURE : FALSE
4884 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4886 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4887 NMLVDISPINFOW dispInfo;
4888 RECT rect;
4889 HWND hwndSelf = infoPtr->hwndSelf;
4891 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4893 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4894 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4896 infoPtr->nEditLabelItem = nItem;
4898 /* Is the EditBox still there, if so remove it */
4899 if(infoPtr->hwndEdit != 0)
4901 SetFocus(infoPtr->hwndSelf);
4902 infoPtr->hwndEdit = 0;
4905 LISTVIEW_SetSelection(infoPtr, nItem);
4906 LISTVIEW_SetItemFocus(infoPtr, nItem);
4907 LISTVIEW_InvalidateItem(infoPtr, nItem);
4909 rect.left = LVIR_LABEL;
4910 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4912 ZeroMemory(&dispInfo, sizeof(dispInfo));
4913 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4914 dispInfo.item.iItem = nItem;
4915 dispInfo.item.iSubItem = 0;
4916 dispInfo.item.stateMask = ~0;
4917 dispInfo.item.pszText = szDispText;
4918 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4919 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4921 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4922 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4923 if (!infoPtr->hwndEdit) return 0;
4925 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4927 if (!IsWindow(hwndSelf))
4928 return 0;
4929 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4930 infoPtr->hwndEdit = 0;
4931 return 0;
4934 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4935 SetFocus(infoPtr->hwndEdit);
4936 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4937 return infoPtr->hwndEdit;
4941 /***
4942 * DESCRIPTION:
4943 * Ensures the specified item is visible, scrolling into view if necessary.
4945 * PARAMETER(S):
4946 * [I] infoPtr : valid pointer to the listview structure
4947 * [I] nItem : item index
4948 * [I] bPartial : partially or entirely visible
4950 * RETURN:
4951 * SUCCESS : TRUE
4952 * FAILURE : FALSE
4954 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4956 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4957 INT nScrollPosHeight = 0;
4958 INT nScrollPosWidth = 0;
4959 INT nHorzAdjust = 0;
4960 INT nVertAdjust = 0;
4961 INT nHorzDiff = 0;
4962 INT nVertDiff = 0;
4963 RECT rcItem, rcTemp;
4965 rcItem.left = LVIR_BOUNDS;
4966 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4968 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4970 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4972 /* scroll left/right, but in LVS_REPORT mode */
4973 if (uView == LVS_LIST)
4974 nScrollPosWidth = infoPtr->nItemWidth;
4975 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4976 nScrollPosWidth = 1;
4978 if (rcItem.left < infoPtr->rcList.left)
4980 nHorzAdjust = -1;
4981 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4983 else
4985 nHorzAdjust = 1;
4986 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4990 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4992 /* scroll up/down, but not in LVS_LIST mode */
4993 if (uView == LVS_REPORT)
4994 nScrollPosHeight = infoPtr->nItemHeight;
4995 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4996 nScrollPosHeight = 1;
4998 if (rcItem.top < infoPtr->rcList.top)
5000 nVertAdjust = -1;
5001 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5003 else
5005 nVertAdjust = 1;
5006 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5010 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5012 if (nScrollPosWidth)
5014 INT diff = nHorzDiff / nScrollPosWidth;
5015 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5016 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5019 if (nScrollPosHeight)
5021 INT diff = nVertDiff / nScrollPosHeight;
5022 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5023 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5026 return TRUE;
5029 /***
5030 * DESCRIPTION:
5031 * Searches for an item with specific characteristics.
5033 * PARAMETER(S):
5034 * [I] hwnd : window handle
5035 * [I] nStart : base item index
5036 * [I] lpFindInfo : item information to look for
5038 * RETURN:
5039 * SUCCESS : index of item
5040 * FAILURE : -1
5042 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5043 const LVFINDINFOW *lpFindInfo)
5045 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5046 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5047 BOOL bWrap = FALSE, bNearest = FALSE;
5048 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5049 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5050 POINT Position, Destination;
5051 LVITEMW lvItem;
5053 if (!lpFindInfo || nItem < 0) return -1;
5055 lvItem.mask = 0;
5056 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5058 lvItem.mask |= LVIF_TEXT;
5059 lvItem.pszText = szDispText;
5060 lvItem.cchTextMax = DISP_TEXT_SIZE;
5063 if (lpFindInfo->flags & LVFI_WRAP)
5064 bWrap = TRUE;
5066 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5067 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5069 POINT Origin;
5070 RECT rcArea;
5072 LISTVIEW_GetOrigin(infoPtr, &Origin);
5073 Destination.x = lpFindInfo->pt.x - Origin.x;
5074 Destination.y = lpFindInfo->pt.y - Origin.y;
5075 switch(lpFindInfo->vkDirection)
5077 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5078 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5079 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5080 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5081 case VK_HOME: Destination.x = Destination.y = 0; break;
5082 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5083 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5084 case VK_END:
5085 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5086 Destination.x = rcArea.right;
5087 Destination.y = rcArea.bottom;
5088 break;
5089 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5091 bNearest = TRUE;
5093 else Destination.x = Destination.y = 0;
5095 /* if LVFI_PARAM is specified, all other flags are ignored */
5096 if (lpFindInfo->flags & LVFI_PARAM)
5098 lvItem.mask |= LVIF_PARAM;
5099 bNearest = FALSE;
5100 lvItem.mask &= ~LVIF_TEXT;
5103 again:
5104 for (; nItem < nLast; nItem++)
5106 lvItem.iItem = nItem;
5107 lvItem.iSubItem = 0;
5108 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5110 if (lvItem.mask & LVIF_PARAM)
5112 if (lpFindInfo->lParam == lvItem.lParam)
5113 return nItem;
5114 else
5115 continue;
5118 if (lvItem.mask & LVIF_TEXT)
5120 if (lpFindInfo->flags & LVFI_PARTIAL)
5122 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5124 else
5126 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5130 if (!bNearest) return nItem;
5132 /* This is very inefficient. To do a good job here,
5133 * we need a sorted array of (x,y) item positions */
5134 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5136 /* compute the distance^2 to the destination */
5137 xdist = Destination.x - Position.x;
5138 ydist = Destination.y - Position.y;
5139 dist = xdist * xdist + ydist * ydist;
5141 /* remember the distance, and item if it's closer */
5142 if (dist < mindist)
5144 mindist = dist;
5145 nNearestItem = nItem;
5149 if (bWrap)
5151 nItem = 0;
5152 nLast = min(nStart + 1, infoPtr->nItemCount);
5153 bWrap = FALSE;
5154 goto again;
5157 return nNearestItem;
5160 /***
5161 * DESCRIPTION:
5162 * Searches for an item with specific characteristics.
5164 * PARAMETER(S):
5165 * [I] hwnd : window handle
5166 * [I] nStart : base item index
5167 * [I] lpFindInfo : item information to look for
5169 * RETURN:
5170 * SUCCESS : index of item
5171 * FAILURE : -1
5173 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5174 const LVFINDINFOA *lpFindInfo)
5176 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5177 LVFINDINFOW fiw;
5178 INT res;
5179 LPWSTR strW = NULL;
5181 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5182 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5183 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5184 textfreeT(strW, FALSE);
5185 return res;
5188 /***
5189 * DESCRIPTION:
5190 * Retrieves the background image of the listview control.
5192 * PARAMETER(S):
5193 * [I] infoPtr : valid pointer to the listview structure
5194 * [O] lpBkImage : background image attributes
5196 * RETURN:
5197 * SUCCESS : TRUE
5198 * FAILURE : FALSE
5200 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5201 /* { */
5202 /* FIXME (listview, "empty stub!\n"); */
5203 /* return FALSE; */
5204 /* } */
5206 /***
5207 * DESCRIPTION:
5208 * Retrieves column attributes.
5210 * PARAMETER(S):
5211 * [I] infoPtr : valid pointer to the listview structure
5212 * [I] nColumn : column index
5213 * [IO] lpColumn : column information
5214 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5215 * otherwise it is in fact a LPLVCOLUMNA
5217 * RETURN:
5218 * SUCCESS : TRUE
5219 * FAILURE : FALSE
5221 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5223 COLUMN_INFO *lpColumnInfo;
5224 HDITEMW hdi;
5226 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5227 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5229 /* initialize memory */
5230 ZeroMemory(&hdi, sizeof(hdi));
5232 if (lpColumn->mask & LVCF_TEXT)
5234 hdi.mask |= HDI_TEXT;
5235 hdi.pszText = lpColumn->pszText;
5236 hdi.cchTextMax = lpColumn->cchTextMax;
5239 if (lpColumn->mask & LVCF_IMAGE)
5240 hdi.mask |= HDI_IMAGE;
5242 if (lpColumn->mask & LVCF_ORDER)
5243 hdi.mask |= HDI_ORDER;
5245 if (lpColumn->mask & LVCF_SUBITEM)
5246 hdi.mask |= HDI_LPARAM;
5248 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5250 if (lpColumn->mask & LVCF_FMT)
5251 lpColumn->fmt = lpColumnInfo->fmt;
5253 if (lpColumn->mask & LVCF_WIDTH)
5254 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5256 if (lpColumn->mask & LVCF_IMAGE)
5257 lpColumn->iImage = hdi.iImage;
5259 if (lpColumn->mask & LVCF_ORDER)
5260 lpColumn->iOrder = hdi.iOrder;
5262 if (lpColumn->mask & LVCF_SUBITEM)
5263 lpColumn->iSubItem = hdi.lParam;
5265 return TRUE;
5269 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5271 INT i;
5273 if (!lpiArray)
5274 return FALSE;
5276 /* FIXME: little hack */
5277 for (i = 0; i < iCount; i++)
5278 lpiArray[i] = i;
5280 return TRUE;
5283 /***
5284 * DESCRIPTION:
5285 * Retrieves the column width.
5287 * PARAMETER(S):
5288 * [I] infoPtr : valid pointer to the listview structure
5289 * [I] int : column index
5291 * RETURN:
5292 * SUCCESS : column width
5293 * FAILURE : zero
5295 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5297 INT nColumnWidth = 0;
5298 HDITEMW hdItem;
5300 TRACE("nColumn=%d\n", nColumn);
5302 /* we have a 'column' in LIST and REPORT mode only */
5303 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5305 case LVS_LIST:
5306 nColumnWidth = infoPtr->nItemWidth;
5307 break;
5308 case LVS_REPORT:
5309 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5310 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5311 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5313 * TODO: should we do the same in LVM_GETCOLUMN?
5315 hdItem.mask = HDI_WIDTH;
5316 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5318 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5319 return 0;
5321 nColumnWidth = hdItem.cxy;
5322 break;
5325 TRACE("nColumnWidth=%d\n", nColumnWidth);
5326 return nColumnWidth;
5329 /***
5330 * DESCRIPTION:
5331 * In list or report display mode, retrieves the number of items that can fit
5332 * vertically in the visible area. In icon or small icon display mode,
5333 * retrieves the total number of visible items.
5335 * PARAMETER(S):
5336 * [I] infoPtr : valid pointer to the listview structure
5338 * RETURN:
5339 * Number of fully visible items.
5341 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5343 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5345 case LVS_ICON:
5346 case LVS_SMALLICON:
5347 return infoPtr->nItemCount;
5348 case LVS_REPORT:
5349 return LISTVIEW_GetCountPerColumn(infoPtr);
5350 case LVS_LIST:
5351 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5353 assert(FALSE);
5354 return 0;
5357 /***
5358 * DESCRIPTION:
5359 * Retrieves an image list handle.
5361 * PARAMETER(S):
5362 * [I] infoPtr : valid pointer to the listview structure
5363 * [I] nImageList : image list identifier
5365 * RETURN:
5366 * SUCCESS : image list handle
5367 * FAILURE : NULL
5369 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5371 switch (nImageList)
5373 case LVSIL_NORMAL: return infoPtr->himlNormal;
5374 case LVSIL_SMALL: return infoPtr->himlSmall;
5375 case LVSIL_STATE: return infoPtr->himlState;
5377 return NULL;
5380 /* LISTVIEW_GetISearchString */
5382 /***
5383 * DESCRIPTION:
5384 * Retrieves item attributes.
5386 * PARAMETER(S):
5387 * [I] hwnd : window handle
5388 * [IO] lpLVItem : item info
5389 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5390 * if FALSE, then lpLVItem is a LPLVITEMA.
5392 * NOTE:
5393 * This is the internal 'GetItem' interface -- it tries to
5394 * be smart and avoid text copies, if possible, by modifying
5395 * lpLVItem->pszText to point to the text string. Please note
5396 * that this is not always possible (e.g. OWNERDATA), so on
5397 * entry you *must* supply valid values for pszText, and cchTextMax.
5398 * The only difference to the documented interface is that upon
5399 * return, you should use *only* the lpLVItem->pszText, rather than
5400 * the buffer pointer you provided on input. Most code already does
5401 * that, so it's not a problem.
5402 * For the two cases when the text must be copied (that is,
5403 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5405 * RETURN:
5406 * SUCCESS : TRUE
5407 * FAILURE : FALSE
5409 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5411 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5412 NMLVDISPINFOW dispInfo;
5413 ITEM_INFO *lpItem;
5414 ITEMHDR* pItemHdr;
5415 HDPA hdpaSubItems;
5416 INT isubitem;
5418 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5420 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5421 return FALSE;
5423 if (lpLVItem->mask == 0) return TRUE;
5425 /* make a local copy */
5426 isubitem = lpLVItem->iSubItem;
5428 /* a quick optimization if all we're asked is the focus state
5429 * these queries are worth optimising since they are common,
5430 * and can be answered in constant time, without the heavy accesses */
5431 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5432 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5434 lpLVItem->state = 0;
5435 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5436 lpLVItem->state |= LVIS_FOCUSED;
5437 return TRUE;
5440 ZeroMemory(&dispInfo, sizeof(dispInfo));
5442 /* if the app stores all the data, handle it separately */
5443 if (infoPtr->dwStyle & LVS_OWNERDATA)
5445 dispInfo.item.state = 0;
5447 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5448 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5450 /* NOTE: copy only fields which we _know_ are initialized, some apps
5451 * depend on the uninitialized fields being 0 */
5452 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5453 dispInfo.item.iItem = lpLVItem->iItem;
5454 dispInfo.item.iSubItem = isubitem;
5455 if (lpLVItem->mask & LVIF_TEXT)
5457 dispInfo.item.pszText = lpLVItem->pszText;
5458 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5460 if (lpLVItem->mask & LVIF_STATE)
5461 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5462 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5463 dispInfo.item.stateMask = lpLVItem->stateMask;
5464 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5466 /* full size structure expected - _WIN32IE >= 0x560 */
5467 *lpLVItem = dispInfo.item;
5469 else if (lpLVItem->mask & LVIF_INDENT)
5471 /* indent member expected - _WIN32IE >= 0x300 */
5472 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5474 else
5476 /* minimal structure expected */
5477 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5479 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5482 /* make sure lParam is zeroed out */
5483 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5485 /* we store only a little state, so if we're not asked, we're done */
5486 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5488 /* if focus is handled by us, report it */
5489 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5491 lpLVItem->state &= ~LVIS_FOCUSED;
5492 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5493 lpLVItem->state |= LVIS_FOCUSED;
5496 /* and do the same for selection, if we handle it */
5497 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5499 lpLVItem->state &= ~LVIS_SELECTED;
5500 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5501 lpLVItem->state |= LVIS_SELECTED;
5504 return TRUE;
5507 /* find the item and subitem structures before we proceed */
5508 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5509 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5510 assert (lpItem);
5512 if (isubitem)
5514 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5515 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5516 if (!lpSubItem)
5518 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5519 isubitem = 0;
5522 else
5523 pItemHdr = &lpItem->hdr;
5525 /* Do we need to query the state from the app? */
5526 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5528 dispInfo.item.mask |= LVIF_STATE;
5529 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5532 /* Do we need to enquire about the image? */
5533 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5534 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5536 dispInfo.item.mask |= LVIF_IMAGE;
5537 dispInfo.item.iImage = I_IMAGECALLBACK;
5540 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5541 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5543 dispInfo.item.mask |= LVIF_TEXT;
5544 dispInfo.item.pszText = lpLVItem->pszText;
5545 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5546 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5547 *dispInfo.item.pszText = '\0';
5550 /* If we don't have all the requested info, query the application */
5551 if (dispInfo.item.mask != 0)
5553 dispInfo.item.iItem = lpLVItem->iItem;
5554 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5555 dispInfo.item.lParam = lpItem->lParam;
5556 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5557 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5560 /* we should not store values for subitems */
5561 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5563 /* Now, handle the iImage field */
5564 if (dispInfo.item.mask & LVIF_IMAGE)
5566 lpLVItem->iImage = dispInfo.item.iImage;
5567 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5568 pItemHdr->iImage = dispInfo.item.iImage;
5570 else if (lpLVItem->mask & LVIF_IMAGE)
5572 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5573 lpLVItem->iImage = pItemHdr->iImage;
5574 else
5575 lpLVItem->iImage = 0;
5578 /* The pszText field */
5579 if (dispInfo.item.mask & LVIF_TEXT)
5581 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5582 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5584 lpLVItem->pszText = dispInfo.item.pszText;
5586 else if (lpLVItem->mask & LVIF_TEXT)
5588 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5589 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5592 /* Next is the lParam field */
5593 if (dispInfo.item.mask & LVIF_PARAM)
5595 lpLVItem->lParam = dispInfo.item.lParam;
5596 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5597 lpItem->lParam = dispInfo.item.lParam;
5599 else if (lpLVItem->mask & LVIF_PARAM)
5600 lpLVItem->lParam = lpItem->lParam;
5602 /* if this is a subitem, we're done */
5603 if (isubitem) return TRUE;
5605 /* ... the state field (this one is different due to uCallbackmask) */
5606 if (lpLVItem->mask & LVIF_STATE)
5608 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5609 if (dispInfo.item.mask & LVIF_STATE)
5611 lpLVItem->state &= ~dispInfo.item.stateMask;
5612 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5614 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5616 lpLVItem->state &= ~LVIS_FOCUSED;
5617 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5618 lpLVItem->state |= LVIS_FOCUSED;
5620 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5622 lpLVItem->state &= ~LVIS_SELECTED;
5623 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5624 lpLVItem->state |= LVIS_SELECTED;
5628 /* and last, but not least, the indent field */
5629 if (lpLVItem->mask & LVIF_INDENT)
5630 lpLVItem->iIndent = lpItem->iIndent;
5632 return TRUE;
5635 /***
5636 * DESCRIPTION:
5637 * Retrieves item attributes.
5639 * PARAMETER(S):
5640 * [I] hwnd : window handle
5641 * [IO] lpLVItem : item info
5642 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5643 * if FALSE, then lpLVItem is a LPLVITEMA.
5645 * NOTE:
5646 * This is the external 'GetItem' interface -- it properly copies
5647 * the text in the provided buffer.
5649 * RETURN:
5650 * SUCCESS : TRUE
5651 * FAILURE : FALSE
5653 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5655 LPWSTR pszText;
5656 BOOL bResult;
5658 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5659 return FALSE;
5661 pszText = lpLVItem->pszText;
5662 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5663 if (bResult && lpLVItem->pszText != pszText)
5664 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5665 lpLVItem->pszText = pszText;
5667 return bResult;
5671 /***
5672 * DESCRIPTION:
5673 * Retrieves the position (upper-left) of the listview control item.
5674 * Note that for LVS_ICON style, the upper-left is that of the icon
5675 * and not the bounding box.
5677 * PARAMETER(S):
5678 * [I] infoPtr : valid pointer to the listview structure
5679 * [I] nItem : item index
5680 * [O] lpptPosition : coordinate information
5682 * RETURN:
5683 * SUCCESS : TRUE
5684 * FAILURE : FALSE
5686 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5688 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5689 POINT Origin;
5691 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5693 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5695 LISTVIEW_GetOrigin(infoPtr, &Origin);
5696 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5698 if (uView == LVS_ICON)
5700 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5701 lpptPosition->y += ICON_TOP_PADDING;
5703 lpptPosition->x += Origin.x;
5704 lpptPosition->y += Origin.y;
5706 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5707 return TRUE;
5711 /***
5712 * DESCRIPTION:
5713 * Retrieves the bounding rectangle for a listview control item.
5715 * PARAMETER(S):
5716 * [I] infoPtr : valid pointer to the listview structure
5717 * [I] nItem : item index
5718 * [IO] lprc : bounding rectangle coordinates
5719 * lprc->left specifies the portion of the item for which the bounding
5720 * rectangle will be retrieved.
5722 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5723 * including the icon and label.
5725 * * For LVS_ICON
5726 * * Experiment shows that native control returns:
5727 * * width = min (48, length of text line)
5728 * * .left = position.x - (width - iconsize.cx)/2
5729 * * .right = .left + width
5730 * * height = #lines of text * ntmHeight + icon height + 8
5731 * * .top = position.y - 2
5732 * * .bottom = .top + height
5733 * * separation between items .y = itemSpacing.cy - height
5734 * * .x = itemSpacing.cx - width
5735 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5737 * * For LVS_ICON
5738 * * Experiment shows that native control returns:
5739 * * width = iconSize.cx + 16
5740 * * .left = position.x - (width - iconsize.cx)/2
5741 * * .right = .left + width
5742 * * height = iconSize.cy + 4
5743 * * .top = position.y - 2
5744 * * .bottom = .top + height
5745 * * separation between items .y = itemSpacing.cy - height
5746 * * .x = itemSpacing.cx - width
5747 * LVIR_LABEL Returns the bounding rectangle of the item text.
5749 * * For LVS_ICON
5750 * * Experiment shows that native control returns:
5751 * * width = text length
5752 * * .left = position.x - width/2
5753 * * .right = .left + width
5754 * * height = ntmH * linecount + 2
5755 * * .top = position.y + iconSize.cy + 6
5756 * * .bottom = .top + height
5757 * * separation between items .y = itemSpacing.cy - height
5758 * * .x = itemSpacing.cx - width
5759 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5760 * rectangles, but excludes columns in report view.
5762 * RETURN:
5763 * SUCCESS : TRUE
5764 * FAILURE : FALSE
5766 * NOTES
5767 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5768 * upon whether the window has the focus currently and on whether the item
5769 * is the one with the focus. Ensure that the control's record of which
5770 * item has the focus agrees with the items' records.
5772 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5774 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5775 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5776 BOOL doLabel = TRUE, oversizedBox = FALSE;
5777 POINT Position, Origin;
5778 LVITEMW lvItem;
5780 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5782 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5784 LISTVIEW_GetOrigin(infoPtr, &Origin);
5785 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5787 /* Be smart and try to figure out the minimum we have to do */
5788 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5789 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5790 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5791 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5792 oversizedBox = TRUE;
5794 /* get what we need from the item before hand, so we make
5795 * only one request. This can speed up things, if data
5796 * is stored on the app side */
5797 lvItem.mask = 0;
5798 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5799 if (doLabel) lvItem.mask |= LVIF_TEXT;
5800 lvItem.iItem = nItem;
5801 lvItem.iSubItem = 0;
5802 lvItem.pszText = szDispText;
5803 lvItem.cchTextMax = DISP_TEXT_SIZE;
5804 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5805 /* we got the state already up, simulate it here, to avoid a reget */
5806 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5808 lvItem.mask |= LVIF_STATE;
5809 lvItem.stateMask = LVIS_FOCUSED;
5810 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5813 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5814 lprc->left = LVIR_BOUNDS;
5815 switch(lprc->left)
5817 case LVIR_ICON:
5818 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5819 break;
5821 case LVIR_LABEL:
5822 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5823 break;
5825 case LVIR_BOUNDS:
5826 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5827 break;
5829 case LVIR_SELECTBOUNDS:
5830 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5831 break;
5833 default:
5834 WARN("Unknown value: %d\n", lprc->left);
5835 return FALSE;
5838 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5840 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5842 return TRUE;
5845 /***
5846 * DESCRIPTION:
5847 * Retrieves the spacing between listview control items.
5849 * PARAMETER(S):
5850 * [I] infoPtr : valid pointer to the listview structure
5851 * [IO] lprc : rectangle to receive the output
5852 * on input, lprc->top = nSubItem
5853 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5855 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5856 * not only those of the first column.
5857 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5859 * RETURN:
5860 * TRUE: success
5861 * FALSE: failure
5863 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5865 POINT Position;
5866 LVITEMW lvItem;
5867 INT nColumn;
5869 if (!lprc) return FALSE;
5871 nColumn = lprc->top;
5873 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5874 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5875 if (lprc->top == 0)
5876 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5878 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5880 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5882 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5884 lvItem.mask = 0;
5885 lvItem.iItem = nItem;
5886 lvItem.iSubItem = nColumn;
5888 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5889 switch(lprc->left)
5891 case LVIR_ICON:
5892 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5893 break;
5895 case LVIR_LABEL:
5896 case LVIR_BOUNDS:
5897 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5898 break;
5900 default:
5901 ERR("Unknown bounds=%d\n", lprc->left);
5902 return FALSE;
5905 OffsetRect(lprc, Position.x, Position.y);
5906 return TRUE;
5910 /***
5911 * DESCRIPTION:
5912 * Retrieves the width of a label.
5914 * PARAMETER(S):
5915 * [I] infoPtr : valid pointer to the listview structure
5917 * RETURN:
5918 * SUCCESS : string width (in pixels)
5919 * FAILURE : zero
5921 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5923 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5924 LVITEMW lvItem;
5926 TRACE("(nItem=%d)\n", nItem);
5928 lvItem.mask = LVIF_TEXT;
5929 lvItem.iItem = nItem;
5930 lvItem.iSubItem = 0;
5931 lvItem.pszText = szDispText;
5932 lvItem.cchTextMax = DISP_TEXT_SIZE;
5933 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5935 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5938 /***
5939 * DESCRIPTION:
5940 * Retrieves the spacing between listview control items.
5942 * PARAMETER(S):
5943 * [I] infoPtr : valid pointer to the listview structure
5944 * [I] bSmall : flag for small or large icon
5946 * RETURN:
5947 * Horizontal + vertical spacing
5949 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5951 LONG lResult;
5953 if (!bSmall)
5955 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5957 else
5959 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5960 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5961 else
5962 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5964 return lResult;
5967 /***
5968 * DESCRIPTION:
5969 * Retrieves the state of a listview control item.
5971 * PARAMETER(S):
5972 * [I] infoPtr : valid pointer to the listview structure
5973 * [I] nItem : item index
5974 * [I] uMask : state mask
5976 * RETURN:
5977 * State specified by the mask.
5979 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5981 LVITEMW lvItem;
5983 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5985 lvItem.iItem = nItem;
5986 lvItem.iSubItem = 0;
5987 lvItem.mask = LVIF_STATE;
5988 lvItem.stateMask = uMask;
5989 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5991 return lvItem.state & uMask;
5994 /***
5995 * DESCRIPTION:
5996 * Retrieves the text of a listview control item or subitem.
5998 * PARAMETER(S):
5999 * [I] hwnd : window handle
6000 * [I] nItem : item index
6001 * [IO] lpLVItem : item information
6002 * [I] isW : TRUE if lpLVItem is Unicode
6004 * RETURN:
6005 * SUCCESS : string length
6006 * FAILURE : 0
6008 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6010 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6012 lpLVItem->mask = LVIF_TEXT;
6013 lpLVItem->iItem = nItem;
6014 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6016 return textlenT(lpLVItem->pszText, isW);
6019 /***
6020 * DESCRIPTION:
6021 * Searches for an item based on properties + relationships.
6023 * PARAMETER(S):
6024 * [I] infoPtr : valid pointer to the listview structure
6025 * [I] nItem : item index
6026 * [I] uFlags : relationship flag
6028 * RETURN:
6029 * SUCCESS : item index
6030 * FAILURE : -1
6032 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6034 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6035 UINT uMask = 0;
6036 LVFINDINFOW lvFindInfo;
6037 INT nCountPerColumn;
6038 INT nCountPerRow;
6039 INT i;
6041 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6042 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6044 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6046 if (uFlags & LVNI_CUT)
6047 uMask |= LVIS_CUT;
6049 if (uFlags & LVNI_DROPHILITED)
6050 uMask |= LVIS_DROPHILITED;
6052 if (uFlags & LVNI_FOCUSED)
6053 uMask |= LVIS_FOCUSED;
6055 if (uFlags & LVNI_SELECTED)
6056 uMask |= LVIS_SELECTED;
6058 /* if we're asked for the focused item, that's only one,
6059 * so it's worth optimizing */
6060 if (uFlags & LVNI_FOCUSED)
6062 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6063 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6066 if (uFlags & LVNI_ABOVE)
6068 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6070 while (nItem >= 0)
6072 nItem--;
6073 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6074 return nItem;
6077 else
6079 /* Special case for autoarrange - move 'til the top of a list */
6080 if (is_autoarrange(infoPtr))
6082 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6083 while (nItem - nCountPerRow >= 0)
6085 nItem -= nCountPerRow;
6086 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6087 return nItem;
6089 return -1;
6091 lvFindInfo.flags = LVFI_NEARESTXY;
6092 lvFindInfo.vkDirection = VK_UP;
6093 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6094 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6096 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6097 return nItem;
6101 else if (uFlags & LVNI_BELOW)
6103 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6105 while (nItem < infoPtr->nItemCount)
6107 nItem++;
6108 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6109 return nItem;
6112 else
6114 /* Special case for autoarrange - move 'til the bottom of a list */
6115 if (is_autoarrange(infoPtr))
6117 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6118 while (nItem + nCountPerRow < infoPtr->nItemCount )
6120 nItem += nCountPerRow;
6121 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6122 return nItem;
6124 return -1;
6126 lvFindInfo.flags = LVFI_NEARESTXY;
6127 lvFindInfo.vkDirection = VK_DOWN;
6128 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6129 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6131 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6132 return nItem;
6136 else if (uFlags & LVNI_TOLEFT)
6138 if (uView == LVS_LIST)
6140 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6141 while (nItem - nCountPerColumn >= 0)
6143 nItem -= nCountPerColumn;
6144 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6145 return nItem;
6148 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6150 /* Special case for autoarrange - move 'ti the beginning of a row */
6151 if (is_autoarrange(infoPtr))
6153 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6154 while (nItem % nCountPerRow > 0)
6156 nItem --;
6157 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6158 return nItem;
6160 return -1;
6162 lvFindInfo.flags = LVFI_NEARESTXY;
6163 lvFindInfo.vkDirection = VK_LEFT;
6164 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6165 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6167 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6168 return nItem;
6172 else if (uFlags & LVNI_TORIGHT)
6174 if (uView == LVS_LIST)
6176 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6177 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6179 nItem += nCountPerColumn;
6180 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6181 return nItem;
6184 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6186 /* Special case for autoarrange - move 'til the end of a row */
6187 if (is_autoarrange(infoPtr))
6189 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6190 while (nItem % nCountPerRow < nCountPerRow - 1 )
6192 nItem ++;
6193 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6194 return nItem;
6196 return -1;
6198 lvFindInfo.flags = LVFI_NEARESTXY;
6199 lvFindInfo.vkDirection = VK_RIGHT;
6200 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6201 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6203 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6204 return nItem;
6208 else
6210 nItem++;
6212 /* search by index */
6213 for (i = nItem; i < infoPtr->nItemCount; i++)
6215 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6216 return i;
6220 return -1;
6223 /* LISTVIEW_GetNumberOfWorkAreas */
6225 /***
6226 * DESCRIPTION:
6227 * Retrieves the origin coordinates when in icon or small icon display mode.
6229 * PARAMETER(S):
6230 * [I] infoPtr : valid pointer to the listview structure
6231 * [O] lpptOrigin : coordinate information
6233 * RETURN:
6234 * None.
6236 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6238 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6239 INT nHorzPos = 0, nVertPos = 0;
6240 SCROLLINFO scrollInfo;
6242 scrollInfo.cbSize = sizeof(SCROLLINFO);
6243 scrollInfo.fMask = SIF_POS;
6245 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6246 nHorzPos = scrollInfo.nPos;
6247 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6248 nVertPos = scrollInfo.nPos;
6250 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6252 lpptOrigin->x = infoPtr->rcList.left;
6253 lpptOrigin->y = infoPtr->rcList.top;
6254 if (uView == LVS_LIST)
6255 nHorzPos *= infoPtr->nItemWidth;
6256 else if (uView == LVS_REPORT)
6257 nVertPos *= infoPtr->nItemHeight;
6259 lpptOrigin->x -= nHorzPos;
6260 lpptOrigin->y -= nVertPos;
6262 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6265 /***
6266 * DESCRIPTION:
6267 * Retrieves the width of a string.
6269 * PARAMETER(S):
6270 * [I] hwnd : window handle
6271 * [I] lpszText : text string to process
6272 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6274 * RETURN:
6275 * SUCCESS : string width (in pixels)
6276 * FAILURE : zero
6278 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6280 SIZE stringSize;
6282 stringSize.cx = 0;
6283 if (is_textT(lpszText, isW))
6285 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6286 HDC hdc = GetDC(infoPtr->hwndSelf);
6287 HFONT hOldFont = SelectObject(hdc, hFont);
6289 if (isW)
6290 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6291 else
6292 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6293 SelectObject(hdc, hOldFont);
6294 ReleaseDC(infoPtr->hwndSelf, hdc);
6296 return stringSize.cx;
6299 /***
6300 * DESCRIPTION:
6301 * Determines which listview item is located at the specified position.
6303 * PARAMETER(S):
6304 * [I] infoPtr : valid pointer to the listview structure
6305 * [IO] lpht : hit test information
6306 * [I] subitem : fill out iSubItem.
6307 * [I] select : return the index only if the hit selects the item
6309 * NOTE:
6310 * (mm 20001022): We must not allow iSubItem to be touched, for
6311 * an app might pass only a structure with space up to iItem!
6312 * (MS Office 97 does that for instance in the file open dialog)
6314 * RETURN:
6315 * SUCCESS : item index
6316 * FAILURE : -1
6318 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6320 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6321 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6322 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6323 POINT Origin, Position, opt;
6324 LVITEMW lvItem;
6325 ITERATOR i;
6326 INT iItem;
6328 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6330 lpht->flags = 0;
6331 lpht->iItem = -1;
6332 if (subitem) lpht->iSubItem = 0;
6334 if (infoPtr->rcList.left > lpht->pt.x)
6335 lpht->flags |= LVHT_TOLEFT;
6336 else if (infoPtr->rcList.right < lpht->pt.x)
6337 lpht->flags |= LVHT_TORIGHT;
6339 if (infoPtr->rcList.top > lpht->pt.y)
6340 lpht->flags |= LVHT_ABOVE;
6341 else if (infoPtr->rcList.bottom < lpht->pt.y)
6342 lpht->flags |= LVHT_BELOW;
6344 TRACE("lpht->flags=0x%x\n", lpht->flags);
6345 if (lpht->flags) return -1;
6347 lpht->flags |= LVHT_NOWHERE;
6349 LISTVIEW_GetOrigin(infoPtr, &Origin);
6351 /* first deal with the large items */
6352 rcSearch.left = lpht->pt.x;
6353 rcSearch.top = lpht->pt.y;
6354 rcSearch.right = rcSearch.left + 1;
6355 rcSearch.bottom = rcSearch.top + 1;
6357 iterator_frameditems(&i, infoPtr, &rcSearch);
6358 iterator_next(&i); /* go to first item in the sequence */
6359 iItem = i.nItem;
6360 iterator_destroy(&i);
6362 TRACE("lpht->iItem=%d\n", iItem);
6363 if (iItem == -1) return -1;
6365 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6366 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6367 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6368 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6369 lvItem.iItem = iItem;
6370 lvItem.iSubItem = 0;
6371 lvItem.pszText = szDispText;
6372 lvItem.cchTextMax = DISP_TEXT_SIZE;
6373 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6374 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6376 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6377 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6378 opt.x = lpht->pt.x - Position.x - Origin.x;
6379 opt.y = lpht->pt.y - Position.y - Origin.y;
6381 if (uView == LVS_REPORT)
6382 rcBounds = rcBox;
6383 else
6385 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6386 UnionRect(&rcBounds, &rcBounds, &rcState);
6388 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6389 if (!PtInRect(&rcBounds, opt)) return -1;
6391 if (PtInRect(&rcIcon, opt))
6392 lpht->flags |= LVHT_ONITEMICON;
6393 else if (PtInRect(&rcLabel, opt))
6394 lpht->flags |= LVHT_ONITEMLABEL;
6395 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6396 lpht->flags |= LVHT_ONITEMSTATEICON;
6397 if (lpht->flags & LVHT_ONITEM)
6398 lpht->flags &= ~LVHT_NOWHERE;
6400 TRACE("lpht->flags=0x%x\n", lpht->flags);
6401 if (uView == LVS_REPORT && subitem)
6403 INT j;
6405 rcBounds.right = rcBounds.left;
6406 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6408 rcBounds.left = rcBounds.right;
6409 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6410 if (PtInRect(&rcBounds, opt))
6412 lpht->iSubItem = j;
6413 break;
6418 if (select && !(uView == LVS_REPORT &&
6419 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6420 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6422 if (uView == LVS_REPORT)
6424 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6425 UnionRect(&rcBounds, &rcBounds, &rcState);
6427 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6429 return lpht->iItem = iItem;
6433 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6434 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6435 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6436 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6437 their own sort proc. when sending LVM_SORTITEMS.
6439 /* Platform SDK:
6440 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6442 LVS_SORTXXX must be specified,
6443 LVS_OWNERDRAW is not set,
6444 <item>.pszText is not LPSTR_TEXTCALLBACK.
6446 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6447 are sorted based on item text..."
6449 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6451 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6452 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6453 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6455 /* if we're sorting descending, negate the return value */
6456 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6459 /***
6460 * DESCRIPTION:
6461 * Inserts a new item in the listview control.
6463 * PARAMETER(S):
6464 * [I] infoPtr : valid pointer to the listview structure
6465 * [I] lpLVItem : item information
6466 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6468 * RETURN:
6469 * SUCCESS : new item index
6470 * FAILURE : -1
6472 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6474 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6475 INT nItem;
6476 HDPA hdpaSubItems;
6477 NMLISTVIEW nmlv;
6478 ITEM_INFO *lpItem;
6479 BOOL is_sorted, has_changed;
6480 LVITEMW item;
6481 HWND hwndSelf = infoPtr->hwndSelf;
6483 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6485 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6487 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6488 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6490 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6492 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6494 /* insert item in listview control data structure */
6495 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6496 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6498 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6499 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6501 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6503 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6504 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6505 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6506 if (nItem == -1) goto fail;
6507 infoPtr->nItemCount++;
6509 /* shift indices first so they don't get tangled */
6510 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6512 /* set the item attributes */
6513 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6515 /* full size structure expected - _WIN32IE >= 0x560 */
6516 item = *lpLVItem;
6518 else if (lpLVItem->mask & LVIF_INDENT)
6520 /* indent member expected - _WIN32IE >= 0x300 */
6521 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6523 else
6525 /* minimal structure expected */
6526 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6528 item.iItem = nItem;
6529 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6531 item.mask |= LVIF_STATE;
6532 item.stateMask |= LVIS_STATEIMAGEMASK;
6533 item.state &= ~LVIS_STATEIMAGEMASK;
6534 item.state |= INDEXTOSTATEIMAGEMASK(1);
6536 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6538 /* if we're sorted, sort the list, and update the index */
6539 if (is_sorted)
6541 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6542 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6543 assert(nItem != -1);
6546 /* make room for the position, if we are in the right mode */
6547 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6549 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6550 goto undo;
6551 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6553 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6554 goto undo;
6558 /* send LVN_INSERTITEM notification */
6559 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6560 nmlv.iItem = nItem;
6561 nmlv.lParam = lpItem->lParam;
6562 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6563 if (!IsWindow(hwndSelf))
6564 return -1;
6566 /* align items (set position of each item) */
6567 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6569 POINT pt;
6571 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6572 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6573 else
6574 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6576 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6579 /* now is the invalidation fun */
6580 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6581 return nItem;
6583 undo:
6584 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6585 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6586 infoPtr->nItemCount--;
6587 fail:
6588 DPA_DeletePtr(hdpaSubItems, 0);
6589 DPA_Destroy (hdpaSubItems);
6590 Free (lpItem);
6591 return -1;
6594 /***
6595 * DESCRIPTION:
6596 * Redraws a range of items.
6598 * PARAMETER(S):
6599 * [I] infoPtr : valid pointer to the listview structure
6600 * [I] nFirst : first item
6601 * [I] nLast : last item
6603 * RETURN:
6604 * SUCCESS : TRUE
6605 * FAILURE : FALSE
6607 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6609 INT i;
6611 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6612 max(nFirst, nLast) >= infoPtr->nItemCount)
6613 return FALSE;
6615 for (i = nFirst; i <= nLast; i++)
6616 LISTVIEW_InvalidateItem(infoPtr, i);
6618 return TRUE;
6621 /***
6622 * DESCRIPTION:
6623 * Scroll the content of a listview.
6625 * PARAMETER(S):
6626 * [I] infoPtr : valid pointer to the listview structure
6627 * [I] dx : horizontal scroll amount in pixels
6628 * [I] dy : vertical scroll amount in pixels
6630 * RETURN:
6631 * SUCCESS : TRUE
6632 * FAILURE : FALSE
6634 * COMMENTS:
6635 * If the control is in report mode (LVS_REPORT) the control can
6636 * be scrolled only in line increments. "dy" will be rounded to the
6637 * nearest number of pixels that are a whole line. Ex: if line height
6638 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6639 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6641 * For: (per experimentation with native control and CSpy ListView)
6642 * LVS_ICON dy=1 = 1 pixel (vertical only)
6643 * dx ignored
6644 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6645 * dx ignored
6646 * LVS_LIST dx=1 = 1 column (horizontal only)
6647 * but will only scroll 1 column per message
6648 * no matter what the value.
6649 * dy must be 0 or FALSE returned.
6650 * LVS_REPORT dx=1 = 1 pixel
6651 * dy= see above
6654 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6656 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6657 case LVS_REPORT:
6658 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6659 dy /= infoPtr->nItemHeight;
6660 break;
6661 case LVS_LIST:
6662 if (dy != 0) return FALSE;
6663 break;
6664 default: /* icon */
6665 dx = 0;
6666 break;
6669 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6670 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6672 return TRUE;
6675 /***
6676 * DESCRIPTION:
6677 * Sets the background color.
6679 * PARAMETER(S):
6680 * [I] infoPtr : valid pointer to the listview structure
6681 * [I] clrBk : background color
6683 * RETURN:
6684 * SUCCESS : TRUE
6685 * FAILURE : FALSE
6687 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6689 TRACE("(clrBk=%x)\n", clrBk);
6691 if(infoPtr->clrBk != clrBk) {
6692 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6693 infoPtr->clrBk = clrBk;
6694 if (clrBk == CLR_NONE)
6695 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6696 else
6697 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6698 LISTVIEW_InvalidateList(infoPtr);
6701 return TRUE;
6704 /* LISTVIEW_SetBkImage */
6706 /*** Helper for {Insert,Set}ColumnT *only* */
6707 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6708 const LVCOLUMNW *lpColumn, BOOL isW)
6710 if (lpColumn->mask & LVCF_FMT)
6712 /* format member is valid */
6713 lphdi->mask |= HDI_FORMAT;
6715 /* set text alignment (leftmost column must be left-aligned) */
6716 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6717 lphdi->fmt |= HDF_LEFT;
6718 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6719 lphdi->fmt |= HDF_RIGHT;
6720 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6721 lphdi->fmt |= HDF_CENTER;
6723 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6724 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6726 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6728 lphdi->fmt |= HDF_IMAGE;
6729 lphdi->iImage = I_IMAGECALLBACK;
6733 if (lpColumn->mask & LVCF_WIDTH)
6735 lphdi->mask |= HDI_WIDTH;
6736 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6738 /* make it fill the remainder of the controls width */
6739 RECT rcHeader;
6740 INT item_index;
6742 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6744 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6745 lphdi->cxy += rcHeader.right - rcHeader.left;
6748 /* retrieve the layout of the header */
6749 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6750 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6752 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6754 else
6755 lphdi->cxy = lpColumn->cx;
6758 if (lpColumn->mask & LVCF_TEXT)
6760 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6761 lphdi->fmt |= HDF_STRING;
6762 lphdi->pszText = lpColumn->pszText;
6763 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6766 if (lpColumn->mask & LVCF_IMAGE)
6768 lphdi->mask |= HDI_IMAGE;
6769 lphdi->iImage = lpColumn->iImage;
6772 if (lpColumn->mask & LVCF_ORDER)
6774 lphdi->mask |= HDI_ORDER;
6775 lphdi->iOrder = lpColumn->iOrder;
6780 /***
6781 * DESCRIPTION:
6782 * Inserts a new column.
6784 * PARAMETER(S):
6785 * [I] infoPtr : valid pointer to the listview structure
6786 * [I] nColumn : column index
6787 * [I] lpColumn : column information
6788 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6790 * RETURN:
6791 * SUCCESS : new column index
6792 * FAILURE : -1
6794 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6795 const LVCOLUMNW *lpColumn, BOOL isW)
6797 COLUMN_INFO *lpColumnInfo;
6798 INT nNewColumn;
6799 HDITEMW hdi;
6801 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6803 if (!lpColumn || nColumn < 0) return -1;
6804 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6806 ZeroMemory(&hdi, sizeof(HDITEMW));
6807 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6810 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6811 * (can be seen in SPY) otherwise column never gets added.
6813 if (!(lpColumn->mask & LVCF_WIDTH)) {
6814 hdi.mask |= HDI_WIDTH;
6815 hdi.cxy = 10;
6819 * when the iSubItem is available Windows copies it to the header lParam. It seems
6820 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6822 if (lpColumn->mask & LVCF_SUBITEM)
6824 hdi.mask |= HDI_LPARAM;
6825 hdi.lParam = lpColumn->iSubItem;
6828 /* insert item in header control */
6829 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6830 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6831 (WPARAM)nColumn, (LPARAM)&hdi);
6832 if (nNewColumn == -1) return -1;
6833 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6835 /* create our own column info */
6836 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6837 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6839 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6840 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6842 /* now we have to actually adjust the data */
6843 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6845 SUBITEM_INFO *lpSubItem;
6846 HDPA hdpaSubItems;
6847 INT nItem, i;
6849 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6851 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6852 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6854 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6855 if (lpSubItem->iSubItem >= nNewColumn)
6856 lpSubItem->iSubItem++;
6861 /* make space for the new column */
6862 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6863 LISTVIEW_UpdateItemSize(infoPtr);
6865 return nNewColumn;
6867 fail:
6868 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6869 if (lpColumnInfo)
6871 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6872 Free(lpColumnInfo);
6874 return -1;
6877 /***
6878 * DESCRIPTION:
6879 * Sets the attributes of a header item.
6881 * PARAMETER(S):
6882 * [I] infoPtr : valid pointer to the listview structure
6883 * [I] nColumn : column index
6884 * [I] lpColumn : column attributes
6885 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6887 * RETURN:
6888 * SUCCESS : TRUE
6889 * FAILURE : FALSE
6891 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6892 const LVCOLUMNW *lpColumn, BOOL isW)
6894 HDITEMW hdi, hdiget;
6895 BOOL bResult;
6897 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6899 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6901 ZeroMemory(&hdi, sizeof(HDITEMW));
6902 if (lpColumn->mask & LVCF_FMT)
6904 hdi.mask |= HDI_FORMAT;
6905 hdiget.mask = HDI_FORMAT;
6906 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6907 hdi.fmt = hdiget.fmt & HDF_STRING;
6909 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6911 /* set header item attributes */
6912 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6913 if (!bResult) return FALSE;
6915 if (lpColumn->mask & LVCF_FMT)
6917 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6918 int oldFmt = lpColumnInfo->fmt;
6920 lpColumnInfo->fmt = lpColumn->fmt;
6921 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6923 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6924 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6928 return TRUE;
6931 /***
6932 * DESCRIPTION:
6933 * Sets the column order array
6935 * PARAMETERS:
6936 * [I] infoPtr : valid pointer to the listview structure
6937 * [I] iCount : number of elements in column order array
6938 * [I] lpiArray : pointer to column order array
6940 * RETURN:
6941 * SUCCESS : TRUE
6942 * FAILURE : FALSE
6944 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6946 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6948 if (!lpiArray)
6949 return FALSE;
6951 return TRUE;
6955 /***
6956 * DESCRIPTION:
6957 * Sets the width of a column
6959 * PARAMETERS:
6960 * [I] infoPtr : valid pointer to the listview structure
6961 * [I] nColumn : column index
6962 * [I] cx : column width
6964 * RETURN:
6965 * SUCCESS : TRUE
6966 * FAILURE : FALSE
6968 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6970 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6971 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6972 INT max_cx = 0;
6973 HDITEMW hdi;
6975 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6977 /* set column width only if in report or list mode */
6978 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6980 /* take care of invalid cx values */
6981 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6982 else if (uView == LVS_LIST && cx < 1) return FALSE;
6984 /* resize all columns if in LVS_LIST mode */
6985 if(uView == LVS_LIST)
6987 infoPtr->nItemWidth = cx;
6988 LISTVIEW_InvalidateList(infoPtr);
6989 return TRUE;
6992 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6994 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6996 INT nLabelWidth;
6997 LVITEMW lvItem;
6999 lvItem.mask = LVIF_TEXT;
7000 lvItem.iItem = 0;
7001 lvItem.iSubItem = nColumn;
7002 lvItem.pszText = szDispText;
7003 lvItem.cchTextMax = DISP_TEXT_SIZE;
7004 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7006 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7007 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7008 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7010 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7011 max_cx += infoPtr->iconSize.cx;
7012 max_cx += TRAILING_LABEL_PADDING;
7015 /* autosize based on listview items width */
7016 if(cx == LVSCW_AUTOSIZE)
7017 cx = max_cx;
7018 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7020 /* if iCol is the last column make it fill the remainder of the controls width */
7021 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7023 RECT rcHeader;
7024 POINT Origin;
7026 LISTVIEW_GetOrigin(infoPtr, &Origin);
7027 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7029 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7031 else
7033 /* Despite what the MS docs say, if this is not the last
7034 column, then MS resizes the column to the width of the
7035 largest text string in the column, including headers
7036 and items. This is different from LVSCW_AUTOSIZE in that
7037 LVSCW_AUTOSIZE ignores the header string length. */
7038 cx = 0;
7040 /* retrieve header text */
7041 hdi.mask = HDI_TEXT;
7042 hdi.cchTextMax = DISP_TEXT_SIZE;
7043 hdi.pszText = szDispText;
7044 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7046 HDC hdc = GetDC(infoPtr->hwndSelf);
7047 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7048 SIZE size;
7050 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7051 cx = size.cx + TRAILING_HEADER_PADDING;
7052 /* FIXME: Take into account the header image, if one is present */
7053 SelectObject(hdc, old_font);
7054 ReleaseDC(infoPtr->hwndSelf, hdc);
7056 cx = max (cx, max_cx);
7060 if (cx < 0) return FALSE;
7062 /* call header to update the column change */
7063 hdi.mask = HDI_WIDTH;
7064 hdi.cxy = cx;
7065 TRACE("hdi.cxy=%d\n", hdi.cxy);
7066 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7069 /***
7070 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7073 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7075 HDC hdc_wnd, hdc;
7076 HBITMAP hbm_im, hbm_mask, hbm_orig;
7077 RECT rc;
7078 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7079 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7080 HIMAGELIST himl;
7082 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7083 ILC_COLOR | ILC_MASK, 2, 2);
7084 hdc_wnd = GetDC(infoPtr->hwndSelf);
7085 hdc = CreateCompatibleDC(hdc_wnd);
7086 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7087 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7088 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7090 rc.left = rc.top = 0;
7091 rc.right = GetSystemMetrics(SM_CXSMICON);
7092 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7094 hbm_orig = SelectObject(hdc, hbm_mask);
7095 FillRect(hdc, &rc, hbr_white);
7096 InflateRect(&rc, -3, -3);
7097 FillRect(hdc, &rc, hbr_black);
7099 SelectObject(hdc, hbm_im);
7100 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7101 SelectObject(hdc, hbm_orig);
7102 ImageList_Add(himl, hbm_im, hbm_mask);
7104 SelectObject(hdc, hbm_im);
7105 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7106 SelectObject(hdc, hbm_orig);
7107 ImageList_Add(himl, hbm_im, hbm_mask);
7109 DeleteObject(hbm_mask);
7110 DeleteObject(hbm_im);
7111 DeleteDC(hdc);
7113 return himl;
7116 /***
7117 * DESCRIPTION:
7118 * Sets the extended listview style.
7120 * PARAMETERS:
7121 * [I] infoPtr : valid pointer to the listview structure
7122 * [I] dwMask : mask
7123 * [I] dwStyle : style
7125 * RETURN:
7126 * SUCCESS : previous style
7127 * FAILURE : 0
7129 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7131 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7133 /* set new style */
7134 if (dwMask)
7135 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7136 else
7137 infoPtr->dwLvExStyle = dwExStyle;
7139 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7141 HIMAGELIST himl = 0;
7142 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7144 LVITEMW item;
7145 item.mask = LVIF_STATE;
7146 item.stateMask = LVIS_STATEIMAGEMASK;
7147 item.state = INDEXTOSTATEIMAGEMASK(1);
7148 LISTVIEW_SetItemState(infoPtr, -1, &item);
7150 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7152 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7155 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7157 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7158 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7159 dwStyle |= HDS_DRAGDROP;
7160 else
7161 dwStyle &= ~HDS_DRAGDROP;
7162 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7165 /* GRIDLINES adds decoration at top so changes sizes */
7166 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7168 LISTVIEW_UpdateSize(infoPtr);
7172 LISTVIEW_InvalidateList(infoPtr);
7173 return dwOldExStyle;
7176 /***
7177 * DESCRIPTION:
7178 * Sets the new hot cursor used during hot tracking and hover selection.
7180 * PARAMETER(S):
7181 * [I] infoPtr : valid pointer to the listview structure
7182 * [I] hCursor : the new hot cursor handle
7184 * RETURN:
7185 * Returns the previous hot cursor
7187 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7189 HCURSOR oldCursor = infoPtr->hHotCursor;
7191 infoPtr->hHotCursor = hCursor;
7193 return oldCursor;
7197 /***
7198 * DESCRIPTION:
7199 * Sets the hot item index.
7201 * PARAMETERS:
7202 * [I] infoPtr : valid pointer to the listview structure
7203 * [I] iIndex : index
7205 * RETURN:
7206 * SUCCESS : previous hot item index
7207 * FAILURE : -1 (no hot item)
7209 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7211 INT iOldIndex = infoPtr->nHotItem;
7213 infoPtr->nHotItem = iIndex;
7215 return iOldIndex;
7219 /***
7220 * DESCRIPTION:
7221 * Sets the amount of time the cursor must hover over an item before it is selected.
7223 * PARAMETER(S):
7224 * [I] infoPtr : valid pointer to the listview structure
7225 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7227 * RETURN:
7228 * Returns the previous hover time
7230 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7232 DWORD oldHoverTime = infoPtr->dwHoverTime;
7234 infoPtr->dwHoverTime = dwHoverTime;
7236 return oldHoverTime;
7239 /***
7240 * DESCRIPTION:
7241 * Sets spacing for icons of LVS_ICON style.
7243 * PARAMETER(S):
7244 * [I] infoPtr : valid pointer to the listview structure
7245 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7246 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7248 * RETURN:
7249 * MAKELONG(oldcx, oldcy)
7251 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7253 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7254 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7256 TRACE("requested=(%d,%d)\n", cx, cy);
7258 /* this is supported only for LVS_ICON style */
7259 if (uView != LVS_ICON) return oldspacing;
7261 /* set to defaults, if instructed to */
7262 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7263 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7265 /* if 0 then compute width
7266 * FIXME: Should scan each item and determine max width of
7267 * icon or label, then make that the width */
7268 if (cx == 0)
7269 cx = infoPtr->iconSpacing.cx;
7271 /* if 0 then compute height */
7272 if (cy == 0)
7273 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7274 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7277 infoPtr->iconSpacing.cx = cx;
7278 infoPtr->iconSpacing.cy = cy;
7280 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7281 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7282 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7283 infoPtr->ntmHeight);
7285 /* these depend on the iconSpacing */
7286 LISTVIEW_UpdateItemSize(infoPtr);
7288 return oldspacing;
7291 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7293 INT cx, cy;
7295 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7297 size->cx = cx;
7298 size->cy = cy;
7300 else
7302 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7303 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7307 /***
7308 * DESCRIPTION:
7309 * Sets image lists.
7311 * PARAMETER(S):
7312 * [I] infoPtr : valid pointer to the listview structure
7313 * [I] nType : image list type
7314 * [I] himl : image list handle
7316 * RETURN:
7317 * SUCCESS : old image list
7318 * FAILURE : NULL
7320 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7322 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7323 INT oldHeight = infoPtr->nItemHeight;
7324 HIMAGELIST himlOld = 0;
7326 TRACE("(nType=%d, himl=%p\n", nType, himl);
7328 switch (nType)
7330 case LVSIL_NORMAL:
7331 himlOld = infoPtr->himlNormal;
7332 infoPtr->himlNormal = himl;
7333 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7334 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7335 break;
7337 case LVSIL_SMALL:
7338 himlOld = infoPtr->himlSmall;
7339 infoPtr->himlSmall = himl;
7340 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7341 break;
7343 case LVSIL_STATE:
7344 himlOld = infoPtr->himlState;
7345 infoPtr->himlState = himl;
7346 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7347 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7348 break;
7350 default:
7351 ERR("Unknown icon type=%d\n", nType);
7352 return NULL;
7355 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7356 if (infoPtr->nItemHeight != oldHeight)
7357 LISTVIEW_UpdateScroll(infoPtr);
7359 return himlOld;
7362 /***
7363 * DESCRIPTION:
7364 * Preallocates memory (does *not* set the actual count of items !)
7366 * PARAMETER(S):
7367 * [I] infoPtr : valid pointer to the listview structure
7368 * [I] nItems : item count (projected number of items to allocate)
7369 * [I] dwFlags : update flags
7371 * RETURN:
7372 * SUCCESS : TRUE
7373 * FAILURE : FALSE
7375 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7377 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7379 if (infoPtr->dwStyle & LVS_OWNERDATA)
7381 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7382 INT nOldCount = infoPtr->nItemCount;
7384 if (nItems < nOldCount)
7386 RANGE range = { nItems, nOldCount };
7387 ranges_del(infoPtr->selectionRanges, range);
7388 if (infoPtr->nFocusedItem >= nItems)
7390 infoPtr->nFocusedItem = -1;
7391 SetRectEmpty(&infoPtr->rcFocus);
7395 infoPtr->nItemCount = nItems;
7396 LISTVIEW_UpdateScroll(infoPtr);
7398 /* the flags are valid only in ownerdata report and list modes */
7399 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7401 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7402 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7404 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7405 LISTVIEW_InvalidateList(infoPtr);
7406 else
7408 INT nFrom, nTo;
7409 POINT Origin;
7410 RECT rcErase;
7412 LISTVIEW_GetOrigin(infoPtr, &Origin);
7413 nFrom = min(nOldCount, nItems);
7414 nTo = max(nOldCount, nItems);
7416 if (uView == LVS_REPORT)
7418 rcErase.left = 0;
7419 rcErase.top = nFrom * infoPtr->nItemHeight;
7420 rcErase.right = infoPtr->nItemWidth;
7421 rcErase.bottom = nTo * infoPtr->nItemHeight;
7422 OffsetRect(&rcErase, Origin.x, Origin.y);
7423 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7424 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7426 else /* LVS_LIST */
7428 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7430 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7431 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7432 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7433 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7434 OffsetRect(&rcErase, Origin.x, Origin.y);
7435 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7436 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7438 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7439 rcErase.top = 0;
7440 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7441 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7442 OffsetRect(&rcErase, Origin.x, Origin.y);
7443 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7444 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7448 else
7450 /* According to MSDN for non-LVS_OWNERDATA this is just
7451 * a performance issue. The control allocates its internal
7452 * data structures for the number of items specified. It
7453 * cuts down on the number of memory allocations. Therefore
7454 * we will just issue a WARN here
7456 WARN("for non-ownerdata performance option not implemented.\n");
7459 return TRUE;
7462 /***
7463 * DESCRIPTION:
7464 * Sets the position of an item.
7466 * PARAMETER(S):
7467 * [I] infoPtr : valid pointer to the listview structure
7468 * [I] nItem : item index
7469 * [I] pt : coordinate
7471 * RETURN:
7472 * SUCCESS : TRUE
7473 * FAILURE : FALSE
7475 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7477 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7478 POINT Origin;
7480 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7482 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7483 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7485 LISTVIEW_GetOrigin(infoPtr, &Origin);
7487 /* This point value seems to be an undocumented feature.
7488 * The best guess is that it means either at the origin,
7489 * or at true beginning of the list. I will assume the origin. */
7490 if ((pt.x == -1) && (pt.y == -1))
7491 pt = Origin;
7493 if (uView == LVS_ICON)
7495 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7496 pt.y -= ICON_TOP_PADDING;
7498 pt.x -= Origin.x;
7499 pt.y -= Origin.y;
7501 infoPtr->bAutoarrange = FALSE;
7503 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7506 /***
7507 * DESCRIPTION:
7508 * Sets the state of one or many items.
7510 * PARAMETER(S):
7511 * [I] infoPtr : valid pointer to the listview structure
7512 * [I] nItem : item index
7513 * [I] lpLVItem : item or subitem info
7515 * RETURN:
7516 * SUCCESS : TRUE
7517 * FAILURE : FALSE
7519 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7521 BOOL bResult = TRUE;
7522 LVITEMW lvItem;
7524 lvItem.iItem = nItem;
7525 lvItem.iSubItem = 0;
7526 lvItem.mask = LVIF_STATE;
7527 lvItem.state = lpLVItem->state;
7528 lvItem.stateMask = lpLVItem->stateMask;
7529 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7531 if (nItem == -1)
7533 /* apply to all items */
7534 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7535 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7537 else
7538 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7541 * Update selection mark
7543 * Investigation on windows 2k showed that selection mark was updated
7544 * whenever a new selection was made, but if the selected item was
7545 * unselected it was not updated.
7547 * we are probably still not 100% accurate, but this at least sets the
7548 * proper selection mark when it is needed
7551 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7552 (infoPtr->nSelectionMark == -1))
7554 int i;
7555 for (i = 0; i < infoPtr->nItemCount; i++)
7557 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7559 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7561 infoPtr->nSelectionMark = i;
7562 break;
7565 else if (ranges_contain(infoPtr->selectionRanges, i))
7567 infoPtr->nSelectionMark = i;
7568 break;
7573 return bResult;
7576 /***
7577 * DESCRIPTION:
7578 * Sets the text of an item or subitem.
7580 * PARAMETER(S):
7581 * [I] hwnd : window handle
7582 * [I] nItem : item index
7583 * [I] lpLVItem : item or subitem info
7584 * [I] isW : TRUE if input is Unicode
7586 * RETURN:
7587 * SUCCESS : TRUE
7588 * FAILURE : FALSE
7590 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7592 LVITEMW lvItem;
7594 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7596 lvItem.iItem = nItem;
7597 lvItem.iSubItem = lpLVItem->iSubItem;
7598 lvItem.mask = LVIF_TEXT;
7599 lvItem.pszText = lpLVItem->pszText;
7600 lvItem.cchTextMax = lpLVItem->cchTextMax;
7602 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7604 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7607 /***
7608 * DESCRIPTION:
7609 * Set item index that marks the start of a multiple selection.
7611 * PARAMETER(S):
7612 * [I] infoPtr : valid pointer to the listview structure
7613 * [I] nIndex : index
7615 * RETURN:
7616 * Index number or -1 if there is no selection mark.
7618 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7620 INT nOldIndex = infoPtr->nSelectionMark;
7622 TRACE("(nIndex=%d)\n", nIndex);
7624 infoPtr->nSelectionMark = nIndex;
7626 return nOldIndex;
7629 /***
7630 * DESCRIPTION:
7631 * Sets the text background color.
7633 * PARAMETER(S):
7634 * [I] infoPtr : valid pointer to the listview structure
7635 * [I] clrTextBk : text background color
7637 * RETURN:
7638 * SUCCESS : TRUE
7639 * FAILURE : FALSE
7641 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7643 TRACE("(clrTextBk=%x)\n", clrTextBk);
7645 if (infoPtr->clrTextBk != clrTextBk)
7647 infoPtr->clrTextBk = clrTextBk;
7648 LISTVIEW_InvalidateList(infoPtr);
7651 return TRUE;
7654 /***
7655 * DESCRIPTION:
7656 * Sets the text foreground color.
7658 * PARAMETER(S):
7659 * [I] infoPtr : valid pointer to the listview structure
7660 * [I] clrText : text color
7662 * RETURN:
7663 * SUCCESS : TRUE
7664 * FAILURE : FALSE
7666 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7668 TRACE("(clrText=%x)\n", clrText);
7670 if (infoPtr->clrText != clrText)
7672 infoPtr->clrText = clrText;
7673 LISTVIEW_InvalidateList(infoPtr);
7676 return TRUE;
7679 /***
7680 * DESCRIPTION:
7681 * Determines which listview item is located at the specified position.
7683 * PARAMETER(S):
7684 * [I] infoPtr : valid pointer to the listview structure
7685 * [I] hwndNewToolTip : handle to new ToolTip
7687 * RETURN:
7688 * old tool tip
7690 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7692 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7693 infoPtr->hwndToolTip = hwndNewToolTip;
7694 return hwndOldToolTip;
7698 * DESCRIPTION:
7699 * sets the Unicode character format flag for the control
7700 * PARAMETER(S):
7701 * [I] infoPtr :valid pointer to the listview structure
7702 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7704 * RETURN:
7705 * Old Unicode Format
7707 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7709 BOOL rc = infoPtr->notifyFormat;
7710 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7711 return rc;
7714 /* LISTVIEW_SetWorkAreas */
7716 /***
7717 * DESCRIPTION:
7718 * Callback internally used by LISTVIEW_SortItems()
7720 * PARAMETER(S):
7721 * [I] first : pointer to first ITEM_INFO to compare
7722 * [I] second : pointer to second ITEM_INFO to compare
7723 * [I] lParam : HWND of control
7725 * RETURN:
7726 * if first comes before second : negative
7727 * if first comes after second : positive
7728 * if first and second are equivalent : zero
7730 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7732 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7733 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7734 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7736 /* Forward the call to the client defined callback */
7737 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7740 /***
7741 * DESCRIPTION:
7742 * Sorts the listview items.
7744 * PARAMETER(S):
7745 * [I] infoPtr : valid pointer to the listview structure
7746 * [I] pfnCompare : application-defined value
7747 * [I] lParamSort : pointer to comparison callback
7749 * RETURN:
7750 * SUCCESS : TRUE
7751 * FAILURE : FALSE
7753 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7755 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7756 HDPA hdpaSubItems;
7757 ITEM_INFO *lpItem;
7758 LPVOID selectionMarkItem;
7759 LVITEMW item;
7760 int i;
7762 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7764 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7766 if (!pfnCompare) return FALSE;
7767 if (!infoPtr->hdpaItems) return FALSE;
7769 /* if there are 0 or 1 items, there is no need to sort */
7770 if (infoPtr->nItemCount < 2) return TRUE;
7772 if (infoPtr->nFocusedItem >= 0)
7774 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7775 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7776 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7778 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7779 /* clear the lpItem->state for non-selected ones */
7780 /* remove the selection ranges */
7782 infoPtr->pfnCompare = pfnCompare;
7783 infoPtr->lParamSort = lParamSort;
7784 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7786 /* Adjust selections and indices so that they are the way they should
7787 * be after the sort (otherwise, the list items move around, but
7788 * whatever is at the item's previous original position will be
7789 * selected instead)
7791 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7792 for (i=0; i < infoPtr->nItemCount; i++)
7794 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7795 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7797 if (lpItem->state & LVIS_SELECTED)
7799 item.state = LVIS_SELECTED;
7800 item.stateMask = LVIS_SELECTED;
7801 LISTVIEW_SetItemState(infoPtr, i, &item);
7803 if (lpItem->state & LVIS_FOCUSED)
7805 infoPtr->nFocusedItem = i;
7806 lpItem->state &= ~LVIS_FOCUSED;
7809 if (selectionMarkItem != NULL)
7810 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7811 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7813 /* refresh the display */
7814 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7815 LISTVIEW_InvalidateList(infoPtr);
7817 return TRUE;
7820 /***
7821 * DESCRIPTION:
7822 * Update theme handle after a theme change.
7824 * PARAMETER(S):
7825 * [I] infoPtr : valid pointer to the listview structure
7827 * RETURN:
7828 * SUCCESS : 0
7829 * FAILURE : something else
7831 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7833 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7834 CloseThemeData(theme);
7835 OpenThemeData(infoPtr->hwndSelf, themeClass);
7836 return 0;
7839 /***
7840 * DESCRIPTION:
7841 * Updates an items or rearranges the listview control.
7843 * PARAMETER(S):
7844 * [I] infoPtr : valid pointer to the listview structure
7845 * [I] nItem : item index
7847 * RETURN:
7848 * SUCCESS : TRUE
7849 * FAILURE : FALSE
7851 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7853 TRACE("(nItem=%d)\n", nItem);
7855 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7857 /* rearrange with default alignment style */
7858 if (is_autoarrange(infoPtr))
7859 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7860 else
7861 LISTVIEW_InvalidateItem(infoPtr, nItem);
7863 return TRUE;
7866 /***
7867 * DESCRIPTION:
7868 * Draw the track line at the place defined in the infoPtr structure.
7869 * The line is drawn with a XOR pen so drawing the line for the second time
7870 * in the same place erases the line.
7872 * PARAMETER(S):
7873 * [I] infoPtr : valid pointer to the listview structure
7875 * RETURN:
7876 * SUCCESS : TRUE
7877 * FAILURE : FALSE
7879 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7881 HPEN hOldPen;
7882 HDC hdc;
7883 INT oldROP;
7885 if (infoPtr->xTrackLine == -1)
7886 return FALSE;
7888 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7889 return FALSE;
7890 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7891 oldROP = SetROP2(hdc, R2_XORPEN);
7892 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7893 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7894 SetROP2(hdc, oldROP);
7895 SelectObject(hdc, hOldPen);
7896 ReleaseDC(infoPtr->hwndSelf, hdc);
7897 return TRUE;
7900 /***
7901 * DESCRIPTION:
7902 * Called when an edit control should be displayed. This function is called after
7903 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7905 * PARAMETER(S):
7906 * [I] hwnd : Handle to the listview
7907 * [I] uMsg : WM_TIMER (ignored)
7908 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7909 * [I] dwTimer : The elapsed time (ignored)
7911 * RETURN:
7912 * None.
7914 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7916 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7917 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7919 KillTimer(hwnd, idEvent);
7920 editItem->fEnabled = FALSE;
7921 /* check if the item is still selected */
7922 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7923 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7926 /***
7927 * DESCRIPTION:
7928 * Creates the listview control - the WM_NCCREATE phase.
7930 * PARAMETER(S):
7931 * [I] hwnd : window handle
7932 * [I] lpcs : the create parameters
7934 * RETURN:
7935 * Success: TRUE
7936 * Failure: FALSE
7938 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7940 LISTVIEW_INFO *infoPtr;
7941 LOGFONTW logFont;
7943 TRACE("(lpcs=%p)\n", lpcs);
7945 /* initialize info pointer */
7946 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7947 if (!infoPtr) return FALSE;
7949 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7951 infoPtr->hwndSelf = hwnd;
7952 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7953 /* determine the type of structures to use */
7954 infoPtr->hwndNotify = lpcs->hwndParent;
7955 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7957 /* initialize color information */
7958 infoPtr->clrBk = CLR_NONE;
7959 infoPtr->clrText = CLR_DEFAULT;
7960 infoPtr->clrTextBk = CLR_DEFAULT;
7961 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7963 /* set default values */
7964 infoPtr->nFocusedItem = -1;
7965 infoPtr->nSelectionMark = -1;
7966 infoPtr->nHotItem = -1;
7967 infoPtr->bRedraw = TRUE;
7968 infoPtr->bNoItemMetrics = TRUE;
7969 infoPtr->bDoChangeNotify = TRUE;
7970 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7971 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7972 infoPtr->nEditLabelItem = -1;
7973 infoPtr->dwHoverTime = -1; /* default system hover time */
7974 infoPtr->nMeasureItemHeight = 0;
7975 infoPtr->xTrackLine = -1; /* no track line */
7976 infoPtr->itemEdit.fEnabled = FALSE;
7978 /* get default font (icon title) */
7979 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7980 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7981 infoPtr->hFont = infoPtr->hDefaultFont;
7982 LISTVIEW_SaveTextMetrics(infoPtr);
7984 /* allocate memory for the data structure */
7985 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7986 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7987 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7988 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7989 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7990 return TRUE;
7992 fail:
7993 DestroyWindow(infoPtr->hwndHeader);
7994 ranges_destroy(infoPtr->selectionRanges);
7995 DPA_Destroy(infoPtr->hdpaItems);
7996 DPA_Destroy(infoPtr->hdpaPosX);
7997 DPA_Destroy(infoPtr->hdpaPosY);
7998 DPA_Destroy(infoPtr->hdpaColumns);
7999 Free(infoPtr);
8000 return FALSE;
8003 /***
8004 * DESCRIPTION:
8005 * Creates the listview control - the WM_CREATE phase. Most of the data is
8006 * already set up in LISTVIEW_NCCreate
8008 * PARAMETER(S):
8009 * [I] hwnd : window handle
8010 * [I] lpcs : the create parameters
8012 * RETURN:
8013 * Success: 0
8014 * Failure: -1
8016 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8018 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8019 UINT uView = lpcs->style & LVS_TYPEMASK;
8020 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
8022 TRACE("(lpcs=%p)\n", lpcs);
8024 infoPtr->dwStyle = lpcs->style;
8025 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8026 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8028 /* setup creation flags */
8029 dFlags |= (LVS_NOSORTHEADER & lpcs->style) ? 0 : HDS_BUTTONS;
8030 dFlags |= (LVS_NOCOLUMNHEADER & lpcs->style) ? HDS_HIDDEN : 0;
8032 /* create header */
8033 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
8034 0, 0, 0, 0, hwnd, NULL,
8035 lpcs->hInstance, NULL);
8036 if (!infoPtr->hwndHeader) return -1;
8038 /* set header unicode format */
8039 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
8041 /* set header font */
8042 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8044 /* init item size to avoid division by 0 */
8045 LISTVIEW_UpdateItemSize (infoPtr);
8047 if (uView == LVS_REPORT)
8049 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8051 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8053 LISTVIEW_UpdateSize(infoPtr);
8054 LISTVIEW_UpdateScroll(infoPtr);
8057 OpenThemeData(hwnd, themeClass);
8059 /* initialize the icon sizes */
8060 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8061 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8062 return 0;
8065 /***
8066 * DESCRIPTION:
8067 * Destroys the listview control.
8069 * PARAMETER(S):
8070 * [I] infoPtr : valid pointer to the listview structure
8072 * RETURN:
8073 * Success: 0
8074 * Failure: -1
8076 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8078 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8079 CloseThemeData(theme);
8080 return 0;
8083 /***
8084 * DESCRIPTION:
8085 * Enables the listview control.
8087 * PARAMETER(S):
8088 * [I] infoPtr : valid pointer to the listview structure
8089 * [I] bEnable : specifies whether to enable or disable the window
8091 * RETURN:
8092 * SUCCESS : TRUE
8093 * FAILURE : FALSE
8095 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8097 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8098 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8099 return TRUE;
8102 /***
8103 * DESCRIPTION:
8104 * Erases the background of the listview control.
8106 * PARAMETER(S):
8107 * [I] infoPtr : valid pointer to the listview structure
8108 * [I] hdc : device context handle
8110 * RETURN:
8111 * SUCCESS : TRUE
8112 * FAILURE : FALSE
8114 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8116 RECT rc;
8118 TRACE("(hdc=%p)\n", hdc);
8120 if (!GetClipBox(hdc, &rc)) return FALSE;
8122 /* for double buffered controls we need to do this during refresh */
8123 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8125 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8129 /***
8130 * DESCRIPTION:
8131 * Helper function for LISTVIEW_[HV]Scroll *only*.
8132 * Performs vertical/horizontal scrolling by a give amount.
8134 * PARAMETER(S):
8135 * [I] infoPtr : valid pointer to the listview structure
8136 * [I] dx : amount of horizontal scroll
8137 * [I] dy : amount of vertical scroll
8139 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8141 /* now we can scroll the list */
8142 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8143 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8144 /* if we have focus, adjust rect */
8145 OffsetRect(&infoPtr->rcFocus, dx, dy);
8146 UpdateWindow(infoPtr->hwndSelf);
8149 /***
8150 * DESCRIPTION:
8151 * Performs vertical scrolling.
8153 * PARAMETER(S):
8154 * [I] infoPtr : valid pointer to the listview structure
8155 * [I] nScrollCode : scroll code
8156 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8157 * [I] hScrollWnd : scrollbar control window handle
8159 * RETURN:
8160 * Zero
8162 * NOTES:
8163 * SB_LINEUP/SB_LINEDOWN:
8164 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8165 * for LVS_REPORT is 1 line
8166 * for LVS_LIST cannot occur
8169 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8170 INT nScrollDiff, HWND hScrollWnd)
8172 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8173 INT nOldScrollPos, nNewScrollPos;
8174 SCROLLINFO scrollInfo;
8175 BOOL is_an_icon;
8177 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8178 debugscrollcode(nScrollCode), nScrollDiff);
8180 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8182 scrollInfo.cbSize = sizeof(SCROLLINFO);
8183 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8185 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8187 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8189 nOldScrollPos = scrollInfo.nPos;
8190 switch (nScrollCode)
8192 case SB_INTERNAL:
8193 break;
8195 case SB_LINEUP:
8196 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8197 break;
8199 case SB_LINEDOWN:
8200 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8201 break;
8203 case SB_PAGEUP:
8204 nScrollDiff = -scrollInfo.nPage;
8205 break;
8207 case SB_PAGEDOWN:
8208 nScrollDiff = scrollInfo.nPage;
8209 break;
8211 case SB_THUMBPOSITION:
8212 case SB_THUMBTRACK:
8213 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8214 break;
8216 default:
8217 nScrollDiff = 0;
8220 /* quit right away if pos isn't changing */
8221 if (nScrollDiff == 0) return 0;
8223 /* calculate new position, and handle overflows */
8224 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8225 if (nScrollDiff > 0) {
8226 if (nNewScrollPos < nOldScrollPos ||
8227 nNewScrollPos > scrollInfo.nMax)
8228 nNewScrollPos = scrollInfo.nMax;
8229 } else {
8230 if (nNewScrollPos > nOldScrollPos ||
8231 nNewScrollPos < scrollInfo.nMin)
8232 nNewScrollPos = scrollInfo.nMin;
8235 /* set the new position, and reread in case it changed */
8236 scrollInfo.fMask = SIF_POS;
8237 scrollInfo.nPos = nNewScrollPos;
8238 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8240 /* carry on only if it really changed */
8241 if (nNewScrollPos == nOldScrollPos) return 0;
8243 /* now adjust to client coordinates */
8244 nScrollDiff = nOldScrollPos - nNewScrollPos;
8245 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8247 /* and scroll the window */
8248 scroll_list(infoPtr, 0, nScrollDiff);
8250 return 0;
8253 /***
8254 * DESCRIPTION:
8255 * Performs horizontal scrolling.
8257 * PARAMETER(S):
8258 * [I] infoPtr : valid pointer to the listview structure
8259 * [I] nScrollCode : scroll code
8260 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8261 * [I] hScrollWnd : scrollbar control window handle
8263 * RETURN:
8264 * Zero
8266 * NOTES:
8267 * SB_LINELEFT/SB_LINERIGHT:
8268 * for LVS_ICON, LVS_SMALLICON 1 pixel
8269 * for LVS_REPORT is 1 pixel
8270 * for LVS_LIST is 1 column --> which is a 1 because the
8271 * scroll is based on columns not pixels
8274 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8275 INT nScrollDiff, HWND hScrollWnd)
8277 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8278 INT nOldScrollPos, nNewScrollPos;
8279 SCROLLINFO scrollInfo;
8281 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8282 debugscrollcode(nScrollCode), nScrollDiff);
8284 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8286 scrollInfo.cbSize = sizeof(SCROLLINFO);
8287 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8289 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8291 nOldScrollPos = scrollInfo.nPos;
8293 switch (nScrollCode)
8295 case SB_INTERNAL:
8296 break;
8298 case SB_LINELEFT:
8299 nScrollDiff = -1;
8300 break;
8302 case SB_LINERIGHT:
8303 nScrollDiff = 1;
8304 break;
8306 case SB_PAGELEFT:
8307 nScrollDiff = -scrollInfo.nPage;
8308 break;
8310 case SB_PAGERIGHT:
8311 nScrollDiff = scrollInfo.nPage;
8312 break;
8314 case SB_THUMBPOSITION:
8315 case SB_THUMBTRACK:
8316 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8317 break;
8319 default:
8320 nScrollDiff = 0;
8323 /* quit right away if pos isn't changing */
8324 if (nScrollDiff == 0) return 0;
8326 /* calculate new position, and handle overflows */
8327 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8328 if (nScrollDiff > 0) {
8329 if (nNewScrollPos < nOldScrollPos ||
8330 nNewScrollPos > scrollInfo.nMax)
8331 nNewScrollPos = scrollInfo.nMax;
8332 } else {
8333 if (nNewScrollPos > nOldScrollPos ||
8334 nNewScrollPos < scrollInfo.nMin)
8335 nNewScrollPos = scrollInfo.nMin;
8338 /* set the new position, and reread in case it changed */
8339 scrollInfo.fMask = SIF_POS;
8340 scrollInfo.nPos = nNewScrollPos;
8341 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8343 /* carry on only if it really changed */
8344 if (nNewScrollPos == nOldScrollPos) return 0;
8346 if(uView == LVS_REPORT)
8347 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8349 /* now adjust to client coordinates */
8350 nScrollDiff = nOldScrollPos - nNewScrollPos;
8351 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8353 /* and scroll the window */
8354 scroll_list(infoPtr, nScrollDiff, 0);
8356 return 0;
8359 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8361 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8362 INT gcWheelDelta = 0;
8363 INT pulScrollLines = 3;
8364 SCROLLINFO scrollInfo;
8366 TRACE("(wheelDelta=%d)\n", wheelDelta);
8368 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8369 gcWheelDelta -= wheelDelta;
8371 scrollInfo.cbSize = sizeof(SCROLLINFO);
8372 scrollInfo.fMask = SIF_POS;
8374 switch(uView)
8376 case LVS_ICON:
8377 case LVS_SMALLICON:
8379 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8380 * should be fixed in the future.
8382 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8383 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8384 break;
8386 case LVS_REPORT:
8387 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8389 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8390 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8391 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8393 break;
8395 case LVS_LIST:
8396 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8397 break;
8399 return 0;
8402 /***
8403 * DESCRIPTION:
8404 * ???
8406 * PARAMETER(S):
8407 * [I] infoPtr : valid pointer to the listview structure
8408 * [I] nVirtualKey : virtual key
8409 * [I] lKeyData : key data
8411 * RETURN:
8412 * Zero
8414 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8416 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8417 HWND hwndSelf = infoPtr->hwndSelf;
8418 INT nItem = -1;
8419 NMLVKEYDOWN nmKeyDown;
8421 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8423 /* send LVN_KEYDOWN notification */
8424 nmKeyDown.wVKey = nVirtualKey;
8425 nmKeyDown.flags = 0;
8426 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8427 if (!IsWindow(hwndSelf))
8428 return 0;
8430 switch (nVirtualKey)
8432 case VK_SPACE:
8433 nItem = infoPtr->nFocusedItem;
8434 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8435 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8436 break;
8438 case VK_RETURN:
8439 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8441 if (!notify(infoPtr, NM_RETURN)) return 0;
8442 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8444 break;
8446 case VK_HOME:
8447 if (infoPtr->nItemCount > 0)
8448 nItem = 0;
8449 break;
8451 case VK_END:
8452 if (infoPtr->nItemCount > 0)
8453 nItem = infoPtr->nItemCount - 1;
8454 break;
8456 case VK_LEFT:
8457 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8458 break;
8460 case VK_UP:
8461 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8462 break;
8464 case VK_RIGHT:
8465 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8466 break;
8468 case VK_DOWN:
8469 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8470 break;
8472 case VK_PRIOR:
8473 if (uView == LVS_REPORT)
8475 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8476 if (infoPtr->nFocusedItem == topidx)
8477 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8478 else
8479 nItem = topidx;
8481 else
8482 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8483 * LISTVIEW_GetCountPerRow(infoPtr);
8484 if(nItem < 0) nItem = 0;
8485 break;
8487 case VK_NEXT:
8488 if (uView == LVS_REPORT)
8490 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8491 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8492 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8493 nItem = infoPtr->nFocusedItem + cnt - 1;
8494 else
8495 nItem = topidx + cnt - 1;
8497 else
8498 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8499 * LISTVIEW_GetCountPerRow(infoPtr);
8500 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8501 break;
8504 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8505 LISTVIEW_KeySelection(infoPtr, nItem);
8507 return 0;
8510 /***
8511 * DESCRIPTION:
8512 * Kills the focus.
8514 * PARAMETER(S):
8515 * [I] infoPtr : valid pointer to the listview structure
8517 * RETURN:
8518 * Zero
8520 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8522 TRACE("()\n");
8524 /* if we did not have the focus, there's nothing to do */
8525 if (!infoPtr->bFocus) return 0;
8527 /* send NM_KILLFOCUS notification */
8528 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8530 /* if we have a focus rectagle, get rid of it */
8531 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8533 /* set window focus flag */
8534 infoPtr->bFocus = FALSE;
8536 /* invalidate the selected items before resetting focus flag */
8537 LISTVIEW_InvalidateSelectedItems(infoPtr);
8539 return 0;
8542 /***
8543 * DESCRIPTION:
8544 * Processes double click messages (left mouse button).
8546 * PARAMETER(S):
8547 * [I] infoPtr : valid pointer to the listview structure
8548 * [I] wKey : key flag
8549 * [I] x,y : mouse coordinate
8551 * RETURN:
8552 * Zero
8554 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8556 LVHITTESTINFO htInfo;
8558 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8560 /* Cancel the item edition if any */
8561 if (infoPtr->itemEdit.fEnabled)
8563 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8564 infoPtr->itemEdit.fEnabled = FALSE;
8567 /* send NM_RELEASEDCAPTURE notification */
8568 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8570 htInfo.pt.x = x;
8571 htInfo.pt.y = y;
8573 /* send NM_DBLCLK notification */
8574 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8575 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8577 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8578 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8580 return 0;
8583 /***
8584 * DESCRIPTION:
8585 * Processes mouse down messages (left mouse button).
8587 * PARAMETERS:
8588 * infoPtr [I ] valid pointer to the listview structure
8589 * wKey [I ] key flag
8590 * x,y [I ] mouse coordinate
8592 * RETURN:
8593 * Zero
8595 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8597 LVHITTESTINFO lvHitTestInfo;
8598 static BOOL bGroupSelect = TRUE;
8599 BOOL bReceivedFocus = FALSE;
8600 POINT pt = { x, y };
8601 INT nItem;
8603 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8605 /* send NM_RELEASEDCAPTURE notification */
8606 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8608 if (!infoPtr->bFocus)
8609 bReceivedFocus = TRUE;
8611 /* set left button down flag and record the click position */
8612 infoPtr->bLButtonDown = TRUE;
8613 infoPtr->ptClickPos = pt;
8614 infoPtr->bDragging = FALSE;
8616 lvHitTestInfo.pt.x = x;
8617 lvHitTestInfo.pt.y = y;
8619 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8620 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8621 infoPtr->nEditLabelItem = -1;
8622 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8624 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8626 toggle_checkbox_state(infoPtr, nItem);
8627 return 0;
8630 if (infoPtr->dwStyle & LVS_SINGLESEL)
8632 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8633 infoPtr->nEditLabelItem = nItem;
8634 else
8635 LISTVIEW_SetSelection(infoPtr, nItem);
8637 else
8639 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8641 if (bGroupSelect)
8643 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8644 LISTVIEW_SetItemFocus(infoPtr, nItem);
8645 infoPtr->nSelectionMark = nItem;
8647 else
8649 LVITEMW item;
8651 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8652 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8654 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8655 infoPtr->nSelectionMark = nItem;
8658 else if (wKey & MK_CONTROL)
8660 LVITEMW item;
8662 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8664 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8665 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8666 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8667 infoPtr->nSelectionMark = nItem;
8669 else if (wKey & MK_SHIFT)
8671 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8673 else
8675 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8676 infoPtr->nEditLabelItem = nItem;
8678 /* set selection (clears other pre-existing selections) */
8679 LISTVIEW_SetSelection(infoPtr, nItem);
8683 else
8685 /* remove all selections */
8686 LISTVIEW_DeselectAll(infoPtr);
8687 ReleaseCapture();
8690 if (bReceivedFocus)
8691 infoPtr->nEditLabelItem = -1;
8693 return 0;
8696 /***
8697 * DESCRIPTION:
8698 * Processes mouse up messages (left mouse button).
8700 * PARAMETERS:
8701 * infoPtr [I ] valid pointer to the listview structure
8702 * wKey [I ] key flag
8703 * x,y [I ] mouse coordinate
8705 * RETURN:
8706 * Zero
8708 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8710 LVHITTESTINFO lvHitTestInfo;
8712 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8714 if (!infoPtr->bLButtonDown) return 0;
8716 lvHitTestInfo.pt.x = x;
8717 lvHitTestInfo.pt.y = y;
8719 /* send NM_CLICK notification */
8720 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8721 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8723 /* set left button flag */
8724 infoPtr->bLButtonDown = FALSE;
8726 if (infoPtr->bDragging)
8728 infoPtr->bDragging = FALSE;
8729 return 0;
8732 /* if we clicked on a selected item, edit the label */
8733 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8735 /* we want to make sure the user doesn't want to do a double click. So we will
8736 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8738 infoPtr->itemEdit.fEnabled = TRUE;
8739 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8740 SetTimer(infoPtr->hwndSelf,
8741 (UINT_PTR)&infoPtr->itemEdit,
8742 GetDoubleClickTime(),
8743 LISTVIEW_DelayedEditItem);
8746 if (!infoPtr->bFocus)
8747 SetFocus(infoPtr->hwndSelf);
8749 return 0;
8752 /***
8753 * DESCRIPTION:
8754 * Destroys the listview control (called after WM_DESTROY).
8756 * PARAMETER(S):
8757 * [I] infoPtr : valid pointer to the listview structure
8759 * RETURN:
8760 * Zero
8762 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8764 TRACE("()\n");
8766 /* delete all items */
8767 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8769 /* destroy data structure */
8770 DPA_Destroy(infoPtr->hdpaItems);
8771 DPA_Destroy(infoPtr->hdpaPosX);
8772 DPA_Destroy(infoPtr->hdpaPosY);
8773 DPA_Destroy(infoPtr->hdpaColumns);
8774 ranges_destroy(infoPtr->selectionRanges);
8776 /* destroy image lists */
8777 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8779 if (infoPtr->himlNormal)
8780 ImageList_Destroy(infoPtr->himlNormal);
8781 if (infoPtr->himlSmall)
8782 ImageList_Destroy(infoPtr->himlSmall);
8783 if (infoPtr->himlState)
8784 ImageList_Destroy(infoPtr->himlState);
8787 /* destroy font, bkgnd brush */
8788 infoPtr->hFont = 0;
8789 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8790 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8792 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8794 /* free listview info pointer*/
8795 Free(infoPtr);
8797 return 0;
8800 /***
8801 * DESCRIPTION:
8802 * Handles notifications from header.
8804 * PARAMETER(S):
8805 * [I] infoPtr : valid pointer to the listview structure
8806 * [I] nCtrlId : control identifier
8807 * [I] lpnmh : notification information
8809 * RETURN:
8810 * Zero
8812 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8814 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8815 HWND hwndSelf = infoPtr->hwndSelf;
8817 TRACE("(lpnmh=%p)\n", lpnmh);
8819 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8821 switch (lpnmh->hdr.code)
8823 case HDN_TRACKW:
8824 case HDN_TRACKA:
8826 COLUMN_INFO *lpColumnInfo;
8827 POINT ptOrigin;
8828 INT x;
8830 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8831 break;
8833 /* remove the old line (if any) */
8834 LISTVIEW_DrawTrackLine(infoPtr);
8836 /* compute & draw the new line */
8837 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8838 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8839 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8840 infoPtr->xTrackLine = x + ptOrigin.x;
8841 LISTVIEW_DrawTrackLine(infoPtr);
8842 break;
8845 case HDN_ENDTRACKA:
8846 case HDN_ENDTRACKW:
8847 /* remove the track line (if any) */
8848 LISTVIEW_DrawTrackLine(infoPtr);
8849 infoPtr->xTrackLine = -1;
8850 break;
8852 case HDN_ENDDRAG:
8853 FIXME("Changing column order not implemented\n");
8854 return TRUE;
8856 case HDN_ITEMCHANGINGW:
8857 case HDN_ITEMCHANGINGA:
8858 return notify_forward_header(infoPtr, lpnmh);
8860 case HDN_ITEMCHANGEDW:
8861 case HDN_ITEMCHANGEDA:
8863 COLUMN_INFO *lpColumnInfo;
8864 INT dx, cxy;
8866 notify_forward_header(infoPtr, lpnmh);
8867 if (!IsWindow(hwndSelf))
8868 break;
8870 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8872 HDITEMW hdi;
8874 hdi.mask = HDI_WIDTH;
8875 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8876 cxy = hdi.cxy;
8878 else
8879 cxy = lpnmh->pitem->cxy;
8881 /* determine how much we change since the last know position */
8882 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8883 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8884 if (dx != 0)
8886 lpColumnInfo->rcHeader.right += dx;
8887 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8888 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8889 else
8891 /* only needs to update the scrolls */
8892 infoPtr->nItemWidth += dx;
8893 LISTVIEW_UpdateScroll(infoPtr);
8895 LISTVIEW_UpdateItemSize(infoPtr);
8896 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8898 POINT ptOrigin;
8899 RECT rcCol = lpColumnInfo->rcHeader;
8901 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8902 OffsetRect(&rcCol, ptOrigin.x, 0);
8904 rcCol.top = infoPtr->rcList.top;
8905 rcCol.bottom = infoPtr->rcList.bottom;
8907 /* resizing left-aligned columns leaves most of the left side untouched */
8908 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8910 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8911 if (dx > 0)
8912 nMaxDirty += dx;
8913 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8916 /* when shrinking the last column clear the now unused field */
8917 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8918 rcCol.right -= dx;
8920 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8924 break;
8926 case HDN_ITEMCLICKW:
8927 case HDN_ITEMCLICKA:
8929 /* Handle sorting by Header Column */
8930 NMLISTVIEW nmlv;
8932 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8933 nmlv.iItem = -1;
8934 nmlv.iSubItem = lpnmh->iItem;
8935 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8937 break;
8939 case HDN_DIVIDERDBLCLICKW:
8940 case HDN_DIVIDERDBLCLICKA:
8941 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8942 break;
8945 return 0;
8948 /***
8949 * DESCRIPTION:
8950 * Paint non-client area of control.
8952 * PARAMETER(S):
8953 * [I] infoPtr : valid pointer to the listview structureof the sender
8954 * [I] region : update region
8956 * RETURN:
8957 * TRUE - frame was painted
8958 * FALSE - call default window proc
8960 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8962 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8963 HDC dc;
8964 RECT r;
8965 HRGN cliprgn;
8966 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8967 cyEdge = GetSystemMetrics (SM_CYEDGE);
8969 if (!theme) return FALSE;
8971 GetWindowRect(infoPtr->hwndSelf, &r);
8973 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8974 r.right - cxEdge, r.bottom - cyEdge);
8975 if (region != (HRGN)1)
8976 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8977 OffsetRect(&r, -r.left, -r.top);
8979 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8980 OffsetRect(&r, -r.left, -r.top);
8982 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8983 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8984 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8985 ReleaseDC(infoPtr->hwndSelf, dc);
8987 /* Call default proc to get the scrollbars etc. painted */
8988 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8990 return TRUE;
8993 /***
8994 * DESCRIPTION:
8995 * Determines the type of structure to use.
8997 * PARAMETER(S):
8998 * [I] infoPtr : valid pointer to the listview structureof the sender
8999 * [I] hwndFrom : listview window handle
9000 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9002 * RETURN:
9003 * Zero
9005 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9007 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9009 if (nCommand == NF_REQUERY)
9010 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9012 return infoPtr->notifyFormat;
9015 /***
9016 * DESCRIPTION:
9017 * Paints/Repaints the listview control.
9019 * PARAMETER(S):
9020 * [I] infoPtr : valid pointer to the listview structure
9021 * [I] hdc : device context handle
9023 * RETURN:
9024 * Zero
9026 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9028 TRACE("(hdc=%p)\n", hdc);
9030 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9032 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9034 infoPtr->bNoItemMetrics = FALSE;
9035 LISTVIEW_UpdateItemSize(infoPtr);
9036 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9037 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9038 LISTVIEW_UpdateScroll(infoPtr);
9041 UpdateWindow(infoPtr->hwndHeader);
9043 if (hdc)
9044 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9045 else
9047 PAINTSTRUCT ps;
9049 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9050 if (!hdc) return 1;
9051 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9052 EndPaint(infoPtr->hwndSelf, &ps);
9055 return 0;
9059 /***
9060 * DESCRIPTION:
9061 * Paints/Repaints the listview control.
9063 * PARAMETER(S):
9064 * [I] infoPtr : valid pointer to the listview structure
9065 * [I] hdc : device context handle
9066 * [I] options : drawing options
9068 * RETURN:
9069 * Zero
9071 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9073 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9075 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9076 return 0;
9078 if (options & PRF_ERASEBKGND)
9079 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9081 if (options & PRF_CLIENT)
9082 LISTVIEW_Paint(infoPtr, hdc);
9084 return 0;
9088 /***
9089 * DESCRIPTION:
9090 * Processes double click messages (right mouse button).
9092 * PARAMETER(S):
9093 * [I] infoPtr : valid pointer to the listview structure
9094 * [I] wKey : key flag
9095 * [I] x,y : mouse coordinate
9097 * RETURN:
9098 * Zero
9100 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9102 LVHITTESTINFO lvHitTestInfo;
9104 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9106 /* send NM_RELEASEDCAPTURE notification */
9107 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9109 /* send NM_RDBLCLK notification */
9110 lvHitTestInfo.pt.x = x;
9111 lvHitTestInfo.pt.y = y;
9112 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9113 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9115 return 0;
9118 /***
9119 * DESCRIPTION:
9120 * Processes mouse down messages (right mouse button).
9122 * PARAMETER(S):
9123 * [I] infoPtr : valid pointer to the listview structure
9124 * [I] wKey : key flag
9125 * [I] x,y : mouse coordinate
9127 * RETURN:
9128 * Zero
9130 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9132 LVHITTESTINFO lvHitTestInfo;
9133 INT nItem;
9135 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9137 /* send NM_RELEASEDCAPTURE notification */
9138 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9140 /* make sure the listview control window has the focus */
9141 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9143 /* set right button down flag */
9144 infoPtr->bRButtonDown = TRUE;
9146 /* determine the index of the selected item */
9147 lvHitTestInfo.pt.x = x;
9148 lvHitTestInfo.pt.y = y;
9149 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9151 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9153 LISTVIEW_SetItemFocus(infoPtr, nItem);
9154 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9155 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9156 LISTVIEW_SetSelection(infoPtr, nItem);
9158 else
9160 LISTVIEW_DeselectAll(infoPtr);
9163 return 0;
9166 /***
9167 * DESCRIPTION:
9168 * Processes mouse up messages (right mouse button).
9170 * PARAMETER(S):
9171 * [I] infoPtr : valid pointer to the listview structure
9172 * [I] wKey : key flag
9173 * [I] x,y : mouse coordinate
9175 * RETURN:
9176 * Zero
9178 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9180 LVHITTESTINFO lvHitTestInfo;
9181 POINT pt;
9183 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9185 if (!infoPtr->bRButtonDown) return 0;
9187 /* set button flag */
9188 infoPtr->bRButtonDown = FALSE;
9190 /* Send NM_RClICK notification */
9191 lvHitTestInfo.pt.x = x;
9192 lvHitTestInfo.pt.y = y;
9193 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9194 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9196 /* Change to screen coordinate for WM_CONTEXTMENU */
9197 pt = lvHitTestInfo.pt;
9198 ClientToScreen(infoPtr->hwndSelf, &pt);
9200 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9201 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9202 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9204 return 0;
9208 /***
9209 * DESCRIPTION:
9210 * Sets the cursor.
9212 * PARAMETER(S):
9213 * [I] infoPtr : valid pointer to the listview structure
9214 * [I] hwnd : window handle of window containing the cursor
9215 * [I] nHittest : hit-test code
9216 * [I] wMouseMsg : ideintifier of the mouse message
9218 * RETURN:
9219 * TRUE if cursor is set
9220 * FALSE otherwise
9222 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9224 LVHITTESTINFO lvHitTestInfo;
9226 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9228 if(!infoPtr->hHotCursor) return FALSE;
9230 GetCursorPos(&lvHitTestInfo.pt);
9231 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9233 SetCursor(infoPtr->hHotCursor);
9235 return TRUE;
9238 /***
9239 * DESCRIPTION:
9240 * Sets the focus.
9242 * PARAMETER(S):
9243 * [I] infoPtr : valid pointer to the listview structure
9244 * [I] hwndLoseFocus : handle of previously focused window
9246 * RETURN:
9247 * Zero
9249 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9251 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9253 /* if we have the focus already, there's nothing to do */
9254 if (infoPtr->bFocus) return 0;
9256 /* send NM_SETFOCUS notification */
9257 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9259 /* set window focus flag */
9260 infoPtr->bFocus = TRUE;
9262 /* put the focus rect back on */
9263 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9265 /* redraw all visible selected items */
9266 LISTVIEW_InvalidateSelectedItems(infoPtr);
9268 return 0;
9271 /***
9272 * DESCRIPTION:
9273 * Sets the font.
9275 * PARAMETER(S):
9276 * [I] infoPtr : valid pointer to the listview structure
9277 * [I] fRedraw : font handle
9278 * [I] fRedraw : redraw flag
9280 * RETURN:
9281 * Zero
9283 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9285 HFONT oldFont = infoPtr->hFont;
9287 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9289 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9290 if (infoPtr->hFont == oldFont) return 0;
9292 LISTVIEW_SaveTextMetrics(infoPtr);
9294 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9296 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9297 LISTVIEW_UpdateSize(infoPtr);
9298 LISTVIEW_UpdateScroll(infoPtr);
9301 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9303 return 0;
9306 /***
9307 * DESCRIPTION:
9308 * Message handling for WM_SETREDRAW.
9309 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9311 * PARAMETER(S):
9312 * [I] infoPtr : valid pointer to the listview structure
9313 * [I] bRedraw: state of redraw flag
9315 * RETURN:
9316 * DefWinProc return value
9318 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9320 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9322 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9323 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9325 infoPtr->bRedraw = bRedraw;
9327 if(!bRedraw) return 0;
9329 if (is_autoarrange(infoPtr))
9330 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9331 LISTVIEW_UpdateScroll(infoPtr);
9333 /* despite what the WM_SETREDRAW docs says, apps expect us
9334 * to invalidate the listview here... stupid! */
9335 LISTVIEW_InvalidateList(infoPtr);
9337 return 0;
9340 /***
9341 * DESCRIPTION:
9342 * Resizes the listview control. This function processes WM_SIZE
9343 * messages. At this time, the width and height are not used.
9345 * PARAMETER(S):
9346 * [I] infoPtr : valid pointer to the listview structure
9347 * [I] Width : new width
9348 * [I] Height : new height
9350 * RETURN:
9351 * Zero
9353 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9355 RECT rcOld = infoPtr->rcList;
9357 TRACE("(width=%d, height=%d)\n", Width, Height);
9359 LISTVIEW_UpdateSize(infoPtr);
9360 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9362 /* do not bother with display related stuff if we're not redrawing */
9363 if (!is_redrawing(infoPtr)) return 0;
9365 if (is_autoarrange(infoPtr))
9366 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9368 LISTVIEW_UpdateScroll(infoPtr);
9370 /* refresh all only for lists whose height changed significantly */
9371 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9372 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9373 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9374 LISTVIEW_InvalidateList(infoPtr);
9376 return 0;
9379 /***
9380 * DESCRIPTION:
9381 * Sets the size information.
9383 * PARAMETER(S):
9384 * [I] infoPtr : valid pointer to the listview structure
9386 * RETURN:
9387 * None
9389 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9391 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9393 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9395 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9397 if (uView == LVS_LIST)
9399 /* Apparently the "LIST" style is supposed to have the same
9400 * number of items in a column even if there is no scroll bar.
9401 * Since if a scroll bar already exists then the bottom is already
9402 * reduced, only reduce if the scroll bar does not currently exist.
9403 * The "2" is there to mimic the native control. I think it may be
9404 * related to either padding or edges. (GLA 7/2002)
9406 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9407 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9408 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9410 else if (uView == LVS_REPORT)
9412 HDLAYOUT hl;
9413 WINDOWPOS wp;
9415 hl.prc = &infoPtr->rcList;
9416 hl.pwpos = &wp;
9417 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9418 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9419 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9420 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9421 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9422 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9424 infoPtr->rcList.top = max(wp.cy, 0);
9425 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9428 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9431 /***
9432 * DESCRIPTION:
9433 * Processes WM_STYLECHANGED messages.
9435 * PARAMETER(S):
9436 * [I] infoPtr : valid pointer to the listview structure
9437 * [I] wStyleType : window style type (normal or extended)
9438 * [I] lpss : window style information
9440 * RETURN:
9441 * Zero
9443 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9444 const STYLESTRUCT *lpss)
9446 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9447 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9448 UINT style;
9450 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9451 wStyleType, lpss->styleOld, lpss->styleNew);
9453 if (wStyleType != GWL_STYLE) return 0;
9455 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9456 /* what if LVS_OWNERDATA changed? */
9457 /* or LVS_SINGLESEL */
9458 /* or LVS_SORT{AS,DES}CENDING */
9460 infoPtr->dwStyle = lpss->styleNew;
9462 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9463 ((lpss->styleNew & WS_HSCROLL) == 0))
9464 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9466 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9467 ((lpss->styleNew & WS_VSCROLL) == 0))
9468 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9470 if (uNewView != uOldView)
9472 SIZE oldIconSize = infoPtr->iconSize;
9473 HIMAGELIST himl;
9475 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9476 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9478 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9479 SetRectEmpty(&infoPtr->rcFocus);
9481 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9482 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9484 if (uNewView == LVS_ICON)
9486 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9488 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9489 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9490 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9493 else if (uNewView == LVS_REPORT)
9495 HDLAYOUT hl;
9496 WINDOWPOS wp;
9498 hl.prc = &infoPtr->rcList;
9499 hl.pwpos = &wp;
9500 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9501 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9502 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9503 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9506 LISTVIEW_UpdateItemSize(infoPtr);
9509 if (uNewView == LVS_REPORT)
9511 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9513 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9515 /* Turn off the header control */
9516 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9517 TRACE("Hide header control, was 0x%08x\n", style);
9518 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9519 } else {
9520 /* Turn on the header control */
9521 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9523 TRACE("Show header control, was 0x%08x\n", style);
9524 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9530 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9531 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9532 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9534 /* update the size of the client area */
9535 LISTVIEW_UpdateSize(infoPtr);
9537 /* add scrollbars if needed */
9538 LISTVIEW_UpdateScroll(infoPtr);
9540 /* invalidate client area + erase background */
9541 LISTVIEW_InvalidateList(infoPtr);
9543 return 0;
9546 /***
9547 * DESCRIPTION:
9548 * Window procedure of the listview control.
9551 static LRESULT WINAPI
9552 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9554 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9556 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9558 if (!infoPtr && (uMsg != WM_NCCREATE))
9559 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9561 switch (uMsg)
9563 case LVM_APPROXIMATEVIEWRECT:
9564 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9565 LOWORD(lParam), HIWORD(lParam));
9566 case LVM_ARRANGE:
9567 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9569 /* case LVM_CANCELEDITLABEL: */
9571 case LVM_CREATEDRAGIMAGE:
9572 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9574 case LVM_DELETEALLITEMS:
9575 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9577 case LVM_DELETECOLUMN:
9578 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9580 case LVM_DELETEITEM:
9581 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9583 case LVM_EDITLABELW:
9584 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9586 case LVM_EDITLABELA:
9587 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9589 /* case LVM_ENABLEGROUPVIEW: */
9591 case LVM_ENSUREVISIBLE:
9592 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9594 case LVM_FINDITEMW:
9595 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9597 case LVM_FINDITEMA:
9598 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9600 case LVM_GETBKCOLOR:
9601 return infoPtr->clrBk;
9603 /* case LVM_GETBKIMAGE: */
9605 case LVM_GETCALLBACKMASK:
9606 return infoPtr->uCallbackMask;
9608 case LVM_GETCOLUMNA:
9609 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9611 case LVM_GETCOLUMNW:
9612 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9614 case LVM_GETCOLUMNORDERARRAY:
9615 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9617 case LVM_GETCOLUMNWIDTH:
9618 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9620 case LVM_GETCOUNTPERPAGE:
9621 return LISTVIEW_GetCountPerPage(infoPtr);
9623 case LVM_GETEDITCONTROL:
9624 return (LRESULT)infoPtr->hwndEdit;
9626 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9627 return infoPtr->dwLvExStyle;
9629 /* case LVM_GETGROUPINFO: */
9631 /* case LVM_GETGROUPMETRICS: */
9633 case LVM_GETHEADER:
9634 return (LRESULT)infoPtr->hwndHeader;
9636 case LVM_GETHOTCURSOR:
9637 return (LRESULT)infoPtr->hHotCursor;
9639 case LVM_GETHOTITEM:
9640 return infoPtr->nHotItem;
9642 case LVM_GETHOVERTIME:
9643 return infoPtr->dwHoverTime;
9645 case LVM_GETIMAGELIST:
9646 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9648 /* case LVM_GETINSERTMARK: */
9650 /* case LVM_GETINSERTMARKCOLOR: */
9652 /* case LVM_GETINSERTMARKRECT: */
9654 case LVM_GETISEARCHSTRINGA:
9655 case LVM_GETISEARCHSTRINGW:
9656 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9657 return FALSE;
9659 case LVM_GETITEMA:
9660 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9662 case LVM_GETITEMW:
9663 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9665 case LVM_GETITEMCOUNT:
9666 return infoPtr->nItemCount;
9668 case LVM_GETITEMPOSITION:
9669 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9671 case LVM_GETITEMRECT:
9672 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9674 case LVM_GETITEMSPACING:
9675 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9677 case LVM_GETITEMSTATE:
9678 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9680 case LVM_GETITEMTEXTA:
9681 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9683 case LVM_GETITEMTEXTW:
9684 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9686 case LVM_GETNEXTITEM:
9687 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9689 case LVM_GETNUMBEROFWORKAREAS:
9690 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9691 return 1;
9693 case LVM_GETORIGIN:
9694 if (!lParam) return FALSE;
9695 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9696 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9697 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9698 return TRUE;
9700 /* case LVM_GETOUTLINECOLOR: */
9702 /* case LVM_GETSELECTEDCOLUMN: */
9704 case LVM_GETSELECTEDCOUNT:
9705 return LISTVIEW_GetSelectedCount(infoPtr);
9707 case LVM_GETSELECTIONMARK:
9708 return infoPtr->nSelectionMark;
9710 case LVM_GETSTRINGWIDTHA:
9711 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9713 case LVM_GETSTRINGWIDTHW:
9714 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9716 case LVM_GETSUBITEMRECT:
9717 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9719 case LVM_GETTEXTBKCOLOR:
9720 return infoPtr->clrTextBk;
9722 case LVM_GETTEXTCOLOR:
9723 return infoPtr->clrText;
9725 /* case LVM_GETTILEINFO: */
9727 /* case LVM_GETTILEVIEWINFO: */
9729 case LVM_GETTOOLTIPS:
9730 if( !infoPtr->hwndToolTip )
9731 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9732 return (LRESULT)infoPtr->hwndToolTip;
9734 case LVM_GETTOPINDEX:
9735 return LISTVIEW_GetTopIndex(infoPtr);
9737 case LVM_GETUNICODEFORMAT:
9738 return (infoPtr->notifyFormat == NFR_UNICODE);
9740 /* case LVM_GETVIEW: */
9742 case LVM_GETVIEWRECT:
9743 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9745 case LVM_GETWORKAREAS:
9746 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9747 return FALSE;
9749 /* case LVM_HASGROUP: */
9751 case LVM_HITTEST:
9752 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9754 case LVM_INSERTCOLUMNA:
9755 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9757 case LVM_INSERTCOLUMNW:
9758 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9760 /* case LVM_INSERTGROUP: */
9762 /* case LVM_INSERTGROUPSORTED: */
9764 case LVM_INSERTITEMA:
9765 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9767 case LVM_INSERTITEMW:
9768 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9770 /* case LVM_INSERTMARKHITTEST: */
9772 /* case LVM_ISGROUPVIEWENABLED: */
9774 /* case LVM_MAPIDTOINDEX: */
9776 /* case LVM_MAPINDEXTOID: */
9778 /* case LVM_MOVEGROUP: */
9780 /* case LVM_MOVEITEMTOGROUP: */
9782 case LVM_REDRAWITEMS:
9783 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9785 /* case LVM_REMOVEALLGROUPS: */
9787 /* case LVM_REMOVEGROUP: */
9789 case LVM_SCROLL:
9790 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9792 case LVM_SETBKCOLOR:
9793 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9795 /* case LVM_SETBKIMAGE: */
9797 case LVM_SETCALLBACKMASK:
9798 infoPtr->uCallbackMask = (UINT)wParam;
9799 return TRUE;
9801 case LVM_SETCOLUMNA:
9802 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9804 case LVM_SETCOLUMNW:
9805 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9807 case LVM_SETCOLUMNORDERARRAY:
9808 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9810 case LVM_SETCOLUMNWIDTH:
9811 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9813 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9814 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9816 /* case LVM_SETGROUPINFO: */
9818 /* case LVM_SETGROUPMETRICS: */
9820 case LVM_SETHOTCURSOR:
9821 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9823 case LVM_SETHOTITEM:
9824 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9826 case LVM_SETHOVERTIME:
9827 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9829 case LVM_SETICONSPACING:
9830 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9832 case LVM_SETIMAGELIST:
9833 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9835 /* case LVM_SETINFOTIP: */
9837 /* case LVM_SETINSERTMARK: */
9839 /* case LVM_SETINSERTMARKCOLOR: */
9841 case LVM_SETITEMA:
9842 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9844 case LVM_SETITEMW:
9845 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9847 case LVM_SETITEMCOUNT:
9848 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9850 case LVM_SETITEMPOSITION:
9852 POINT pt;
9853 pt.x = (short)LOWORD(lParam);
9854 pt.y = (short)HIWORD(lParam);
9855 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9858 case LVM_SETITEMPOSITION32:
9859 if (lParam == 0) return FALSE;
9860 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9862 case LVM_SETITEMSTATE:
9863 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9865 case LVM_SETITEMTEXTA:
9866 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9868 case LVM_SETITEMTEXTW:
9869 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9871 /* case LVM_SETOUTLINECOLOR: */
9873 /* case LVM_SETSELECTEDCOLUMN: */
9875 case LVM_SETSELECTIONMARK:
9876 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9878 case LVM_SETTEXTBKCOLOR:
9879 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9881 case LVM_SETTEXTCOLOR:
9882 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9884 /* case LVM_SETTILEINFO: */
9886 /* case LVM_SETTILEVIEWINFO: */
9888 /* case LVM_SETTILEWIDTH: */
9890 case LVM_SETTOOLTIPS:
9891 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9893 case LVM_SETUNICODEFORMAT:
9894 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9896 /* case LVM_SETVIEW: */
9898 /* case LVM_SETWORKAREAS: */
9900 /* case LVM_SORTGROUPS: */
9902 case LVM_SORTITEMS:
9903 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9905 /* LVM_SORTITEMSEX: */
9907 case LVM_SUBITEMHITTEST:
9908 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9910 case LVM_UPDATE:
9911 return LISTVIEW_Update(infoPtr, (INT)wParam);
9913 case WM_CHAR:
9914 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9916 case WM_COMMAND:
9917 return LISTVIEW_Command(infoPtr, wParam, lParam);
9919 case WM_NCCREATE:
9920 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9922 case WM_CREATE:
9923 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9925 case WM_DESTROY:
9926 return LISTVIEW_Destroy(infoPtr);
9928 case WM_ENABLE:
9929 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9931 case WM_ERASEBKGND:
9932 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9934 case WM_GETDLGCODE:
9935 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9937 case WM_GETFONT:
9938 return (LRESULT)infoPtr->hFont;
9940 case WM_HSCROLL:
9941 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9943 case WM_KEYDOWN:
9944 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9946 case WM_KILLFOCUS:
9947 return LISTVIEW_KillFocus(infoPtr);
9949 case WM_LBUTTONDBLCLK:
9950 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9952 case WM_LBUTTONDOWN:
9953 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9955 case WM_LBUTTONUP:
9956 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9958 case WM_MOUSEMOVE:
9959 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9961 case WM_MOUSEHOVER:
9962 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9964 case WM_NCDESTROY:
9965 return LISTVIEW_NCDestroy(infoPtr);
9967 case WM_NCPAINT:
9968 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9969 return 0;
9970 goto fwd_msg;
9972 case WM_NOTIFY:
9973 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9974 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9975 else return 0;
9977 case WM_NOTIFYFORMAT:
9978 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9980 case WM_PRINTCLIENT:
9981 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9983 case WM_PAINT:
9984 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9986 case WM_RBUTTONDBLCLK:
9987 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9989 case WM_RBUTTONDOWN:
9990 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9992 case WM_RBUTTONUP:
9993 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9995 case WM_SETCURSOR:
9996 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9997 return TRUE;
9998 goto fwd_msg;
10000 case WM_SETFOCUS:
10001 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10003 case WM_SETFONT:
10004 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10006 case WM_SETREDRAW:
10007 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10009 case WM_SIZE:
10010 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10012 case WM_STYLECHANGED:
10013 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10015 case WM_SYSCOLORCHANGE:
10016 COMCTL32_RefreshSysColors();
10017 return 0;
10019 /* case WM_TIMER: */
10020 case WM_THEMECHANGED:
10021 return LISTVIEW_ThemeChanged(infoPtr);
10023 case WM_VSCROLL:
10024 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10026 case WM_MOUSEWHEEL:
10027 if (wParam & (MK_SHIFT | MK_CONTROL))
10028 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10029 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10031 case WM_WINDOWPOSCHANGED:
10032 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10034 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10035 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10036 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10038 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10040 MEASUREITEMSTRUCT mis;
10041 mis.CtlType = ODT_LISTVIEW;
10042 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10043 mis.itemID = -1;
10044 mis.itemWidth = 0;
10045 mis.itemData = 0;
10046 mis.itemHeight= infoPtr->nItemHeight;
10047 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10048 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10049 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10052 LISTVIEW_UpdateSize(infoPtr);
10053 LISTVIEW_UpdateScroll(infoPtr);
10055 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10057 /* case WM_WININICHANGE: */
10059 default:
10060 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10061 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10063 fwd_msg:
10064 /* call default window procedure */
10065 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10070 /***
10071 * DESCRIPTION:
10072 * Registers the window class.
10074 * PARAMETER(S):
10075 * None
10077 * RETURN:
10078 * None
10080 void LISTVIEW_Register(void)
10082 WNDCLASSW wndClass;
10084 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10085 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10086 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10087 wndClass.cbClsExtra = 0;
10088 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10089 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10090 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10091 wndClass.lpszClassName = WC_LISTVIEWW;
10092 RegisterClassW(&wndClass);
10095 /***
10096 * DESCRIPTION:
10097 * Unregisters the window class.
10099 * PARAMETER(S):
10100 * None
10102 * RETURN:
10103 * None
10105 void LISTVIEW_Unregister(void)
10107 UnregisterClassW(WC_LISTVIEWW, NULL);
10110 /***
10111 * DESCRIPTION:
10112 * Handle any WM_COMMAND messages
10114 * PARAMETER(S):
10115 * [I] infoPtr : valid pointer to the listview structure
10116 * [I] wParam : the first message parameter
10117 * [I] lParam : the second message parameter
10119 * RETURN:
10120 * Zero.
10122 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10124 switch (HIWORD(wParam))
10126 case EN_UPDATE:
10129 * Adjust the edit window size
10131 WCHAR buffer[1024];
10132 HDC hdc = GetDC(infoPtr->hwndEdit);
10133 HFONT hFont, hOldFont = 0;
10134 RECT rect;
10135 SIZE sz;
10137 if (!infoPtr->hwndEdit || !hdc) return 0;
10138 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10139 GetWindowRect(infoPtr->hwndEdit, &rect);
10141 /* Select font to get the right dimension of the string */
10142 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10143 if(hFont != 0)
10145 hOldFont = SelectObject(hdc, hFont);
10148 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10150 TEXTMETRICW textMetric;
10152 /* Add Extra spacing for the next character */
10153 GetTextMetricsW(hdc, &textMetric);
10154 sz.cx += (textMetric.tmMaxCharWidth * 2);
10156 SetWindowPos (
10157 infoPtr->hwndEdit,
10158 HWND_TOP,
10161 sz.cx,
10162 rect.bottom - rect.top,
10163 SWP_DRAWFRAME|SWP_NOMOVE);
10165 if(hFont != 0)
10166 SelectObject(hdc, hOldFont);
10168 ReleaseDC(infoPtr->hwndEdit, hdc);
10170 break;
10173 default:
10174 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10177 return 0;
10181 /***
10182 * DESCRIPTION:
10183 * Subclassed edit control windproc function
10185 * PARAMETER(S):
10186 * [I] hwnd : the edit window handle
10187 * [I] uMsg : the message that is to be processed
10188 * [I] wParam : first message parameter
10189 * [I] lParam : second message parameter
10190 * [I] isW : TRUE if input is Unicode
10192 * RETURN:
10193 * Zero.
10195 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10197 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10198 BOOL cancel = FALSE;
10200 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10201 hwnd, uMsg, wParam, lParam, isW);
10203 switch (uMsg)
10205 case WM_GETDLGCODE:
10206 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10208 case WM_KILLFOCUS:
10209 break;
10211 case WM_DESTROY:
10213 WNDPROC editProc = infoPtr->EditWndProc;
10214 infoPtr->EditWndProc = 0;
10215 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10216 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10219 case WM_KEYDOWN:
10220 if (VK_ESCAPE == (INT)wParam)
10222 cancel = TRUE;
10223 break;
10225 else if (VK_RETURN == (INT)wParam)
10226 break;
10228 default:
10229 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10232 /* kill the edit */
10233 if (infoPtr->hwndEdit)
10235 LPWSTR buffer = NULL;
10237 infoPtr->hwndEdit = 0;
10238 if (!cancel)
10240 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10242 if (len)
10244 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10246 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10247 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10251 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10253 Free(buffer);
10256 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10257 return 0;
10260 /***
10261 * DESCRIPTION:
10262 * Subclassed edit control Unicode windproc function
10264 * PARAMETER(S):
10265 * [I] hwnd : the edit window handle
10266 * [I] uMsg : the message that is to be processed
10267 * [I] wParam : first message parameter
10268 * [I] lParam : second message parameter
10270 * RETURN:
10272 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10274 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10277 /***
10278 * DESCRIPTION:
10279 * Subclassed edit control ANSI windproc function
10281 * PARAMETER(S):
10282 * [I] hwnd : the edit window handle
10283 * [I] uMsg : the message that is to be processed
10284 * [I] wParam : first message parameter
10285 * [I] lParam : second message parameter
10287 * RETURN:
10289 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10291 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10294 /***
10295 * DESCRIPTION:
10296 * Creates a subclassed edit control
10298 * PARAMETER(S):
10299 * [I] infoPtr : valid pointer to the listview structure
10300 * [I] text : initial text for the edit
10301 * [I] style : the window style
10302 * [I] isW : TRUE if input is Unicode
10304 * RETURN:
10306 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10307 INT x, INT y, INT width, INT height, BOOL isW)
10309 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10310 HWND hedit;
10311 SIZE sz;
10312 HDC hdc;
10313 HDC hOldFont=0;
10314 TEXTMETRICW textMetric;
10315 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10317 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10319 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10320 hdc = GetDC(infoPtr->hwndSelf);
10322 /* Select the font to get appropriate metric dimensions */
10323 if(infoPtr->hFont != 0)
10324 hOldFont = SelectObject(hdc, infoPtr->hFont);
10326 /*Get String Length in pixels */
10327 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10329 /*Add Extra spacing for the next character */
10330 GetTextMetricsW(hdc, &textMetric);
10331 sz.cx += (textMetric.tmMaxCharWidth * 2);
10333 if(infoPtr->hFont != 0)
10334 SelectObject(hdc, hOldFont);
10336 ReleaseDC(infoPtr->hwndSelf, hdc);
10337 if (isW)
10338 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10339 else
10340 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10342 if (!hedit) return 0;
10344 infoPtr->EditWndProc = (WNDPROC)
10345 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10346 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10348 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10350 return hedit;