comctl32: Don't notify if listview edit box contents have not changed.
[wine/multimedia.git] / dlls / comctl32 / listview.c
blobad48403cb8812f9d9191b8e1ad0537dea44a9d31
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 if (infoPtr->nItemHeight > 0)
1717 vertInfo.nPage /= infoPtr->nItemHeight;
1719 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1721 RECT rcView;
1723 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1726 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1727 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1728 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1729 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1730 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1732 /* Change of the range may have changed the scroll pos. If so move the content */
1733 if (dx != 0 || dy != 0)
1735 RECT listRect;
1736 listRect = infoPtr->rcList;
1737 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1738 SW_ERASE | SW_INVALIDATE);
1741 /* Update the Header Control */
1742 if (uView == LVS_REPORT)
1744 horzInfo.fMask = SIF_POS;
1745 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1746 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1751 /***
1752 * DESCRIPTION:
1753 * Shows/hides the focus rectangle.
1755 * PARAMETER(S):
1756 * [I] infoPtr : valid pointer to the listview structure
1757 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1759 * RETURN:
1760 * None
1762 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1764 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1765 HDC hdc;
1767 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1769 if (infoPtr->nFocusedItem < 0) return;
1771 /* we need some gymnastics in ICON mode to handle large items */
1772 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1774 RECT rcBox;
1776 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1777 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1779 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1780 return;
1784 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1786 /* for some reason, owner draw should work only in report mode */
1787 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1789 DRAWITEMSTRUCT dis;
1790 LVITEMW item;
1792 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1793 HFONT hOldFont = SelectObject(hdc, hFont);
1795 item.iItem = infoPtr->nFocusedItem;
1796 item.iSubItem = 0;
1797 item.mask = LVIF_PARAM;
1798 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1800 ZeroMemory(&dis, sizeof(dis));
1801 dis.CtlType = ODT_LISTVIEW;
1802 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1803 dis.itemID = item.iItem;
1804 dis.itemAction = ODA_FOCUS;
1805 if (fShow) dis.itemState |= ODS_FOCUS;
1806 dis.hwndItem = infoPtr->hwndSelf;
1807 dis.hDC = hdc;
1808 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1809 dis.itemData = item.lParam;
1811 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1813 SelectObject(hdc, hOldFont);
1815 else
1817 DrawFocusRect(hdc, &infoPtr->rcFocus);
1819 done:
1820 ReleaseDC(infoPtr->hwndSelf, hdc);
1823 /***
1824 * Invalidates all visible selected items.
1826 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1828 ITERATOR i;
1830 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1831 while(iterator_next(&i))
1833 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1834 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1836 iterator_destroy(&i);
1840 /***
1841 * DESCRIPTION: [INTERNAL]
1842 * Computes an item's (left,top) corner, relative to rcView.
1843 * That is, the position has NOT been made relative to the Origin.
1844 * This is deliberate, to avoid computing the Origin over, and
1845 * over again, when this function is called in a loop. Instead,
1846 * one can factor the computation of the Origin before the loop,
1847 * and offset the value returned by this function, on every iteration.
1849 * PARAMETER(S):
1850 * [I] infoPtr : valid pointer to the listview structure
1851 * [I] nItem : item number
1852 * [O] lpptOrig : item top, left corner
1854 * RETURN:
1855 * None.
1857 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1859 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1861 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1863 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1865 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1866 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1868 else if (uView == LVS_LIST)
1870 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1871 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1872 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1874 else /* LVS_REPORT */
1876 lpptPosition->x = 0;
1877 lpptPosition->y = nItem * infoPtr->nItemHeight;
1881 /***
1882 * DESCRIPTION: [INTERNAL]
1883 * Compute the rectangles of an item. This is to localize all
1884 * the computations in one place. If you are not interested in some
1885 * of these values, simply pass in a NULL -- the function is smart
1886 * enough to compute only what's necessary. The function computes
1887 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1888 * one, the BOX rectangle. This rectangle is very cheap to compute,
1889 * and is guaranteed to contain all the other rectangles. Computing
1890 * the ICON rect is also cheap, but all the others are potentially
1891 * expensive. This gives an easy and effective optimization when
1892 * searching (like point inclusion, or rectangle intersection):
1893 * first test against the BOX, and if TRUE, test against the desired
1894 * rectangle.
1895 * If the function does not have all the necessary information
1896 * to computed the requested rectangles, will crash with a
1897 * failed assertion. This is done so we catch all programming
1898 * errors, given that the function is called only from our code.
1900 * We have the following 'special' meanings for a few fields:
1901 * * If LVIS_FOCUSED is set, we assume the item has the focus
1902 * This is important in ICON mode, where it might get a larger
1903 * then usual rectangle
1905 * Please note that subitem support works only in REPORT mode.
1907 * PARAMETER(S):
1908 * [I] infoPtr : valid pointer to the listview structure
1909 * [I] lpLVItem : item to compute the measures for
1910 * [O] lprcBox : ptr to Box rectangle
1911 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1912 * [0] lprcSelectBox : ptr to select box rectangle
1913 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1914 * [O] lprcIcon : ptr to Icon rectangle
1915 * Same as LVM_GETITEMRECT with LVIR_ICON
1916 * [O] lprcStateIcon: ptr to State Icon rectangle
1917 * [O] lprcLabel : ptr to Label rectangle
1918 * Same as LVM_GETITEMRECT with LVIR_LABEL
1920 * RETURN:
1921 * None.
1923 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1924 LPRECT lprcBox, LPRECT lprcSelectBox,
1925 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1927 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1928 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1929 RECT Box, SelectBox, Icon, Label;
1930 COLUMN_INFO *lpColumnInfo = NULL;
1931 SIZE labelSize = { 0, 0 };
1933 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1935 /* Be smart and try to figure out the minimum we have to do */
1936 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1937 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1939 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1940 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1942 if (lprcSelectBox) doSelectBox = TRUE;
1943 if (lprcLabel) doLabel = TRUE;
1944 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1945 if (doSelectBox)
1947 doIcon = TRUE;
1948 doLabel = TRUE;
1951 /************************************************************/
1952 /* compute the box rectangle (it should be cheap to do) */
1953 /************************************************************/
1954 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1955 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1957 if (lpLVItem->iSubItem)
1959 Box = lpColumnInfo->rcHeader;
1961 else
1963 Box.left = 0;
1964 Box.right = infoPtr->nItemWidth;
1966 Box.top = 0;
1967 Box.bottom = infoPtr->nItemHeight;
1969 /******************************************************************/
1970 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1971 /******************************************************************/
1972 if (doIcon)
1974 LONG state_width = 0;
1976 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1977 state_width = infoPtr->iconStateSize.cx;
1979 if (uView == LVS_ICON)
1981 Icon.left = Box.left + state_width;
1982 if (infoPtr->himlNormal)
1983 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1984 Icon.top = Box.top + ICON_TOP_PADDING;
1985 Icon.right = Icon.left;
1986 Icon.bottom = Icon.top;
1987 if (infoPtr->himlNormal)
1989 Icon.right += infoPtr->iconSize.cx;
1990 Icon.bottom += infoPtr->iconSize.cy;
1993 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1995 Icon.left = Box.left + state_width;
1997 if (uView == LVS_REPORT)
1998 Icon.left += REPORT_MARGINX;
2000 Icon.top = Box.top;
2001 Icon.right = Icon.left;
2002 if (infoPtr->himlSmall &&
2003 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2004 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2005 Icon.right += infoPtr->iconSize.cx;
2006 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2008 if(lprcIcon) *lprcIcon = Icon;
2009 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2011 /* TODO: is this correct? */
2012 if (lprcStateIcon)
2014 lprcStateIcon->left = Icon.left - state_width;
2015 lprcStateIcon->right = Icon.left;
2016 lprcStateIcon->top = Icon.top;
2017 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2018 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2021 else Icon.right = 0;
2023 /************************************************************/
2024 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2025 /************************************************************/
2026 if (doLabel)
2028 /* calculate how far to the right can the label stretch */
2029 Label.right = Box.right;
2030 if (uView == LVS_REPORT)
2032 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2035 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2037 labelSize.cx = infoPtr->nItemWidth;
2038 labelSize.cy = infoPtr->nItemHeight;
2039 goto calc_label;
2042 /* we need the text in non owner draw mode */
2043 assert(lpLVItem->mask & LVIF_TEXT);
2044 if (is_textT(lpLVItem->pszText, TRUE))
2046 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2047 HDC hdc = GetDC(infoPtr->hwndSelf);
2048 HFONT hOldFont = SelectObject(hdc, hFont);
2049 UINT uFormat;
2050 RECT rcText;
2052 /* compute rough rectangle where the label will go */
2053 SetRectEmpty(&rcText);
2054 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2055 rcText.bottom = infoPtr->nItemHeight;
2056 if (uView == LVS_ICON)
2057 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2059 /* now figure out the flags */
2060 if (uView == LVS_ICON)
2061 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2062 else
2063 uFormat = LV_SL_DT_FLAGS;
2065 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2067 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2068 labelSize.cy = rcText.bottom - rcText.top;
2070 SelectObject(hdc, hOldFont);
2071 ReleaseDC(infoPtr->hwndSelf, hdc);
2074 calc_label:
2075 if (uView == LVS_ICON)
2077 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2078 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2079 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2080 Label.right = Label.left + labelSize.cx;
2081 Label.bottom = Label.top + infoPtr->nItemHeight;
2082 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2084 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2085 labelSize.cy /= infoPtr->ntmHeight;
2086 labelSize.cy = max(labelSize.cy, 1);
2087 labelSize.cy *= infoPtr->ntmHeight;
2089 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2091 else if (uView == LVS_REPORT)
2093 Label.left = Icon.right;
2094 Label.top = Box.top;
2095 Label.right = lpColumnInfo->rcHeader.right;
2096 Label.bottom = Label.top + infoPtr->nItemHeight;
2098 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2100 Label.left = Icon.right;
2101 Label.top = Box.top;
2102 Label.right = min(Label.left + labelSize.cx, Label.right);
2103 Label.bottom = Label.top + infoPtr->nItemHeight;
2106 if (lprcLabel) *lprcLabel = Label;
2107 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2110 /************************************************************/
2111 /* compute STATEICON bounding box */
2112 /************************************************************/
2113 if (doSelectBox)
2115 if (uView == LVS_REPORT)
2117 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2118 SelectBox.top = Box.top;
2119 SelectBox.bottom = Box.bottom;
2120 if (lpLVItem->iSubItem == 0)
2122 /* we need the indent in report mode */
2123 assert(lpLVItem->mask & LVIF_INDENT);
2124 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2126 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2128 else
2130 UnionRect(&SelectBox, &Icon, &Label);
2132 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2133 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2136 /* Fix the Box if necessary */
2137 if (lprcBox)
2139 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2140 else *lprcBox = Box;
2142 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2145 /***
2146 * DESCRIPTION: [INTERNAL]
2148 * PARAMETER(S):
2149 * [I] infoPtr : valid pointer to the listview structure
2150 * [I] nItem : item number
2151 * [O] lprcBox : ptr to Box rectangle
2153 * RETURN:
2154 * None.
2156 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2158 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2159 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2160 POINT Position, Origin;
2161 LVITEMW lvItem;
2163 LISTVIEW_GetOrigin(infoPtr, &Origin);
2164 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2166 /* Be smart and try to figure out the minimum we have to do */
2167 lvItem.mask = 0;
2168 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2169 lvItem.mask |= LVIF_TEXT;
2170 lvItem.iItem = nItem;
2171 lvItem.iSubItem = 0;
2172 lvItem.pszText = szDispText;
2173 lvItem.cchTextMax = DISP_TEXT_SIZE;
2174 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2175 if (uView == LVS_ICON)
2177 lvItem.mask |= LVIF_STATE;
2178 lvItem.stateMask = LVIS_FOCUSED;
2179 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2181 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2183 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2187 /***
2188 * DESCRIPTION:
2189 * Returns the current icon position, and advances it along the top.
2190 * The returned position is not offset by Origin.
2192 * PARAMETER(S):
2193 * [I] infoPtr : valid pointer to the listview structure
2194 * [O] lpPos : will get the current icon position
2196 * RETURN:
2197 * None
2199 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2201 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2203 *lpPos = infoPtr->currIconPos;
2205 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2206 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2208 infoPtr->currIconPos.x = 0;
2209 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2213 /***
2214 * DESCRIPTION:
2215 * Returns the current icon position, and advances it down the left edge.
2216 * The returned position is not offset by Origin.
2218 * PARAMETER(S):
2219 * [I] infoPtr : valid pointer to the listview structure
2220 * [O] lpPos : will get the current icon position
2222 * RETURN:
2223 * None
2225 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2227 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2229 *lpPos = infoPtr->currIconPos;
2231 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2232 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2234 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2235 infoPtr->currIconPos.y = 0;
2239 /***
2240 * DESCRIPTION:
2241 * Moves an icon to the specified position.
2242 * It takes care of invalidating the item, etc.
2244 * PARAMETER(S):
2245 * [I] infoPtr : valid pointer to the listview structure
2246 * [I] nItem : the item to move
2247 * [I] lpPos : the new icon position
2248 * [I] isNew : flags the item as being new
2250 * RETURN:
2251 * Success: TRUE
2252 * Failure: FALSE
2254 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2256 POINT old;
2258 if (!isNew)
2260 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2261 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2263 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2264 LISTVIEW_InvalidateItem(infoPtr, nItem);
2267 /* Allocating a POINTER for every item is too resource intensive,
2268 * so we'll keep the (x,y) in different arrays */
2269 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2270 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2272 LISTVIEW_InvalidateItem(infoPtr, nItem);
2274 return TRUE;
2277 /***
2278 * DESCRIPTION:
2279 * Arranges listview items in icon display mode.
2281 * PARAMETER(S):
2282 * [I] infoPtr : valid pointer to the listview structure
2283 * [I] nAlignCode : alignment code
2285 * RETURN:
2286 * SUCCESS : TRUE
2287 * FAILURE : FALSE
2289 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2291 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2292 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2293 POINT pos;
2294 INT i;
2296 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2298 TRACE("nAlignCode=%d\n", nAlignCode);
2300 if (nAlignCode == LVA_DEFAULT)
2302 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2303 else nAlignCode = LVA_ALIGNTOP;
2306 switch (nAlignCode)
2308 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2309 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2310 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2311 default: return FALSE;
2314 infoPtr->bAutoarrange = TRUE;
2315 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2316 for (i = 0; i < infoPtr->nItemCount; i++)
2318 next_pos(infoPtr, &pos);
2319 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2322 return TRUE;
2325 /***
2326 * DESCRIPTION:
2327 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2329 * PARAMETER(S):
2330 * [I] infoPtr : valid pointer to the listview structure
2331 * [O] lprcView : bounding rectangle
2333 * RETURN:
2334 * SUCCESS : TRUE
2335 * FAILURE : FALSE
2337 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2339 INT i, x, y;
2341 SetRectEmpty(lprcView);
2343 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2345 case LVS_ICON:
2346 case LVS_SMALLICON:
2347 for (i = 0; i < infoPtr->nItemCount; i++)
2349 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2350 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2351 lprcView->right = max(lprcView->right, x);
2352 lprcView->bottom = max(lprcView->bottom, y);
2354 if (infoPtr->nItemCount > 0)
2356 lprcView->right += infoPtr->nItemWidth;
2357 lprcView->bottom += infoPtr->nItemHeight;
2359 break;
2361 case LVS_LIST:
2362 y = LISTVIEW_GetCountPerColumn(infoPtr);
2363 x = infoPtr->nItemCount / y;
2364 if (infoPtr->nItemCount % y) x++;
2365 lprcView->right = x * infoPtr->nItemWidth;
2366 lprcView->bottom = y * infoPtr->nItemHeight;
2367 break;
2369 case LVS_REPORT:
2370 lprcView->right = infoPtr->nItemWidth;
2371 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2372 break;
2376 /***
2377 * DESCRIPTION:
2378 * Retrieves the bounding rectangle of all the items.
2380 * PARAMETER(S):
2381 * [I] infoPtr : valid pointer to the listview structure
2382 * [O] lprcView : bounding rectangle
2384 * RETURN:
2385 * SUCCESS : TRUE
2386 * FAILURE : FALSE
2388 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2390 POINT ptOrigin;
2392 TRACE("(lprcView=%p)\n", lprcView);
2394 if (!lprcView) return FALSE;
2396 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2397 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2398 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2400 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2402 return TRUE;
2405 /***
2406 * DESCRIPTION:
2407 * Retrieves the subitem pointer associated with the subitem index.
2409 * PARAMETER(S):
2410 * [I] hdpaSubItems : DPA handle for a specific item
2411 * [I] nSubItem : index of subitem
2413 * RETURN:
2414 * SUCCESS : subitem pointer
2415 * FAILURE : NULL
2417 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2419 SUBITEM_INFO *lpSubItem;
2420 INT i;
2422 /* we should binary search here if need be */
2423 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2425 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2426 if (lpSubItem->iSubItem == nSubItem)
2427 return lpSubItem;
2430 return NULL;
2434 /***
2435 * DESCRIPTION:
2436 * Calculates the desired item width.
2438 * PARAMETER(S):
2439 * [I] infoPtr : valid pointer to the listview structure
2441 * RETURN:
2442 * The desired item width.
2444 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2446 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2447 INT nItemWidth = 0;
2449 TRACE("uView=%d\n", uView);
2451 if (uView == LVS_ICON)
2452 nItemWidth = infoPtr->iconSpacing.cx;
2453 else if (uView == LVS_REPORT)
2455 RECT rcHeader;
2457 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2459 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2460 nItemWidth = rcHeader.right;
2463 else /* LVS_SMALLICON, or LVS_LIST */
2465 INT i;
2467 for (i = 0; i < infoPtr->nItemCount; i++)
2468 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2470 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2471 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2473 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2476 return max(nItemWidth, 1);
2479 /***
2480 * DESCRIPTION:
2481 * Calculates the desired item height.
2483 * PARAMETER(S):
2484 * [I] infoPtr : valid pointer to the listview structure
2486 * RETURN:
2487 * The desired item height.
2489 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2491 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2492 INT nItemHeight;
2494 TRACE("uView=%d\n", uView);
2496 if (uView == LVS_ICON)
2497 nItemHeight = infoPtr->iconSpacing.cy;
2498 else
2500 nItemHeight = infoPtr->ntmHeight;
2501 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2502 nItemHeight++;
2503 if (infoPtr->himlState)
2504 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2505 if (infoPtr->himlSmall)
2506 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2507 if (infoPtr->himlState || infoPtr->himlSmall)
2508 nItemHeight += HEIGHT_PADDING;
2509 if (infoPtr->nMeasureItemHeight > 0)
2510 nItemHeight = infoPtr->nMeasureItemHeight;
2513 return max(nItemHeight, 1);
2516 /***
2517 * DESCRIPTION:
2518 * Updates the width, and height of an item.
2520 * PARAMETER(S):
2521 * [I] infoPtr : valid pointer to the listview structure
2523 * RETURN:
2524 * None.
2526 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2528 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2529 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2533 /***
2534 * DESCRIPTION:
2535 * Retrieves and saves important text metrics info for the current
2536 * Listview font.
2538 * PARAMETER(S):
2539 * [I] infoPtr : valid pointer to the listview structure
2542 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2544 HDC hdc = GetDC(infoPtr->hwndSelf);
2545 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2546 HFONT hOldFont = SelectObject(hdc, hFont);
2547 TEXTMETRICW tm;
2548 SIZE sz;
2550 if (GetTextMetricsW(hdc, &tm))
2552 infoPtr->ntmHeight = tm.tmHeight;
2553 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2556 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2557 infoPtr->nEllipsisWidth = sz.cx;
2559 SelectObject(hdc, hOldFont);
2560 ReleaseDC(infoPtr->hwndSelf, hdc);
2562 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2565 /***
2566 * DESCRIPTION:
2567 * A compare function for ranges
2569 * PARAMETER(S)
2570 * [I] range1 : pointer to range 1;
2571 * [I] range2 : pointer to range 2;
2572 * [I] flags : flags
2574 * RETURNS:
2575 * > 0 : if range 1 > range 2
2576 * < 0 : if range 2 > range 1
2577 * = 0 : if range intersects range 2
2579 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2581 INT cmp;
2583 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2584 cmp = -1;
2585 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2586 cmp = 1;
2587 else
2588 cmp = 0;
2590 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2592 return cmp;
2595 #if DEBUG_RANGES
2596 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2597 #else
2598 #define ranges_check(ranges, desc) do { } while(0)
2599 #endif
2601 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2603 INT i;
2604 RANGE *prev, *curr;
2606 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2607 assert (ranges);
2608 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2609 ranges_dump(ranges);
2610 prev = DPA_GetPtr(ranges->hdpa, 0);
2611 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2612 assert (prev->lower >= 0 && prev->lower < prev->upper);
2613 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2615 curr = DPA_GetPtr(ranges->hdpa, i);
2616 assert (prev->upper <= curr->lower);
2617 assert (curr->lower < curr->upper);
2618 prev = curr;
2620 TRACE("--- Done checking---\n");
2623 static RANGES ranges_create(int count)
2625 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2626 if (!ranges) return NULL;
2627 ranges->hdpa = DPA_Create(count);
2628 if (ranges->hdpa) return ranges;
2629 Free(ranges);
2630 return NULL;
2633 static void ranges_clear(RANGES ranges)
2635 INT i;
2637 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2638 Free(DPA_GetPtr(ranges->hdpa, i));
2639 DPA_DeleteAllPtrs(ranges->hdpa);
2643 static void ranges_destroy(RANGES ranges)
2645 if (!ranges) return;
2646 ranges_clear(ranges);
2647 DPA_Destroy(ranges->hdpa);
2648 Free(ranges);
2651 static RANGES ranges_clone(RANGES ranges)
2653 RANGES clone;
2654 INT i;
2656 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2658 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2660 RANGE *newrng = Alloc(sizeof(RANGE));
2661 if (!newrng) goto fail;
2662 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2663 DPA_SetPtr(clone->hdpa, i, newrng);
2665 return clone;
2667 fail:
2668 TRACE ("clone failed\n");
2669 ranges_destroy(clone);
2670 return NULL;
2673 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2675 INT i;
2677 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2678 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2680 return ranges;
2683 static void ranges_dump(RANGES ranges)
2685 INT i;
2687 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2688 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2691 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2693 RANGE srchrng = { nItem, nItem + 1 };
2695 TRACE("(nItem=%d)\n", nItem);
2696 ranges_check(ranges, "before contain");
2697 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2700 static INT ranges_itemcount(RANGES ranges)
2702 INT i, count = 0;
2704 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2706 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2707 count += sel->upper - sel->lower;
2710 return count;
2713 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2715 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2716 INT index;
2718 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2719 if (index == -1) return TRUE;
2721 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2723 chkrng = DPA_GetPtr(ranges->hdpa, index);
2724 if (chkrng->lower >= nItem)
2725 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2726 if (chkrng->upper > nItem)
2727 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2729 return TRUE;
2732 static BOOL ranges_add(RANGES ranges, RANGE range)
2734 RANGE srchrgn;
2735 INT index;
2737 TRACE("(%s)\n", debugrange(&range));
2738 ranges_check(ranges, "before add");
2740 /* try find overlapping regions first */
2741 srchrgn.lower = range.lower - 1;
2742 srchrgn.upper = range.upper + 1;
2743 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2745 if (index == -1)
2747 RANGE *newrgn;
2749 TRACE("Adding new range\n");
2751 /* create the brand new range to insert */
2752 newrgn = Alloc(sizeof(RANGE));
2753 if(!newrgn) goto fail;
2754 *newrgn = range;
2756 /* figure out where to insert it */
2757 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2758 TRACE("index=%d\n", index);
2759 if (index == -1) index = 0;
2761 /* and get it over with */
2762 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2764 Free(newrgn);
2765 goto fail;
2768 else
2770 RANGE *chkrgn, *mrgrgn;
2771 INT fromindex, mergeindex;
2773 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2774 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2776 chkrgn->lower = min(range.lower, chkrgn->lower);
2777 chkrgn->upper = max(range.upper, chkrgn->upper);
2779 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2781 /* merge now common ranges */
2782 fromindex = 0;
2783 srchrgn.lower = chkrgn->lower - 1;
2784 srchrgn.upper = chkrgn->upper + 1;
2788 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2789 if (mergeindex == -1) break;
2790 if (mergeindex == index)
2792 fromindex = index + 1;
2793 continue;
2796 TRACE("Merge with index %i\n", mergeindex);
2798 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2799 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2800 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2801 Free(mrgrgn);
2802 DPA_DeletePtr(ranges->hdpa, mergeindex);
2803 if (mergeindex < index) index --;
2804 } while(1);
2807 ranges_check(ranges, "after add");
2808 return TRUE;
2810 fail:
2811 ranges_check(ranges, "failed add");
2812 return FALSE;
2815 static BOOL ranges_del(RANGES ranges, RANGE range)
2817 RANGE *chkrgn;
2818 INT index;
2820 TRACE("(%s)\n", debugrange(&range));
2821 ranges_check(ranges, "before del");
2823 /* we don't use DPAS_SORTED here, since we need *
2824 * to find the first overlapping range */
2825 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2826 while(index != -1)
2828 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2830 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2832 /* case 1: Same range */
2833 if ( (chkrgn->upper == range.upper) &&
2834 (chkrgn->lower == range.lower) )
2836 DPA_DeletePtr(ranges->hdpa, index);
2837 break;
2839 /* case 2: engulf */
2840 else if ( (chkrgn->upper <= range.upper) &&
2841 (chkrgn->lower >= range.lower) )
2843 DPA_DeletePtr(ranges->hdpa, index);
2845 /* case 3: overlap upper */
2846 else if ( (chkrgn->upper <= range.upper) &&
2847 (chkrgn->lower < range.lower) )
2849 chkrgn->upper = range.lower;
2851 /* case 4: overlap lower */
2852 else if ( (chkrgn->upper > range.upper) &&
2853 (chkrgn->lower >= range.lower) )
2855 chkrgn->lower = range.upper;
2856 break;
2858 /* case 5: fully internal */
2859 else
2861 RANGE tmprgn = *chkrgn, *newrgn;
2863 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2864 newrgn->lower = chkrgn->lower;
2865 newrgn->upper = range.lower;
2866 chkrgn->lower = range.upper;
2867 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2869 Free(newrgn);
2870 goto fail;
2872 chkrgn = &tmprgn;
2873 break;
2876 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2879 ranges_check(ranges, "after del");
2880 return TRUE;
2882 fail:
2883 ranges_check(ranges, "failed del");
2884 return FALSE;
2887 /***
2888 * DESCRIPTION:
2889 * Removes all selection ranges
2891 * Parameters(s):
2892 * [I] infoPtr : valid pointer to the listview structure
2893 * [I] toSkip : item range to skip removing the selection
2895 * RETURNS:
2896 * SUCCESS : TRUE
2897 * FAILURE : TRUE
2899 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2901 LVITEMW lvItem;
2902 ITERATOR i;
2903 RANGES clone;
2905 TRACE("()\n");
2907 lvItem.state = 0;
2908 lvItem.stateMask = LVIS_SELECTED;
2910 /* need to clone the DPA because callbacks can change it */
2911 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2912 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2913 while(iterator_next(&i))
2914 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2915 /* note that the iterator destructor will free the cloned range */
2916 iterator_destroy(&i);
2918 return TRUE;
2921 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2923 RANGES toSkip;
2925 if (!(toSkip = ranges_create(1))) return FALSE;
2926 if (nItem != -1) ranges_additem(toSkip, nItem);
2927 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2928 ranges_destroy(toSkip);
2929 return TRUE;
2932 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2934 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2937 /***
2938 * DESCRIPTION:
2939 * Retrieves the number of items that are marked as selected.
2941 * PARAMETER(S):
2942 * [I] infoPtr : valid pointer to the listview structure
2944 * RETURN:
2945 * Number of items selected.
2947 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2949 INT nSelectedCount = 0;
2951 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2953 INT i;
2954 for (i = 0; i < infoPtr->nItemCount; i++)
2956 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2957 nSelectedCount++;
2960 else
2961 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2963 TRACE("nSelectedCount=%d\n", nSelectedCount);
2964 return nSelectedCount;
2967 /***
2968 * DESCRIPTION:
2969 * Manages the item focus.
2971 * PARAMETER(S):
2972 * [I] infoPtr : valid pointer to the listview structure
2973 * [I] nItem : item index
2975 * RETURN:
2976 * TRUE : focused item changed
2977 * FALSE : focused item has NOT changed
2979 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2981 INT oldFocus = infoPtr->nFocusedItem;
2982 LVITEMW lvItem;
2984 if (nItem == infoPtr->nFocusedItem) return FALSE;
2986 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2987 lvItem.stateMask = LVIS_FOCUSED;
2988 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2990 return oldFocus != infoPtr->nFocusedItem;
2993 /* Helper function for LISTVIEW_ShiftIndices *only* */
2994 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2996 if (nShiftItem < nItem) return nShiftItem;
2998 if (nShiftItem > nItem) return nShiftItem + direction;
3000 if (direction > 0) return nShiftItem + direction;
3002 return min(nShiftItem, infoPtr->nItemCount - 1);
3006 * DESCRIPTION:
3007 * Updates the various indices after an item has been inserted or deleted.
3009 * PARAMETER(S):
3010 * [I] infoPtr : valid pointer to the listview structure
3011 * [I] nItem : item index
3012 * [I] direction : Direction of shift, +1 or -1.
3014 * RETURN:
3015 * None
3017 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3019 INT nNewFocus;
3020 BOOL bOldChange;
3022 /* temporarily disable change notification while shifting items */
3023 bOldChange = infoPtr->bDoChangeNotify;
3024 infoPtr->bDoChangeNotify = FALSE;
3026 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3028 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3030 assert(abs(direction) == 1);
3032 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3034 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3035 if (nNewFocus != infoPtr->nFocusedItem)
3036 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3038 /* But we are not supposed to modify nHotItem! */
3040 infoPtr->bDoChangeNotify = bOldChange;
3045 * DESCRIPTION:
3046 * Adds a block of selections.
3048 * PARAMETER(S):
3049 * [I] infoPtr : valid pointer to the listview structure
3050 * [I] nItem : item index
3052 * RETURN:
3053 * Whether the window is still valid.
3055 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3057 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3058 INT nLast = max(infoPtr->nSelectionMark, nItem);
3059 HWND hwndSelf = infoPtr->hwndSelf;
3060 NMLVODSTATECHANGE nmlv;
3061 LVITEMW item;
3062 BOOL bOldChange;
3063 INT i;
3065 /* Temporarily disable change notification
3066 * If the control is LVS_OWNERDATA, we need to send
3067 * only one LVN_ODSTATECHANGED notification.
3068 * See MSDN documentation for LVN_ITEMCHANGED.
3070 bOldChange = infoPtr->bDoChangeNotify;
3071 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3073 if (nFirst == -1) nFirst = nItem;
3075 item.state = LVIS_SELECTED;
3076 item.stateMask = LVIS_SELECTED;
3078 for (i = nFirst; i <= nLast; i++)
3079 LISTVIEW_SetItemState(infoPtr,i,&item);
3081 ZeroMemory(&nmlv, sizeof(nmlv));
3082 nmlv.iFrom = nFirst;
3083 nmlv.iTo = nLast;
3084 nmlv.uNewState = 0;
3085 nmlv.uOldState = item.state;
3087 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3088 if (!IsWindow(hwndSelf))
3089 return FALSE;
3090 infoPtr->bDoChangeNotify = bOldChange;
3091 return TRUE;
3095 /***
3096 * DESCRIPTION:
3097 * Sets a single group selection.
3099 * PARAMETER(S):
3100 * [I] infoPtr : valid pointer to the listview structure
3101 * [I] nItem : item index
3103 * RETURN:
3104 * None
3106 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3108 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3109 RANGES selection;
3110 LVITEMW item;
3111 ITERATOR i;
3112 BOOL bOldChange;
3114 if (!(selection = ranges_create(100))) return;
3116 item.state = LVIS_SELECTED;
3117 item.stateMask = LVIS_SELECTED;
3119 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3121 if (infoPtr->nSelectionMark == -1)
3123 infoPtr->nSelectionMark = nItem;
3124 ranges_additem(selection, nItem);
3126 else
3128 RANGE sel;
3130 sel.lower = min(infoPtr->nSelectionMark, nItem);
3131 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3132 ranges_add(selection, sel);
3135 else
3137 RECT rcItem, rcSel, rcSelMark;
3138 POINT ptItem;
3140 rcItem.left = LVIR_BOUNDS;
3141 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3142 rcSelMark.left = LVIR_BOUNDS;
3143 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3144 UnionRect(&rcSel, &rcItem, &rcSelMark);
3145 iterator_frameditems(&i, infoPtr, &rcSel);
3146 while(iterator_next(&i))
3148 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3149 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3151 iterator_destroy(&i);
3154 bOldChange = infoPtr->bDoChangeNotify;
3155 infoPtr->bDoChangeNotify = FALSE;
3157 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3160 iterator_rangesitems(&i, selection);
3161 while(iterator_next(&i))
3162 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3163 /* this will also destroy the selection */
3164 iterator_destroy(&i);
3166 infoPtr->bDoChangeNotify = bOldChange;
3168 LISTVIEW_SetItemFocus(infoPtr, nItem);
3171 /***
3172 * DESCRIPTION:
3173 * Sets a single selection.
3175 * PARAMETER(S):
3176 * [I] infoPtr : valid pointer to the listview structure
3177 * [I] nItem : item index
3179 * RETURN:
3180 * None
3182 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3184 LVITEMW lvItem;
3186 TRACE("nItem=%d\n", nItem);
3188 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3190 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3191 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3192 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3194 infoPtr->nSelectionMark = nItem;
3197 /***
3198 * DESCRIPTION:
3199 * Set selection(s) with keyboard.
3201 * PARAMETER(S):
3202 * [I] infoPtr : valid pointer to the listview structure
3203 * [I] nItem : item index
3205 * RETURN:
3206 * SUCCESS : TRUE (needs to be repainted)
3207 * FAILURE : FALSE (nothing has changed)
3209 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3211 /* FIXME: pass in the state */
3212 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3213 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3214 BOOL bResult = FALSE;
3216 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3217 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3219 if (infoPtr->dwStyle & LVS_SINGLESEL)
3221 bResult = TRUE;
3222 LISTVIEW_SetSelection(infoPtr, nItem);
3224 else
3226 if (wShift)
3228 bResult = TRUE;
3229 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3231 else if (wCtrl)
3233 LVITEMW lvItem;
3234 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3235 lvItem.stateMask = LVIS_SELECTED;
3236 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3238 if (lvItem.state & LVIS_SELECTED)
3239 infoPtr->nSelectionMark = nItem;
3241 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3243 else
3245 bResult = TRUE;
3246 LISTVIEW_SetSelection(infoPtr, nItem);
3249 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3252 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3253 return bResult;
3256 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3258 LVHITTESTINFO lvHitTestInfo;
3260 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3261 lvHitTestInfo.pt.x = pt.x;
3262 lvHitTestInfo.pt.y = pt.y;
3264 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3266 lpLVItem->mask = LVIF_PARAM;
3267 lpLVItem->iItem = lvHitTestInfo.iItem;
3268 lpLVItem->iSubItem = 0;
3270 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3273 /***
3274 * DESCRIPTION:
3275 * Called when the mouse is being actively tracked and has hovered for a specified
3276 * amount of time
3278 * PARAMETER(S):
3279 * [I] infoPtr : valid pointer to the listview structure
3280 * [I] fwKeys : key indicator
3281 * [I] x,y : mouse position
3283 * RETURN:
3284 * 0 if the message was processed, non-zero if there was an error
3286 * INFO:
3287 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3288 * over the item for a certain period of time.
3291 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3293 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3295 LVITEMW item;
3296 POINT pt;
3298 pt.x = x;
3299 pt.y = y;
3301 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3302 LISTVIEW_SetSelection(infoPtr, item.iItem);
3305 return 0;
3308 /***
3309 * DESCRIPTION:
3310 * Called whenever WM_MOUSEMOVE is received.
3312 * PARAMETER(S):
3313 * [I] infoPtr : valid pointer to the listview structure
3314 * [I] fwKeys : key indicator
3315 * [I] x,y : mouse position
3317 * RETURN:
3318 * 0 if the message is processed, non-zero if there was an error
3320 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3322 TRACKMOUSEEVENT trackinfo;
3324 if (!(fwKeys & MK_LBUTTON))
3325 infoPtr->bLButtonDown = FALSE;
3327 if (infoPtr->bLButtonDown)
3329 POINT tmp;
3330 RECT rect;
3331 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3332 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3334 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3335 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3336 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3337 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3339 tmp.x = x;
3340 tmp.y = y;
3342 if (!PtInRect(&rect, tmp))
3344 LVHITTESTINFO lvHitTestInfo;
3345 NMLISTVIEW nmlv;
3347 lvHitTestInfo.pt = infoPtr->ptClickPos;
3348 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3350 ZeroMemory(&nmlv, sizeof(nmlv));
3351 nmlv.iItem = lvHitTestInfo.iItem;
3352 nmlv.ptAction = infoPtr->ptClickPos;
3354 if (!infoPtr->bDragging)
3356 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3357 infoPtr->bDragging = TRUE;
3360 return 0;
3363 else
3364 infoPtr->bLButtonDown = FALSE;
3366 /* see if we are supposed to be tracking mouse hovering */
3367 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3368 /* fill in the trackinfo struct */
3369 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3370 trackinfo.dwFlags = TME_QUERY;
3371 trackinfo.hwndTrack = infoPtr->hwndSelf;
3372 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3374 /* see if we are already tracking this hwnd */
3375 _TrackMouseEvent(&trackinfo);
3377 if(!(trackinfo.dwFlags & TME_HOVER)) {
3378 trackinfo.dwFlags = TME_HOVER;
3380 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3381 _TrackMouseEvent(&trackinfo);
3385 return 0;
3389 /***
3390 * Tests whether the item is assignable to a list with style lStyle
3392 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3394 if ( (lpLVItem->mask & LVIF_TEXT) &&
3395 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3396 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3398 return TRUE;
3402 /***
3403 * DESCRIPTION:
3404 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3406 * PARAMETER(S):
3407 * [I] infoPtr : valid pointer to the listview structure
3408 * [I] lpLVItem : valid pointer to new item attributes
3409 * [I] isNew : the item being set is being inserted
3410 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3411 * [O] bChanged : will be set to TRUE if the item really changed
3413 * RETURN:
3414 * SUCCESS : TRUE
3415 * FAILURE : FALSE
3417 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3419 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3420 ITEM_INFO *lpItem;
3421 NMLISTVIEW nmlv;
3422 UINT uChanged = 0;
3423 LVITEMW item;
3425 TRACE("()\n");
3427 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3429 if (lpLVItem->mask == 0) return TRUE;
3431 if (infoPtr->dwStyle & LVS_OWNERDATA)
3433 /* a virtual listview only stores selection and focus */
3434 if (lpLVItem->mask & ~LVIF_STATE)
3435 return FALSE;
3436 lpItem = NULL;
3438 else
3440 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3441 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3442 assert (lpItem);
3445 /* we need to get the lParam and state of the item */
3446 item.iItem = lpLVItem->iItem;
3447 item.iSubItem = lpLVItem->iSubItem;
3448 item.mask = LVIF_STATE | LVIF_PARAM;
3449 item.stateMask = ~0;
3450 item.state = 0;
3451 item.lParam = 0;
3452 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3454 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3455 /* determine what fields will change */
3456 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3457 uChanged |= LVIF_STATE;
3459 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3460 uChanged |= LVIF_IMAGE;
3462 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3463 uChanged |= LVIF_PARAM;
3465 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3466 uChanged |= LVIF_INDENT;
3468 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3469 uChanged |= LVIF_TEXT;
3471 TRACE("uChanged=0x%x\n", uChanged);
3472 if (!uChanged) return TRUE;
3473 *bChanged = TRUE;
3475 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3476 nmlv.iItem = lpLVItem->iItem;
3477 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3478 nmlv.uOldState = item.state;
3479 nmlv.uChanged = uChanged;
3480 nmlv.lParam = item.lParam;
3482 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3483 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3484 /* are enabled */
3485 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3487 HWND hwndSelf = infoPtr->hwndSelf;
3489 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3490 return FALSE;
3491 if (!IsWindow(hwndSelf))
3492 return FALSE;
3495 /* copy information */
3496 if (lpLVItem->mask & LVIF_TEXT)
3497 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3499 if (lpLVItem->mask & LVIF_IMAGE)
3500 lpItem->hdr.iImage = lpLVItem->iImage;
3502 if (lpLVItem->mask & LVIF_PARAM)
3503 lpItem->lParam = lpLVItem->lParam;
3505 if (lpLVItem->mask & LVIF_INDENT)
3506 lpItem->iIndent = lpLVItem->iIndent;
3508 if (uChanged & LVIF_STATE)
3510 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3512 lpItem->state &= ~lpLVItem->stateMask;
3513 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3515 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3517 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3518 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3520 else if (lpLVItem->stateMask & LVIS_SELECTED)
3521 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3523 /* if we are asked to change focus, and we manage it, do it */
3524 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3526 if (lpLVItem->state & LVIS_FOCUSED)
3528 LISTVIEW_SetItemFocus(infoPtr, -1);
3529 infoPtr->nFocusedItem = lpLVItem->iItem;
3530 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3532 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3533 infoPtr->nFocusedItem = -1;
3537 /* if we're inserting the item, we're done */
3538 if (isNew) return TRUE;
3540 /* send LVN_ITEMCHANGED notification */
3541 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3542 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3544 return TRUE;
3547 /***
3548 * DESCRIPTION:
3549 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3551 * PARAMETER(S):
3552 * [I] infoPtr : valid pointer to the listview structure
3553 * [I] lpLVItem : valid pointer to new subitem attributes
3554 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3555 * [O] bChanged : will be set to TRUE if the item really changed
3557 * RETURN:
3558 * SUCCESS : TRUE
3559 * FAILURE : FALSE
3561 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3563 HDPA hdpaSubItems;
3564 SUBITEM_INFO *lpSubItem;
3566 /* we do not support subitems for virtual listviews */
3567 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3569 /* set subitem only if column is present */
3570 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3572 /* First do some sanity checks */
3573 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3574 particularly useful. We currently do not actually do anything with
3575 the flag on subitems.
3577 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3578 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3580 /* get the subitem structure, and create it if not there */
3581 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3582 assert (hdpaSubItems);
3584 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3585 if (!lpSubItem)
3587 SUBITEM_INFO *tmpSubItem;
3588 INT i;
3590 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3591 if (!lpSubItem) return FALSE;
3592 /* we could binary search here, if need be...*/
3593 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3595 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3596 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3598 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3600 Free(lpSubItem);
3601 return FALSE;
3603 lpSubItem->iSubItem = lpLVItem->iSubItem;
3604 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3605 *bChanged = TRUE;
3608 if (lpLVItem->mask & LVIF_IMAGE)
3609 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3611 lpSubItem->hdr.iImage = lpLVItem->iImage;
3612 *bChanged = TRUE;
3615 if (lpLVItem->mask & LVIF_TEXT)
3616 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3618 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3619 *bChanged = TRUE;
3622 return TRUE;
3625 /***
3626 * DESCRIPTION:
3627 * Sets item attributes.
3629 * PARAMETER(S):
3630 * [I] infoPtr : valid pointer to the listview structure
3631 * [I] lpLVItem : new item attributes
3632 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3634 * RETURN:
3635 * SUCCESS : TRUE
3636 * FAILURE : FALSE
3638 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3640 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3641 HWND hwndSelf = infoPtr->hwndSelf;
3642 LPWSTR pszText = NULL;
3643 BOOL bResult, bChanged = FALSE;
3645 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3647 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3648 return FALSE;
3650 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3651 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3653 pszText = lpLVItem->pszText;
3654 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3657 /* actually set the fields */
3658 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3660 if (lpLVItem->iSubItem)
3661 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3662 else
3663 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3664 if (!IsWindow(hwndSelf))
3665 return FALSE;
3667 /* redraw item, if necessary */
3668 if (bChanged && !infoPtr->bIsDrawing)
3670 /* this little optimization eliminates some nasty flicker */
3671 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3672 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3673 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3674 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3675 else
3676 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3678 /* restore text */
3679 if (pszText)
3681 textfreeT(lpLVItem->pszText, isW);
3682 lpLVItem->pszText = pszText;
3685 return bResult;
3688 /***
3689 * DESCRIPTION:
3690 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3692 * PARAMETER(S):
3693 * [I] infoPtr : valid pointer to the listview structure
3695 * RETURN:
3696 * item index
3698 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3700 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3701 INT nItem = 0;
3702 SCROLLINFO scrollInfo;
3704 scrollInfo.cbSize = sizeof(SCROLLINFO);
3705 scrollInfo.fMask = SIF_POS;
3707 if (uView == LVS_LIST)
3709 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3710 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3712 else if (uView == LVS_REPORT)
3714 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3715 nItem = scrollInfo.nPos;
3717 else
3719 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3720 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3723 TRACE("nItem=%d\n", nItem);
3725 return nItem;
3729 /***
3730 * DESCRIPTION:
3731 * Erases the background of the given rectangle
3733 * PARAMETER(S):
3734 * [I] infoPtr : valid pointer to the listview structure
3735 * [I] hdc : device context handle
3736 * [I] lprcBox : clipping rectangle
3738 * RETURN:
3739 * Success: TRUE
3740 * Failure: FALSE
3742 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3744 if (!infoPtr->hBkBrush) return FALSE;
3746 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3748 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3751 /***
3752 * DESCRIPTION:
3753 * Draws an item.
3755 * PARAMETER(S):
3756 * [I] infoPtr : valid pointer to the listview structure
3757 * [I] hdc : device context handle
3758 * [I] nItem : item index
3759 * [I] nSubItem : subitem index
3760 * [I] pos : item position in client coordinates
3761 * [I] cdmode : custom draw mode
3763 * RETURN:
3764 * Success: TRUE
3765 * Failure: FALSE
3767 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3769 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3770 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3771 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3772 DWORD cdsubitemmode = CDRF_DODEFAULT;
3773 LPRECT lprcFocus;
3774 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3775 NMLVCUSTOMDRAW nmlvcd;
3776 HIMAGELIST himl;
3777 LVITEMW lvItem;
3778 HFONT hOldFont;
3780 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3782 /* get information needed for drawing the item */
3783 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3784 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3785 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3786 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3787 lvItem.iItem = nItem;
3788 lvItem.iSubItem = nSubItem;
3789 lvItem.state = 0;
3790 lvItem.lParam = 0;
3791 lvItem.cchTextMax = DISP_TEXT_SIZE;
3792 lvItem.pszText = szDispText;
3793 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3794 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3795 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3796 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3797 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3799 /* now check if we need to update the focus rectangle */
3800 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3802 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3803 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3804 OffsetRect(&rcBox, pos.x, pos.y);
3805 OffsetRect(&rcSelect, pos.x, pos.y);
3806 OffsetRect(&rcIcon, pos.x, pos.y);
3807 OffsetRect(&rcStateIcon, pos.x, pos.y);
3808 OffsetRect(&rcLabel, pos.x, pos.y);
3809 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3810 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3811 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3813 /* fill in the custom draw structure */
3814 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3816 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3817 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3818 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3819 if (cdmode & CDRF_NOTIFYITEMDRAW)
3820 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3821 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3822 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3823 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3824 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3826 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3827 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3829 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3830 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3831 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3832 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3834 /* in full row select, subitems, will just use main item's colors */
3835 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3836 nmlvcd.clrTextBk = CLR_NONE;
3838 /* state icons */
3839 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3841 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3842 if (uStateImage)
3844 TRACE("uStateImage=%d\n", uStateImage);
3845 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3846 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3850 /* small icons */
3851 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3852 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3854 TRACE("iImage=%d\n", lvItem.iImage);
3855 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3856 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3857 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3860 /* Don't bother painting item being edited */
3861 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3863 /* FIXME: temporary hack */
3864 rcSelect.left = rcLabel.left;
3866 /* draw the selection background, if we're drawing the main item */
3867 if (nSubItem == 0)
3869 /* in icon mode, the label rect is really what we want to draw the
3870 * background for */
3871 if (uView == LVS_ICON)
3872 rcSelect = rcLabel;
3874 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3875 rcSelect.right = rcBox.right;
3877 if (nmlvcd.clrTextBk != CLR_NONE)
3878 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3879 if(lprcFocus) *lprcFocus = rcSelect;
3882 /* figure out the text drawing flags */
3883 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3884 if (uView == LVS_ICON)
3885 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3886 else if (nSubItem)
3888 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3890 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3891 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3892 default: uFormat |= DT_LEFT;
3895 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3897 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3898 else rcLabel.left += LABEL_HOR_PADDING;
3900 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3902 /* for GRIDLINES reduce the bottom so the text formats correctly */
3903 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3904 rcLabel.bottom--;
3906 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3908 postpaint:
3909 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3910 notify_postpaint(infoPtr, &nmlvcd);
3911 if (cdsubitemmode & CDRF_NEWFONT)
3912 SelectObject(hdc, hOldFont);
3913 return TRUE;
3916 /***
3917 * DESCRIPTION:
3918 * Draws listview items when in owner draw mode.
3920 * PARAMETER(S):
3921 * [I] infoPtr : valid pointer to the listview structure
3922 * [I] hdc : device context handle
3924 * RETURN:
3925 * None
3927 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3929 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3930 DWORD cditemmode = CDRF_DODEFAULT;
3931 NMLVCUSTOMDRAW nmlvcd;
3932 POINT Origin, Position;
3933 DRAWITEMSTRUCT dis;
3934 LVITEMW item;
3936 TRACE("()\n");
3938 ZeroMemory(&dis, sizeof(dis));
3940 /* Get scroll info once before loop */
3941 LISTVIEW_GetOrigin(infoPtr, &Origin);
3943 /* iterate through the invalidated rows */
3944 while(iterator_next(i))
3946 item.iItem = i->nItem;
3947 item.iSubItem = 0;
3948 item.mask = LVIF_PARAM | LVIF_STATE;
3949 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3950 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3952 dis.CtlType = ODT_LISTVIEW;
3953 dis.CtlID = uID;
3954 dis.itemID = item.iItem;
3955 dis.itemAction = ODA_DRAWENTIRE;
3956 dis.itemState = 0;
3957 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3958 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3959 dis.hwndItem = infoPtr->hwndSelf;
3960 dis.hDC = hdc;
3961 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3962 dis.rcItem.left = Position.x + Origin.x;
3963 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3964 dis.rcItem.top = Position.y + Origin.y;
3965 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3966 dis.itemData = item.lParam;
3968 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3971 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3972 * structure for the rest. of the paint cycle
3974 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3975 if (cdmode & CDRF_NOTIFYITEMDRAW)
3976 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3978 if (!(cditemmode & CDRF_SKIPDEFAULT))
3980 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3981 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3984 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3985 notify_postpaint(infoPtr, &nmlvcd);
3989 /***
3990 * DESCRIPTION:
3991 * Draws listview items when in report display mode.
3993 * PARAMETER(S):
3994 * [I] infoPtr : valid pointer to the listview structure
3995 * [I] hdc : device context handle
3996 * [I] cdmode : custom draw mode
3998 * RETURN:
3999 * None
4001 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4003 INT rgntype;
4004 RECT rcClip, rcItem;
4005 POINT Origin, Position;
4006 RANGE colRange;
4007 ITERATOR j;
4009 TRACE("()\n");
4011 /* figure out what to draw */
4012 rgntype = GetClipBox(hdc, &rcClip);
4013 if (rgntype == NULLREGION) return;
4015 /* Get scroll info once before loop */
4016 LISTVIEW_GetOrigin(infoPtr, &Origin);
4018 /* narrow down the columns we need to paint */
4019 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4021 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4022 if (rcItem.right + Origin.x >= rcClip.left) break;
4024 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4026 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4027 if (rcItem.left + Origin.x < rcClip.right) break;
4029 iterator_rangeitems(&j, colRange);
4031 /* in full row select, we _have_ to draw the main item */
4032 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4033 j.nSpecial = 0;
4035 /* iterate through the invalidated rows */
4036 while(iterator_next(i))
4038 /* iterate through the invalidated columns */
4039 while(iterator_next(&j))
4041 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4042 Position.x += Origin.x;
4043 Position.y += Origin.y;
4045 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4047 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4048 rcItem.top = 0;
4049 rcItem.bottom = infoPtr->nItemHeight;
4050 OffsetRect(&rcItem, Position.x, Position.y);
4051 if (!RectVisible(hdc, &rcItem)) continue;
4054 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4057 iterator_destroy(&j);
4060 /***
4061 * DESCRIPTION:
4062 * Draws the gridlines if necessary when in report display mode.
4064 * PARAMETER(S):
4065 * [I] infoPtr : valid pointer to the listview structure
4066 * [I] hdc : device context handle
4068 * RETURN:
4069 * None
4071 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4073 INT rgntype;
4074 INT y, itemheight;
4075 HPEN hPen, hOldPen;
4076 RECT rcClip, rcItem;
4077 POINT Origin;
4078 RANGE colRange;
4079 ITERATOR j;
4081 TRACE("()\n");
4083 /* figure out what to draw */
4084 rgntype = GetClipBox(hdc, &rcClip);
4085 if (rgntype == NULLREGION) return;
4087 /* Get scroll info once before loop */
4088 LISTVIEW_GetOrigin(infoPtr, &Origin);
4090 /* narrow down the columns we need to paint */
4091 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4093 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4094 if (rcItem.right + Origin.x >= rcClip.left) break;
4096 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4098 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4099 if (rcItem.left + Origin.x < rcClip.right) break;
4101 iterator_rangeitems(&j, colRange);
4103 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4105 hOldPen = SelectObject ( hdc, hPen );
4107 /* draw the vertical lines for the columns */
4108 iterator_rangeitems(&j, colRange);
4109 while(iterator_next(&j))
4111 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4112 if (rcItem.left == 0) continue; /* skip first column */
4113 rcItem.left += Origin.x;
4114 rcItem.right += Origin.x;
4115 rcItem.top = infoPtr->rcList.top;
4116 rcItem.bottom = infoPtr->rcList.bottom;
4117 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4118 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4119 LineTo (hdc, rcItem.left, rcItem.bottom);
4121 iterator_destroy(&j);
4123 /* draw the horizontial lines for the rows */
4124 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4125 rcItem.left = infoPtr->rcList.left + Origin.x;
4126 rcItem.right = infoPtr->rcList.right + Origin.x;
4127 rcItem.bottom = rcItem.top = Origin.y - 1;
4128 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4129 LineTo(hdc, rcItem.right, rcItem.top);
4130 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4132 rcItem.bottom = rcItem.top = y;
4133 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4134 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4135 LineTo (hdc, rcItem.right, rcItem.top);
4138 SelectObject( hdc, hOldPen );
4139 DeleteObject( hPen );
4143 /***
4144 * DESCRIPTION:
4145 * Draws listview items when in list display mode.
4147 * PARAMETER(S):
4148 * [I] infoPtr : valid pointer to the listview structure
4149 * [I] hdc : device context handle
4150 * [I] cdmode : custom draw mode
4152 * RETURN:
4153 * None
4155 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4157 POINT Origin, Position;
4159 /* Get scroll info once before loop */
4160 LISTVIEW_GetOrigin(infoPtr, &Origin);
4162 while(iterator_prev(i))
4164 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4165 Position.x += Origin.x;
4166 Position.y += Origin.y;
4168 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4173 /***
4174 * DESCRIPTION:
4175 * Draws listview items.
4177 * PARAMETER(S):
4178 * [I] infoPtr : valid pointer to the listview structure
4179 * [I] hdc : device context handle
4180 * [I] prcErase : rect to be erased before refresh (may be NULL)
4182 * RETURN:
4183 * NoneX
4185 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4187 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4188 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4189 NMLVCUSTOMDRAW nmlvcd;
4190 HFONT hOldFont = 0;
4191 DWORD cdmode;
4192 INT oldBkMode = 0;
4193 RECT rcClient;
4194 ITERATOR i;
4195 HDC hdcOrig = hdc;
4196 HBITMAP hbmp = NULL;
4198 LISTVIEW_DUMP(infoPtr);
4200 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4201 TRACE("double buffering\n");
4203 hdc = CreateCompatibleDC(hdcOrig);
4204 if (!hdc) {
4205 ERR("Failed to create DC for backbuffer\n");
4206 return;
4208 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4209 infoPtr->rcList.bottom);
4210 if (!hbmp) {
4211 ERR("Failed to create bitmap for backbuffer\n");
4212 DeleteDC(hdc);
4213 return;
4216 SelectObject(hdc, hbmp);
4217 SelectObject(hdc, infoPtr->hFont);
4218 } else {
4219 /* Save dc values we're gonna trash while drawing
4220 * FIXME: Should be done in LISTVIEW_DrawItem() */
4221 hOldFont = SelectObject(hdc, infoPtr->hFont);
4222 oldBkMode = GetBkMode(hdc);
4223 oldBkColor = GetBkColor(hdc);
4224 oldTextColor = GetTextColor(hdc);
4227 infoPtr->bIsDrawing = TRUE;
4229 if (prcErase) {
4230 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4231 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4232 /* If no erasing was done (usually because RedrawWindow was called
4233 * with RDW_INVALIDATE only) we need to copy the old contents into
4234 * the backbuffer before continuing. */
4235 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4236 infoPtr->rcList.right - infoPtr->rcList.left,
4237 infoPtr->rcList.bottom - infoPtr->rcList.top,
4238 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4241 /* FIXME: Shouldn't need to do this */
4242 oldClrTextBk = infoPtr->clrTextBk;
4243 oldClrText = infoPtr->clrText;
4245 infoPtr->cditemmode = CDRF_DODEFAULT;
4247 GetClientRect(infoPtr->hwndSelf, &rcClient);
4248 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4249 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4250 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4251 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4253 /* Use these colors to draw the items */
4254 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4255 infoPtr->clrText = nmlvcd.clrText;
4257 /* nothing to draw */
4258 if(infoPtr->nItemCount == 0) goto enddraw;
4260 /* figure out what we need to draw */
4261 iterator_visibleitems(&i, infoPtr, hdc);
4263 /* send cache hint notification */
4264 if (infoPtr->dwStyle & LVS_OWNERDATA)
4266 RANGE range = iterator_range(&i);
4267 NMLVCACHEHINT nmlv;
4269 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4270 nmlv.iFrom = range.lower;
4271 nmlv.iTo = range.upper - 1;
4272 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4275 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4276 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4277 else
4279 if (uView == LVS_REPORT)
4280 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4281 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4282 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4284 /* if we have a focus rect, draw it */
4285 if (infoPtr->bFocus)
4286 DrawFocusRect(hdc, &infoPtr->rcFocus);
4288 iterator_destroy(&i);
4290 enddraw:
4291 /* For LVS_EX_GRIDLINES go and draw lines */
4292 /* This includes the case where there were *no* items */
4293 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4294 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4295 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4297 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4298 notify_postpaint(infoPtr, &nmlvcd);
4300 infoPtr->clrTextBk = oldClrTextBk;
4301 infoPtr->clrText = oldClrText;
4303 if(hbmp) {
4304 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4305 infoPtr->rcList.right - infoPtr->rcList.left,
4306 infoPtr->rcList.bottom - infoPtr->rcList.top,
4307 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4309 DeleteObject(hbmp);
4310 DeleteDC(hdc);
4311 } else {
4312 SelectObject(hdc, hOldFont);
4313 SetBkMode(hdc, oldBkMode);
4314 SetBkColor(hdc, oldBkColor);
4315 SetTextColor(hdc, oldTextColor);
4318 infoPtr->bIsDrawing = FALSE;
4322 /***
4323 * DESCRIPTION:
4324 * Calculates the approximate width and height of a given number of items.
4326 * PARAMETER(S):
4327 * [I] infoPtr : valid pointer to the listview structure
4328 * [I] nItemCount : number of items
4329 * [I] wWidth : width
4330 * [I] wHeight : height
4332 * RETURN:
4333 * Returns a DWORD. The width in the low word and the height in high word.
4335 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4336 WORD wWidth, WORD wHeight)
4338 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4339 INT nItemCountPerColumn = 1;
4340 INT nColumnCount = 0;
4341 DWORD dwViewRect = 0;
4343 if (nItemCount == -1)
4344 nItemCount = infoPtr->nItemCount;
4346 if (uView == LVS_LIST)
4348 if (wHeight == 0xFFFF)
4350 /* use current height */
4351 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4354 if (wHeight < infoPtr->nItemHeight)
4355 wHeight = infoPtr->nItemHeight;
4357 if (nItemCount > 0)
4359 if (infoPtr->nItemHeight > 0)
4361 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4362 if (nItemCountPerColumn == 0)
4363 nItemCountPerColumn = 1;
4365 if (nItemCount % nItemCountPerColumn != 0)
4366 nColumnCount = nItemCount / nItemCountPerColumn;
4367 else
4368 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4372 /* Microsoft padding magic */
4373 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4374 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4376 dwViewRect = MAKELONG(wWidth, wHeight);
4378 else if (uView == LVS_REPORT)
4380 RECT rcBox;
4382 if (infoPtr->nItemCount > 0)
4384 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4385 wWidth = rcBox.right - rcBox.left;
4386 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4388 else
4390 /* use current height and width */
4391 if (wHeight == 0xffff)
4392 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4393 if (wWidth == 0xffff)
4394 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4397 dwViewRect = MAKELONG(wWidth, wHeight);
4399 else if (uView == LVS_SMALLICON)
4400 FIXME("uView == LVS_SMALLICON: not implemented\n");
4401 else if (uView == LVS_ICON)
4402 FIXME("uView == LVS_ICON: not implemented\n");
4404 return dwViewRect;
4408 /***
4409 * DESCRIPTION:
4410 * Create a drag image list for the specified item.
4412 * PARAMETER(S):
4413 * [I] infoPtr : valid pointer to the listview structure
4414 * [I] iItem : index of item
4415 * [O] lppt : Upperr-left corner of the image
4417 * RETURN:
4418 * Returns a handle to the image list if successful, NULL otherwise.
4420 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4422 RECT rcItem;
4423 SIZE size;
4424 POINT pos;
4425 HDC hdc, hdcOrig;
4426 HBITMAP hbmp, hOldbmp;
4427 HIMAGELIST dragList = 0;
4428 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4430 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4431 return 0;
4433 rcItem.left = LVIR_BOUNDS;
4434 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4435 return 0;
4437 lppt->x = rcItem.left;
4438 lppt->y = rcItem.top;
4440 size.cx = rcItem.right - rcItem.left;
4441 size.cy = rcItem.bottom - rcItem.top;
4443 hdcOrig = GetDC(infoPtr->hwndSelf);
4444 hdc = CreateCompatibleDC(hdcOrig);
4445 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4446 hOldbmp = SelectObject(hdc, hbmp);
4448 rcItem.left = rcItem.top = 0;
4449 rcItem.right = size.cx;
4450 rcItem.bottom = size.cy;
4451 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4453 pos.x = pos.y = 0;
4454 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4456 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4457 SelectObject(hdc, hOldbmp);
4458 ImageList_Add(dragList, hbmp, 0);
4460 else
4461 SelectObject(hdc, hOldbmp);
4463 DeleteObject(hbmp);
4464 DeleteDC(hdc);
4465 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4467 TRACE("ret=%p\n", dragList);
4469 return dragList;
4473 /***
4474 * DESCRIPTION:
4475 * Removes all listview items and subitems.
4477 * PARAMETER(S):
4478 * [I] infoPtr : valid pointer to the listview structure
4480 * RETURN:
4481 * SUCCESS : TRUE
4482 * FAILURE : FALSE
4484 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4486 NMLISTVIEW nmlv;
4487 HDPA hdpaSubItems = NULL;
4488 BOOL bSuppress;
4489 ITEMHDR *hdrItem;
4490 INT i, j;
4492 TRACE("()\n");
4494 /* we do it directly, to avoid notifications */
4495 ranges_clear(infoPtr->selectionRanges);
4496 infoPtr->nSelectionMark = -1;
4497 infoPtr->nFocusedItem = -1;
4498 SetRectEmpty(&infoPtr->rcFocus);
4499 /* But we are supposed to leave nHotItem as is! */
4502 /* send LVN_DELETEALLITEMS notification */
4503 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4504 nmlv.iItem = -1;
4505 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4507 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4509 /* send LVN_DELETEITEM notification, if not suppressed */
4510 if (!bSuppress) notify_deleteitem(infoPtr, i);
4511 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4513 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4514 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4516 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4517 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4518 Free(hdrItem);
4520 DPA_Destroy(hdpaSubItems);
4521 DPA_DeletePtr(infoPtr->hdpaItems, i);
4523 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4524 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4525 infoPtr->nItemCount --;
4528 if (!destroy)
4530 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4531 LISTVIEW_UpdateScroll(infoPtr);
4533 LISTVIEW_InvalidateList(infoPtr);
4535 return TRUE;
4538 /***
4539 * DESCRIPTION:
4540 * Scrolls, and updates the columns, when a column is changing width.
4542 * PARAMETER(S):
4543 * [I] infoPtr : valid pointer to the listview structure
4544 * [I] nColumn : column to scroll
4545 * [I] dx : amount of scroll, in pixels
4547 * RETURN:
4548 * None.
4550 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4552 COLUMN_INFO *lpColumnInfo;
4553 RECT rcOld, rcCol;
4554 POINT ptOrigin;
4555 INT nCol;
4557 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4558 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4559 rcCol = lpColumnInfo->rcHeader;
4560 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4561 rcCol.left = rcCol.right;
4563 /* adjust the other columns */
4564 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4566 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4567 lpColumnInfo->rcHeader.left += dx;
4568 lpColumnInfo->rcHeader.right += dx;
4571 /* do not update screen if not in report mode */
4572 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4574 /* if we have a focus, we must first erase the focus rect */
4575 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4577 /* Need to reset the item width when inserting a new column */
4578 infoPtr->nItemWidth += dx;
4580 LISTVIEW_UpdateScroll(infoPtr);
4581 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4583 /* scroll to cover the deleted column, and invalidate for redraw */
4584 rcOld = infoPtr->rcList;
4585 rcOld.left = ptOrigin.x + rcCol.left + dx;
4586 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4588 /* we can restore focus now */
4589 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4592 /***
4593 * DESCRIPTION:
4594 * Removes a column from the listview control.
4596 * PARAMETER(S):
4597 * [I] infoPtr : valid pointer to the listview structure
4598 * [I] nColumn : column index
4600 * RETURN:
4601 * SUCCESS : TRUE
4602 * FAILURE : FALSE
4604 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4606 RECT rcCol;
4608 TRACE("nColumn=%d\n", nColumn);
4610 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4611 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4613 /* While the MSDN specifically says that column zero should not be deleted,
4614 what actually happens is that the column itself is deleted but no items or subitems
4615 are removed.
4618 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4620 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4621 return FALSE;
4623 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4624 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4626 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4628 SUBITEM_INFO *lpSubItem, *lpDelItem;
4629 HDPA hdpaSubItems;
4630 INT nItem, nSubItem, i;
4632 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4634 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4635 nSubItem = 0;
4636 lpDelItem = 0;
4637 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4639 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4640 if (lpSubItem->iSubItem == nColumn)
4642 nSubItem = i;
4643 lpDelItem = lpSubItem;
4645 else if (lpSubItem->iSubItem > nColumn)
4647 lpSubItem->iSubItem--;
4651 /* if we found our subitem, zapp it */
4652 if (nSubItem > 0)
4654 /* free string */
4655 if (is_textW(lpDelItem->hdr.pszText))
4656 Free(lpDelItem->hdr.pszText);
4658 /* free item */
4659 Free(lpDelItem);
4661 /* free dpa memory */
4662 DPA_DeletePtr(hdpaSubItems, nSubItem);
4667 /* update the other column info */
4668 LISTVIEW_UpdateItemSize(infoPtr);
4669 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4670 LISTVIEW_InvalidateList(infoPtr);
4671 else
4672 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4674 return TRUE;
4677 /***
4678 * DESCRIPTION:
4679 * Invalidates the listview after an item's insertion or deletion.
4681 * PARAMETER(S):
4682 * [I] infoPtr : valid pointer to the listview structure
4683 * [I] nItem : item index
4684 * [I] dir : -1 if deleting, 1 if inserting
4686 * RETURN:
4687 * None
4689 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4691 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4692 INT nPerCol, nItemCol, nItemRow;
4693 RECT rcScroll;
4694 POINT Origin;
4696 /* if we don't refresh, what's the point of scrolling? */
4697 if (!is_redrawing(infoPtr)) return;
4699 assert (abs(dir) == 1);
4701 /* arrange icons if autoarrange is on */
4702 if (is_autoarrange(infoPtr))
4704 BOOL arrange = TRUE;
4705 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4706 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4707 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4710 /* scrollbars need updating */
4711 LISTVIEW_UpdateScroll(infoPtr);
4713 /* figure out the item's position */
4714 if (uView == LVS_REPORT)
4715 nPerCol = infoPtr->nItemCount + 1;
4716 else if (uView == LVS_LIST)
4717 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4718 else /* LVS_ICON, or LVS_SMALLICON */
4719 return;
4721 nItemCol = nItem / nPerCol;
4722 nItemRow = nItem % nPerCol;
4723 LISTVIEW_GetOrigin(infoPtr, &Origin);
4725 /* move the items below up a slot */
4726 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4727 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4728 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4729 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4730 OffsetRect(&rcScroll, Origin.x, Origin.y);
4731 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4732 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4734 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4735 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4736 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4739 /* report has only that column, so we're done */
4740 if (uView == LVS_REPORT) return;
4742 /* now for LISTs, we have to deal with the columns to the right */
4743 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4744 rcScroll.top = 0;
4745 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4746 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4747 OffsetRect(&rcScroll, Origin.x, Origin.y);
4748 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4749 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4750 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4753 /***
4754 * DESCRIPTION:
4755 * Removes an item from the listview control.
4757 * PARAMETER(S):
4758 * [I] infoPtr : valid pointer to the listview structure
4759 * [I] nItem : item index
4761 * RETURN:
4762 * SUCCESS : TRUE
4763 * FAILURE : FALSE
4765 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4767 LVITEMW item;
4768 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4769 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4771 TRACE("(nItem=%d)\n", nItem);
4773 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4775 /* remove selection, and focus */
4776 item.state = 0;
4777 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4778 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4780 /* send LVN_DELETEITEM notification. */
4781 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4783 /* we need to do this here, because we'll be deleting stuff */
4784 if (is_icon)
4785 LISTVIEW_InvalidateItem(infoPtr, nItem);
4787 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4789 HDPA hdpaSubItems;
4790 ITEMHDR *hdrItem;
4791 INT i;
4793 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4794 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4796 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4797 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4798 Free(hdrItem);
4800 DPA_Destroy(hdpaSubItems);
4803 if (is_icon)
4805 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4806 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4809 infoPtr->nItemCount--;
4810 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4812 /* now is the invalidation fun */
4813 if (!is_icon)
4814 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4815 return TRUE;
4819 /***
4820 * DESCRIPTION:
4821 * Callback implementation for editlabel control
4823 * PARAMETER(S):
4824 * [I] infoPtr : valid pointer to the listview structure
4825 * [I] pszText : modified text
4826 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4828 * RETURN:
4829 * SUCCESS : TRUE
4830 * FAILURE : FALSE
4832 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4834 HWND hwndSelf = infoPtr->hwndSelf;
4835 NMLVDISPINFOW dispInfo;
4836 BOOL bSame;
4838 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4840 ZeroMemory(&dispInfo, sizeof(dispInfo));
4841 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4842 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4843 dispInfo.item.iSubItem = 0;
4844 dispInfo.item.stateMask = ~0;
4845 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4847 /* Don't bother continuing if text has not changed */
4848 if (isW)
4849 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4850 else
4852 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4853 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4854 textfreeT(tmp, FALSE);
4856 if (bSame) return TRUE;
4858 /* add the text from the edit in */
4859 dispInfo.item.mask |= LVIF_TEXT;
4860 dispInfo.item.pszText = pszText;
4861 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4863 /* Do we need to update the Item Text */
4864 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4865 if (!IsWindow(hwndSelf))
4866 return FALSE;
4867 if (!pszText) return TRUE;
4869 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4871 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4872 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4873 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4875 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4876 return TRUE;
4880 ZeroMemory(&dispInfo, sizeof(dispInfo));
4881 dispInfo.item.mask = LVIF_TEXT;
4882 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4883 dispInfo.item.iSubItem = 0;
4884 dispInfo.item.pszText = pszText;
4885 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4886 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4889 /***
4890 * DESCRIPTION:
4891 * Begin in place editing of specified list view item
4893 * PARAMETER(S):
4894 * [I] infoPtr : valid pointer to the listview structure
4895 * [I] nItem : item index
4896 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4898 * RETURN:
4899 * SUCCESS : TRUE
4900 * FAILURE : FALSE
4902 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4904 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4905 NMLVDISPINFOW dispInfo;
4906 RECT rect;
4907 HWND hwndSelf = infoPtr->hwndSelf;
4909 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4911 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4912 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4914 infoPtr->nEditLabelItem = nItem;
4916 /* Is the EditBox still there, if so remove it */
4917 if(infoPtr->hwndEdit != 0)
4919 SetFocus(infoPtr->hwndSelf);
4920 infoPtr->hwndEdit = 0;
4923 LISTVIEW_SetSelection(infoPtr, nItem);
4924 LISTVIEW_SetItemFocus(infoPtr, nItem);
4925 LISTVIEW_InvalidateItem(infoPtr, nItem);
4927 rect.left = LVIR_LABEL;
4928 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4930 ZeroMemory(&dispInfo, sizeof(dispInfo));
4931 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4932 dispInfo.item.iItem = nItem;
4933 dispInfo.item.iSubItem = 0;
4934 dispInfo.item.stateMask = ~0;
4935 dispInfo.item.pszText = szDispText;
4936 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4937 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4939 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4940 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4941 if (!infoPtr->hwndEdit) return 0;
4943 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4945 if (!IsWindow(hwndSelf))
4946 return 0;
4947 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4948 infoPtr->hwndEdit = 0;
4949 return 0;
4952 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4953 SetFocus(infoPtr->hwndEdit);
4954 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4955 return infoPtr->hwndEdit;
4959 /***
4960 * DESCRIPTION:
4961 * Ensures the specified item is visible, scrolling into view if necessary.
4963 * PARAMETER(S):
4964 * [I] infoPtr : valid pointer to the listview structure
4965 * [I] nItem : item index
4966 * [I] bPartial : partially or entirely visible
4968 * RETURN:
4969 * SUCCESS : TRUE
4970 * FAILURE : FALSE
4972 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4974 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4975 INT nScrollPosHeight = 0;
4976 INT nScrollPosWidth = 0;
4977 INT nHorzAdjust = 0;
4978 INT nVertAdjust = 0;
4979 INT nHorzDiff = 0;
4980 INT nVertDiff = 0;
4981 RECT rcItem, rcTemp;
4983 rcItem.left = LVIR_BOUNDS;
4984 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4986 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4988 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4990 /* scroll left/right, but in LVS_REPORT mode */
4991 if (uView == LVS_LIST)
4992 nScrollPosWidth = infoPtr->nItemWidth;
4993 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4994 nScrollPosWidth = 1;
4996 if (rcItem.left < infoPtr->rcList.left)
4998 nHorzAdjust = -1;
4999 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5001 else
5003 nHorzAdjust = 1;
5004 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5008 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5010 /* scroll up/down, but not in LVS_LIST mode */
5011 if (uView == LVS_REPORT)
5012 nScrollPosHeight = infoPtr->nItemHeight;
5013 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5014 nScrollPosHeight = 1;
5016 if (rcItem.top < infoPtr->rcList.top)
5018 nVertAdjust = -1;
5019 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5021 else
5023 nVertAdjust = 1;
5024 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5028 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5030 if (nScrollPosWidth)
5032 INT diff = nHorzDiff / nScrollPosWidth;
5033 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5034 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5037 if (nScrollPosHeight)
5039 INT diff = nVertDiff / nScrollPosHeight;
5040 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5041 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5044 return TRUE;
5047 /***
5048 * DESCRIPTION:
5049 * Searches for an item with specific characteristics.
5051 * PARAMETER(S):
5052 * [I] hwnd : window handle
5053 * [I] nStart : base item index
5054 * [I] lpFindInfo : item information to look for
5056 * RETURN:
5057 * SUCCESS : index of item
5058 * FAILURE : -1
5060 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5061 const LVFINDINFOW *lpFindInfo)
5063 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5064 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5065 BOOL bWrap = FALSE, bNearest = FALSE;
5066 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5067 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5068 POINT Position, Destination;
5069 LVITEMW lvItem;
5071 if (!lpFindInfo || nItem < 0) return -1;
5073 lvItem.mask = 0;
5074 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5076 lvItem.mask |= LVIF_TEXT;
5077 lvItem.pszText = szDispText;
5078 lvItem.cchTextMax = DISP_TEXT_SIZE;
5081 if (lpFindInfo->flags & LVFI_WRAP)
5082 bWrap = TRUE;
5084 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5085 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5087 POINT Origin;
5088 RECT rcArea;
5090 LISTVIEW_GetOrigin(infoPtr, &Origin);
5091 Destination.x = lpFindInfo->pt.x - Origin.x;
5092 Destination.y = lpFindInfo->pt.y - Origin.y;
5093 switch(lpFindInfo->vkDirection)
5095 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5096 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5097 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5098 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5099 case VK_HOME: Destination.x = Destination.y = 0; break;
5100 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5101 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5102 case VK_END:
5103 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5104 Destination.x = rcArea.right;
5105 Destination.y = rcArea.bottom;
5106 break;
5107 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5109 bNearest = TRUE;
5111 else Destination.x = Destination.y = 0;
5113 /* if LVFI_PARAM is specified, all other flags are ignored */
5114 if (lpFindInfo->flags & LVFI_PARAM)
5116 lvItem.mask |= LVIF_PARAM;
5117 bNearest = FALSE;
5118 lvItem.mask &= ~LVIF_TEXT;
5121 again:
5122 for (; nItem < nLast; nItem++)
5124 lvItem.iItem = nItem;
5125 lvItem.iSubItem = 0;
5126 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5128 if (lvItem.mask & LVIF_PARAM)
5130 if (lpFindInfo->lParam == lvItem.lParam)
5131 return nItem;
5132 else
5133 continue;
5136 if (lvItem.mask & LVIF_TEXT)
5138 if (lpFindInfo->flags & LVFI_PARTIAL)
5140 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5142 else
5144 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5148 if (!bNearest) return nItem;
5150 /* This is very inefficient. To do a good job here,
5151 * we need a sorted array of (x,y) item positions */
5152 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5154 /* compute the distance^2 to the destination */
5155 xdist = Destination.x - Position.x;
5156 ydist = Destination.y - Position.y;
5157 dist = xdist * xdist + ydist * ydist;
5159 /* remember the distance, and item if it's closer */
5160 if (dist < mindist)
5162 mindist = dist;
5163 nNearestItem = nItem;
5167 if (bWrap)
5169 nItem = 0;
5170 nLast = min(nStart + 1, infoPtr->nItemCount);
5171 bWrap = FALSE;
5172 goto again;
5175 return nNearestItem;
5178 /***
5179 * DESCRIPTION:
5180 * Searches for an item with specific characteristics.
5182 * PARAMETER(S):
5183 * [I] hwnd : window handle
5184 * [I] nStart : base item index
5185 * [I] lpFindInfo : item information to look for
5187 * RETURN:
5188 * SUCCESS : index of item
5189 * FAILURE : -1
5191 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5192 const LVFINDINFOA *lpFindInfo)
5194 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5195 LVFINDINFOW fiw;
5196 INT res;
5197 LPWSTR strW = NULL;
5199 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5200 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5201 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5202 textfreeT(strW, FALSE);
5203 return res;
5206 /***
5207 * DESCRIPTION:
5208 * Retrieves the background image of the listview control.
5210 * PARAMETER(S):
5211 * [I] infoPtr : valid pointer to the listview structure
5212 * [O] lpBkImage : background image attributes
5214 * RETURN:
5215 * SUCCESS : TRUE
5216 * FAILURE : FALSE
5218 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5219 /* { */
5220 /* FIXME (listview, "empty stub!\n"); */
5221 /* return FALSE; */
5222 /* } */
5224 /***
5225 * DESCRIPTION:
5226 * Retrieves column attributes.
5228 * PARAMETER(S):
5229 * [I] infoPtr : valid pointer to the listview structure
5230 * [I] nColumn : column index
5231 * [IO] lpColumn : column information
5232 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5233 * otherwise it is in fact a LPLVCOLUMNA
5235 * RETURN:
5236 * SUCCESS : TRUE
5237 * FAILURE : FALSE
5239 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5241 COLUMN_INFO *lpColumnInfo;
5242 HDITEMW hdi;
5244 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5245 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5247 /* initialize memory */
5248 ZeroMemory(&hdi, sizeof(hdi));
5250 if (lpColumn->mask & LVCF_TEXT)
5252 hdi.mask |= HDI_TEXT;
5253 hdi.pszText = lpColumn->pszText;
5254 hdi.cchTextMax = lpColumn->cchTextMax;
5257 if (lpColumn->mask & LVCF_IMAGE)
5258 hdi.mask |= HDI_IMAGE;
5260 if (lpColumn->mask & LVCF_ORDER)
5261 hdi.mask |= HDI_ORDER;
5263 if (lpColumn->mask & LVCF_SUBITEM)
5264 hdi.mask |= HDI_LPARAM;
5266 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5268 if (lpColumn->mask & LVCF_FMT)
5269 lpColumn->fmt = lpColumnInfo->fmt;
5271 if (lpColumn->mask & LVCF_WIDTH)
5272 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5274 if (lpColumn->mask & LVCF_IMAGE)
5275 lpColumn->iImage = hdi.iImage;
5277 if (lpColumn->mask & LVCF_ORDER)
5278 lpColumn->iOrder = hdi.iOrder;
5280 if (lpColumn->mask & LVCF_SUBITEM)
5281 lpColumn->iSubItem = hdi.lParam;
5283 return TRUE;
5287 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5289 INT i;
5291 if (!lpiArray)
5292 return FALSE;
5294 /* FIXME: little hack */
5295 for (i = 0; i < iCount; i++)
5296 lpiArray[i] = i;
5298 return TRUE;
5301 /***
5302 * DESCRIPTION:
5303 * Retrieves the column width.
5305 * PARAMETER(S):
5306 * [I] infoPtr : valid pointer to the listview structure
5307 * [I] int : column index
5309 * RETURN:
5310 * SUCCESS : column width
5311 * FAILURE : zero
5313 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5315 INT nColumnWidth = 0;
5316 HDITEMW hdItem;
5318 TRACE("nColumn=%d\n", nColumn);
5320 /* we have a 'column' in LIST and REPORT mode only */
5321 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5323 case LVS_LIST:
5324 nColumnWidth = infoPtr->nItemWidth;
5325 break;
5326 case LVS_REPORT:
5327 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5328 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5329 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5331 * TODO: should we do the same in LVM_GETCOLUMN?
5333 hdItem.mask = HDI_WIDTH;
5334 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5336 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5337 return 0;
5339 nColumnWidth = hdItem.cxy;
5340 break;
5343 TRACE("nColumnWidth=%d\n", nColumnWidth);
5344 return nColumnWidth;
5347 /***
5348 * DESCRIPTION:
5349 * In list or report display mode, retrieves the number of items that can fit
5350 * vertically in the visible area. In icon or small icon display mode,
5351 * retrieves the total number of visible items.
5353 * PARAMETER(S):
5354 * [I] infoPtr : valid pointer to the listview structure
5356 * RETURN:
5357 * Number of fully visible items.
5359 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5361 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5363 case LVS_ICON:
5364 case LVS_SMALLICON:
5365 return infoPtr->nItemCount;
5366 case LVS_REPORT:
5367 return LISTVIEW_GetCountPerColumn(infoPtr);
5368 case LVS_LIST:
5369 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5371 assert(FALSE);
5372 return 0;
5375 /***
5376 * DESCRIPTION:
5377 * Retrieves an image list handle.
5379 * PARAMETER(S):
5380 * [I] infoPtr : valid pointer to the listview structure
5381 * [I] nImageList : image list identifier
5383 * RETURN:
5384 * SUCCESS : image list handle
5385 * FAILURE : NULL
5387 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5389 switch (nImageList)
5391 case LVSIL_NORMAL: return infoPtr->himlNormal;
5392 case LVSIL_SMALL: return infoPtr->himlSmall;
5393 case LVSIL_STATE: return infoPtr->himlState;
5395 return NULL;
5398 /* LISTVIEW_GetISearchString */
5400 /***
5401 * DESCRIPTION:
5402 * Retrieves item attributes.
5404 * PARAMETER(S):
5405 * [I] hwnd : window handle
5406 * [IO] lpLVItem : item info
5407 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5408 * if FALSE, then lpLVItem is a LPLVITEMA.
5410 * NOTE:
5411 * This is the internal 'GetItem' interface -- it tries to
5412 * be smart and avoid text copies, if possible, by modifying
5413 * lpLVItem->pszText to point to the text string. Please note
5414 * that this is not always possible (e.g. OWNERDATA), so on
5415 * entry you *must* supply valid values for pszText, and cchTextMax.
5416 * The only difference to the documented interface is that upon
5417 * return, you should use *only* the lpLVItem->pszText, rather than
5418 * the buffer pointer you provided on input. Most code already does
5419 * that, so it's not a problem.
5420 * For the two cases when the text must be copied (that is,
5421 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5423 * RETURN:
5424 * SUCCESS : TRUE
5425 * FAILURE : FALSE
5427 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5429 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5430 NMLVDISPINFOW dispInfo;
5431 ITEM_INFO *lpItem;
5432 ITEMHDR* pItemHdr;
5433 HDPA hdpaSubItems;
5434 INT isubitem;
5436 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5438 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5439 return FALSE;
5441 if (lpLVItem->mask == 0) return TRUE;
5443 /* make a local copy */
5444 isubitem = lpLVItem->iSubItem;
5446 /* a quick optimization if all we're asked is the focus state
5447 * these queries are worth optimising since they are common,
5448 * and can be answered in constant time, without the heavy accesses */
5449 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5450 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5452 lpLVItem->state = 0;
5453 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5454 lpLVItem->state |= LVIS_FOCUSED;
5455 return TRUE;
5458 ZeroMemory(&dispInfo, sizeof(dispInfo));
5460 /* if the app stores all the data, handle it separately */
5461 if (infoPtr->dwStyle & LVS_OWNERDATA)
5463 dispInfo.item.state = 0;
5465 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5466 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5468 /* NOTE: copy only fields which we _know_ are initialized, some apps
5469 * depend on the uninitialized fields being 0 */
5470 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5471 dispInfo.item.iItem = lpLVItem->iItem;
5472 dispInfo.item.iSubItem = isubitem;
5473 if (lpLVItem->mask & LVIF_TEXT)
5475 dispInfo.item.pszText = lpLVItem->pszText;
5476 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5478 if (lpLVItem->mask & LVIF_STATE)
5479 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5480 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5481 dispInfo.item.stateMask = lpLVItem->stateMask;
5482 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5484 /* full size structure expected - _WIN32IE >= 0x560 */
5485 *lpLVItem = dispInfo.item;
5487 else if (lpLVItem->mask & LVIF_INDENT)
5489 /* indent member expected - _WIN32IE >= 0x300 */
5490 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5492 else
5494 /* minimal structure expected */
5495 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5497 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5500 /* make sure lParam is zeroed out */
5501 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5503 /* we store only a little state, so if we're not asked, we're done */
5504 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5506 /* if focus is handled by us, report it */
5507 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5509 lpLVItem->state &= ~LVIS_FOCUSED;
5510 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5511 lpLVItem->state |= LVIS_FOCUSED;
5514 /* and do the same for selection, if we handle it */
5515 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5517 lpLVItem->state &= ~LVIS_SELECTED;
5518 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5519 lpLVItem->state |= LVIS_SELECTED;
5522 return TRUE;
5525 /* find the item and subitem structures before we proceed */
5526 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5527 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5528 assert (lpItem);
5530 if (isubitem)
5532 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5533 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5534 if (!lpSubItem)
5536 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5537 isubitem = 0;
5540 else
5541 pItemHdr = &lpItem->hdr;
5543 /* Do we need to query the state from the app? */
5544 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5546 dispInfo.item.mask |= LVIF_STATE;
5547 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5550 /* Do we need to enquire about the image? */
5551 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5552 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5554 dispInfo.item.mask |= LVIF_IMAGE;
5555 dispInfo.item.iImage = I_IMAGECALLBACK;
5558 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5559 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5561 dispInfo.item.mask |= LVIF_TEXT;
5562 dispInfo.item.pszText = lpLVItem->pszText;
5563 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5564 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5565 *dispInfo.item.pszText = '\0';
5568 /* If we don't have all the requested info, query the application */
5569 if (dispInfo.item.mask != 0)
5571 dispInfo.item.iItem = lpLVItem->iItem;
5572 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5573 dispInfo.item.lParam = lpItem->lParam;
5574 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5575 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5578 /* we should not store values for subitems */
5579 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5581 /* Now, handle the iImage field */
5582 if (dispInfo.item.mask & LVIF_IMAGE)
5584 lpLVItem->iImage = dispInfo.item.iImage;
5585 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5586 pItemHdr->iImage = dispInfo.item.iImage;
5588 else if (lpLVItem->mask & LVIF_IMAGE)
5590 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5591 lpLVItem->iImage = pItemHdr->iImage;
5592 else
5593 lpLVItem->iImage = 0;
5596 /* The pszText field */
5597 if (dispInfo.item.mask & LVIF_TEXT)
5599 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5600 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5602 lpLVItem->pszText = dispInfo.item.pszText;
5604 else if (lpLVItem->mask & LVIF_TEXT)
5606 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5607 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5610 /* Next is the lParam field */
5611 if (dispInfo.item.mask & LVIF_PARAM)
5613 lpLVItem->lParam = dispInfo.item.lParam;
5614 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5615 lpItem->lParam = dispInfo.item.lParam;
5617 else if (lpLVItem->mask & LVIF_PARAM)
5618 lpLVItem->lParam = lpItem->lParam;
5620 /* if this is a subitem, we're done */
5621 if (isubitem) return TRUE;
5623 /* ... the state field (this one is different due to uCallbackmask) */
5624 if (lpLVItem->mask & LVIF_STATE)
5626 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5627 if (dispInfo.item.mask & LVIF_STATE)
5629 lpLVItem->state &= ~dispInfo.item.stateMask;
5630 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5632 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5634 lpLVItem->state &= ~LVIS_FOCUSED;
5635 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5636 lpLVItem->state |= LVIS_FOCUSED;
5638 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5640 lpLVItem->state &= ~LVIS_SELECTED;
5641 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5642 lpLVItem->state |= LVIS_SELECTED;
5646 /* and last, but not least, the indent field */
5647 if (lpLVItem->mask & LVIF_INDENT)
5648 lpLVItem->iIndent = lpItem->iIndent;
5650 return TRUE;
5653 /***
5654 * DESCRIPTION:
5655 * Retrieves item attributes.
5657 * PARAMETER(S):
5658 * [I] hwnd : window handle
5659 * [IO] lpLVItem : item info
5660 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5661 * if FALSE, then lpLVItem is a LPLVITEMA.
5663 * NOTE:
5664 * This is the external 'GetItem' interface -- it properly copies
5665 * the text in the provided buffer.
5667 * RETURN:
5668 * SUCCESS : TRUE
5669 * FAILURE : FALSE
5671 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5673 LPWSTR pszText;
5674 BOOL bResult;
5676 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5677 return FALSE;
5679 pszText = lpLVItem->pszText;
5680 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5681 if (bResult && lpLVItem->pszText != pszText)
5682 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5683 lpLVItem->pszText = pszText;
5685 return bResult;
5689 /***
5690 * DESCRIPTION:
5691 * Retrieves the position (upper-left) of the listview control item.
5692 * Note that for LVS_ICON style, the upper-left is that of the icon
5693 * and not the bounding box.
5695 * PARAMETER(S):
5696 * [I] infoPtr : valid pointer to the listview structure
5697 * [I] nItem : item index
5698 * [O] lpptPosition : coordinate information
5700 * RETURN:
5701 * SUCCESS : TRUE
5702 * FAILURE : FALSE
5704 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5706 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5707 POINT Origin;
5709 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5711 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5713 LISTVIEW_GetOrigin(infoPtr, &Origin);
5714 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5716 if (uView == LVS_ICON)
5718 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5719 lpptPosition->y += ICON_TOP_PADDING;
5721 lpptPosition->x += Origin.x;
5722 lpptPosition->y += Origin.y;
5724 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5725 return TRUE;
5729 /***
5730 * DESCRIPTION:
5731 * Retrieves the bounding rectangle for a listview control item.
5733 * PARAMETER(S):
5734 * [I] infoPtr : valid pointer to the listview structure
5735 * [I] nItem : item index
5736 * [IO] lprc : bounding rectangle coordinates
5737 * lprc->left specifies the portion of the item for which the bounding
5738 * rectangle will be retrieved.
5740 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5741 * including the icon and label.
5743 * * For LVS_ICON
5744 * * Experiment shows that native control returns:
5745 * * width = min (48, length of text line)
5746 * * .left = position.x - (width - iconsize.cx)/2
5747 * * .right = .left + width
5748 * * height = #lines of text * ntmHeight + icon height + 8
5749 * * .top = position.y - 2
5750 * * .bottom = .top + height
5751 * * separation between items .y = itemSpacing.cy - height
5752 * * .x = itemSpacing.cx - width
5753 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5755 * * For LVS_ICON
5756 * * Experiment shows that native control returns:
5757 * * width = iconSize.cx + 16
5758 * * .left = position.x - (width - iconsize.cx)/2
5759 * * .right = .left + width
5760 * * height = iconSize.cy + 4
5761 * * .top = position.y - 2
5762 * * .bottom = .top + height
5763 * * separation between items .y = itemSpacing.cy - height
5764 * * .x = itemSpacing.cx - width
5765 * LVIR_LABEL Returns the bounding rectangle of the item text.
5767 * * For LVS_ICON
5768 * * Experiment shows that native control returns:
5769 * * width = text length
5770 * * .left = position.x - width/2
5771 * * .right = .left + width
5772 * * height = ntmH * linecount + 2
5773 * * .top = position.y + iconSize.cy + 6
5774 * * .bottom = .top + height
5775 * * separation between items .y = itemSpacing.cy - height
5776 * * .x = itemSpacing.cx - width
5777 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5778 * rectangles, but excludes columns in report view.
5780 * RETURN:
5781 * SUCCESS : TRUE
5782 * FAILURE : FALSE
5784 * NOTES
5785 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5786 * upon whether the window has the focus currently and on whether the item
5787 * is the one with the focus. Ensure that the control's record of which
5788 * item has the focus agrees with the items' records.
5790 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5792 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5793 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5794 BOOL doLabel = TRUE, oversizedBox = FALSE;
5795 POINT Position, Origin;
5796 LVITEMW lvItem;
5798 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5800 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5802 LISTVIEW_GetOrigin(infoPtr, &Origin);
5803 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5805 /* Be smart and try to figure out the minimum we have to do */
5806 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5807 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5808 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5809 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5810 oversizedBox = TRUE;
5812 /* get what we need from the item before hand, so we make
5813 * only one request. This can speed up things, if data
5814 * is stored on the app side */
5815 lvItem.mask = 0;
5816 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5817 if (doLabel) lvItem.mask |= LVIF_TEXT;
5818 lvItem.iItem = nItem;
5819 lvItem.iSubItem = 0;
5820 lvItem.pszText = szDispText;
5821 lvItem.cchTextMax = DISP_TEXT_SIZE;
5822 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5823 /* we got the state already up, simulate it here, to avoid a reget */
5824 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5826 lvItem.mask |= LVIF_STATE;
5827 lvItem.stateMask = LVIS_FOCUSED;
5828 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5831 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5832 lprc->left = LVIR_BOUNDS;
5833 switch(lprc->left)
5835 case LVIR_ICON:
5836 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5837 break;
5839 case LVIR_LABEL:
5840 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5841 break;
5843 case LVIR_BOUNDS:
5844 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5845 break;
5847 case LVIR_SELECTBOUNDS:
5848 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5849 break;
5851 default:
5852 WARN("Unknown value: %d\n", lprc->left);
5853 return FALSE;
5856 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5858 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5860 return TRUE;
5863 /***
5864 * DESCRIPTION:
5865 * Retrieves the spacing between listview control items.
5867 * PARAMETER(S):
5868 * [I] infoPtr : valid pointer to the listview structure
5869 * [IO] lprc : rectangle to receive the output
5870 * on input, lprc->top = nSubItem
5871 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5873 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5874 * not only those of the first column.
5875 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5877 * RETURN:
5878 * TRUE: success
5879 * FALSE: failure
5881 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5883 POINT Position;
5884 LVITEMW lvItem;
5885 INT nColumn;
5887 if (!lprc) return FALSE;
5889 nColumn = lprc->top;
5891 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5892 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5893 if (lprc->top == 0)
5894 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5896 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5898 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5900 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5902 lvItem.mask = 0;
5903 lvItem.iItem = nItem;
5904 lvItem.iSubItem = nColumn;
5906 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5907 switch(lprc->left)
5909 case LVIR_ICON:
5910 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5911 break;
5913 case LVIR_LABEL:
5914 case LVIR_BOUNDS:
5915 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5916 break;
5918 default:
5919 ERR("Unknown bounds=%d\n", lprc->left);
5920 return FALSE;
5923 OffsetRect(lprc, Position.x, Position.y);
5924 return TRUE;
5928 /***
5929 * DESCRIPTION:
5930 * Retrieves the width of a label.
5932 * PARAMETER(S):
5933 * [I] infoPtr : valid pointer to the listview structure
5935 * RETURN:
5936 * SUCCESS : string width (in pixels)
5937 * FAILURE : zero
5939 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5941 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5942 LVITEMW lvItem;
5944 TRACE("(nItem=%d)\n", nItem);
5946 lvItem.mask = LVIF_TEXT;
5947 lvItem.iItem = nItem;
5948 lvItem.iSubItem = 0;
5949 lvItem.pszText = szDispText;
5950 lvItem.cchTextMax = DISP_TEXT_SIZE;
5951 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5953 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5956 /***
5957 * DESCRIPTION:
5958 * Retrieves the spacing between listview control items.
5960 * PARAMETER(S):
5961 * [I] infoPtr : valid pointer to the listview structure
5962 * [I] bSmall : flag for small or large icon
5964 * RETURN:
5965 * Horizontal + vertical spacing
5967 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5969 LONG lResult;
5971 if (!bSmall)
5973 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5975 else
5977 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5978 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5979 else
5980 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5982 return lResult;
5985 /***
5986 * DESCRIPTION:
5987 * Retrieves the state of a listview control item.
5989 * PARAMETER(S):
5990 * [I] infoPtr : valid pointer to the listview structure
5991 * [I] nItem : item index
5992 * [I] uMask : state mask
5994 * RETURN:
5995 * State specified by the mask.
5997 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5999 LVITEMW lvItem;
6001 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6003 lvItem.iItem = nItem;
6004 lvItem.iSubItem = 0;
6005 lvItem.mask = LVIF_STATE;
6006 lvItem.stateMask = uMask;
6007 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6009 return lvItem.state & uMask;
6012 /***
6013 * DESCRIPTION:
6014 * Retrieves the text of a listview control item or subitem.
6016 * PARAMETER(S):
6017 * [I] hwnd : window handle
6018 * [I] nItem : item index
6019 * [IO] lpLVItem : item information
6020 * [I] isW : TRUE if lpLVItem is Unicode
6022 * RETURN:
6023 * SUCCESS : string length
6024 * FAILURE : 0
6026 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6028 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6030 lpLVItem->mask = LVIF_TEXT;
6031 lpLVItem->iItem = nItem;
6032 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6034 return textlenT(lpLVItem->pszText, isW);
6037 /***
6038 * DESCRIPTION:
6039 * Searches for an item based on properties + relationships.
6041 * PARAMETER(S):
6042 * [I] infoPtr : valid pointer to the listview structure
6043 * [I] nItem : item index
6044 * [I] uFlags : relationship flag
6046 * RETURN:
6047 * SUCCESS : item index
6048 * FAILURE : -1
6050 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6052 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6053 UINT uMask = 0;
6054 LVFINDINFOW lvFindInfo;
6055 INT nCountPerColumn;
6056 INT nCountPerRow;
6057 INT i;
6059 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6060 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6062 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6064 if (uFlags & LVNI_CUT)
6065 uMask |= LVIS_CUT;
6067 if (uFlags & LVNI_DROPHILITED)
6068 uMask |= LVIS_DROPHILITED;
6070 if (uFlags & LVNI_FOCUSED)
6071 uMask |= LVIS_FOCUSED;
6073 if (uFlags & LVNI_SELECTED)
6074 uMask |= LVIS_SELECTED;
6076 /* if we're asked for the focused item, that's only one,
6077 * so it's worth optimizing */
6078 if (uFlags & LVNI_FOCUSED)
6080 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6081 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6084 if (uFlags & LVNI_ABOVE)
6086 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6088 while (nItem >= 0)
6090 nItem--;
6091 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6092 return nItem;
6095 else
6097 /* Special case for autoarrange - move 'til the top of a list */
6098 if (is_autoarrange(infoPtr))
6100 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6101 while (nItem - nCountPerRow >= 0)
6103 nItem -= nCountPerRow;
6104 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6105 return nItem;
6107 return -1;
6109 lvFindInfo.flags = LVFI_NEARESTXY;
6110 lvFindInfo.vkDirection = VK_UP;
6111 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6112 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6114 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6115 return nItem;
6119 else if (uFlags & LVNI_BELOW)
6121 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6123 while (nItem < infoPtr->nItemCount)
6125 nItem++;
6126 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6127 return nItem;
6130 else
6132 /* Special case for autoarrange - move 'til the bottom of a list */
6133 if (is_autoarrange(infoPtr))
6135 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6136 while (nItem + nCountPerRow < infoPtr->nItemCount )
6138 nItem += nCountPerRow;
6139 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6140 return nItem;
6142 return -1;
6144 lvFindInfo.flags = LVFI_NEARESTXY;
6145 lvFindInfo.vkDirection = VK_DOWN;
6146 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6147 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6149 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6150 return nItem;
6154 else if (uFlags & LVNI_TOLEFT)
6156 if (uView == LVS_LIST)
6158 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6159 while (nItem - nCountPerColumn >= 0)
6161 nItem -= nCountPerColumn;
6162 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6163 return nItem;
6166 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6168 /* Special case for autoarrange - move 'ti the beginning of a row */
6169 if (is_autoarrange(infoPtr))
6171 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6172 while (nItem % nCountPerRow > 0)
6174 nItem --;
6175 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6176 return nItem;
6178 return -1;
6180 lvFindInfo.flags = LVFI_NEARESTXY;
6181 lvFindInfo.vkDirection = VK_LEFT;
6182 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6183 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6185 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6186 return nItem;
6190 else if (uFlags & LVNI_TORIGHT)
6192 if (uView == LVS_LIST)
6194 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6195 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6197 nItem += nCountPerColumn;
6198 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6199 return nItem;
6202 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6204 /* Special case for autoarrange - move 'til the end of a row */
6205 if (is_autoarrange(infoPtr))
6207 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6208 while (nItem % nCountPerRow < nCountPerRow - 1 )
6210 nItem ++;
6211 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6212 return nItem;
6214 return -1;
6216 lvFindInfo.flags = LVFI_NEARESTXY;
6217 lvFindInfo.vkDirection = VK_RIGHT;
6218 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6219 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6221 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6222 return nItem;
6226 else
6228 nItem++;
6230 /* search by index */
6231 for (i = nItem; i < infoPtr->nItemCount; i++)
6233 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6234 return i;
6238 return -1;
6241 /* LISTVIEW_GetNumberOfWorkAreas */
6243 /***
6244 * DESCRIPTION:
6245 * Retrieves the origin coordinates when in icon or small icon display mode.
6247 * PARAMETER(S):
6248 * [I] infoPtr : valid pointer to the listview structure
6249 * [O] lpptOrigin : coordinate information
6251 * RETURN:
6252 * None.
6254 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6256 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6257 INT nHorzPos = 0, nVertPos = 0;
6258 SCROLLINFO scrollInfo;
6260 scrollInfo.cbSize = sizeof(SCROLLINFO);
6261 scrollInfo.fMask = SIF_POS;
6263 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6264 nHorzPos = scrollInfo.nPos;
6265 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6266 nVertPos = scrollInfo.nPos;
6268 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6270 lpptOrigin->x = infoPtr->rcList.left;
6271 lpptOrigin->y = infoPtr->rcList.top;
6272 if (uView == LVS_LIST)
6273 nHorzPos *= infoPtr->nItemWidth;
6274 else if (uView == LVS_REPORT)
6275 nVertPos *= infoPtr->nItemHeight;
6277 lpptOrigin->x -= nHorzPos;
6278 lpptOrigin->y -= nVertPos;
6280 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6283 /***
6284 * DESCRIPTION:
6285 * Retrieves the width of a string.
6287 * PARAMETER(S):
6288 * [I] hwnd : window handle
6289 * [I] lpszText : text string to process
6290 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6292 * RETURN:
6293 * SUCCESS : string width (in pixels)
6294 * FAILURE : zero
6296 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6298 SIZE stringSize;
6300 stringSize.cx = 0;
6301 if (is_textT(lpszText, isW))
6303 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6304 HDC hdc = GetDC(infoPtr->hwndSelf);
6305 HFONT hOldFont = SelectObject(hdc, hFont);
6307 if (isW)
6308 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6309 else
6310 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6311 SelectObject(hdc, hOldFont);
6312 ReleaseDC(infoPtr->hwndSelf, hdc);
6314 return stringSize.cx;
6317 /***
6318 * DESCRIPTION:
6319 * Determines which listview item is located at the specified position.
6321 * PARAMETER(S):
6322 * [I] infoPtr : valid pointer to the listview structure
6323 * [IO] lpht : hit test information
6324 * [I] subitem : fill out iSubItem.
6325 * [I] select : return the index only if the hit selects the item
6327 * NOTE:
6328 * (mm 20001022): We must not allow iSubItem to be touched, for
6329 * an app might pass only a structure with space up to iItem!
6330 * (MS Office 97 does that for instance in the file open dialog)
6332 * RETURN:
6333 * SUCCESS : item index
6334 * FAILURE : -1
6336 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6338 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6339 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6340 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6341 POINT Origin, Position, opt;
6342 LVITEMW lvItem;
6343 ITERATOR i;
6344 INT iItem;
6346 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6348 lpht->flags = 0;
6349 lpht->iItem = -1;
6350 if (subitem) lpht->iSubItem = 0;
6352 if (infoPtr->rcList.left > lpht->pt.x)
6353 lpht->flags |= LVHT_TOLEFT;
6354 else if (infoPtr->rcList.right < lpht->pt.x)
6355 lpht->flags |= LVHT_TORIGHT;
6357 if (infoPtr->rcList.top > lpht->pt.y)
6358 lpht->flags |= LVHT_ABOVE;
6359 else if (infoPtr->rcList.bottom < lpht->pt.y)
6360 lpht->flags |= LVHT_BELOW;
6362 TRACE("lpht->flags=0x%x\n", lpht->flags);
6363 if (lpht->flags) return -1;
6365 lpht->flags |= LVHT_NOWHERE;
6367 LISTVIEW_GetOrigin(infoPtr, &Origin);
6369 /* first deal with the large items */
6370 rcSearch.left = lpht->pt.x;
6371 rcSearch.top = lpht->pt.y;
6372 rcSearch.right = rcSearch.left + 1;
6373 rcSearch.bottom = rcSearch.top + 1;
6375 iterator_frameditems(&i, infoPtr, &rcSearch);
6376 iterator_next(&i); /* go to first item in the sequence */
6377 iItem = i.nItem;
6378 iterator_destroy(&i);
6380 TRACE("lpht->iItem=%d\n", iItem);
6381 if (iItem == -1) return -1;
6383 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6384 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6385 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6386 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6387 lvItem.iItem = iItem;
6388 lvItem.iSubItem = 0;
6389 lvItem.pszText = szDispText;
6390 lvItem.cchTextMax = DISP_TEXT_SIZE;
6391 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6392 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6394 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6395 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6396 opt.x = lpht->pt.x - Position.x - Origin.x;
6397 opt.y = lpht->pt.y - Position.y - Origin.y;
6399 if (uView == LVS_REPORT)
6400 rcBounds = rcBox;
6401 else
6403 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6404 UnionRect(&rcBounds, &rcBounds, &rcState);
6406 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6407 if (!PtInRect(&rcBounds, opt)) return -1;
6409 if (PtInRect(&rcIcon, opt))
6410 lpht->flags |= LVHT_ONITEMICON;
6411 else if (PtInRect(&rcLabel, opt))
6412 lpht->flags |= LVHT_ONITEMLABEL;
6413 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6414 lpht->flags |= LVHT_ONITEMSTATEICON;
6415 if (lpht->flags & LVHT_ONITEM)
6416 lpht->flags &= ~LVHT_NOWHERE;
6418 TRACE("lpht->flags=0x%x\n", lpht->flags);
6419 if (uView == LVS_REPORT && subitem)
6421 INT j;
6423 rcBounds.right = rcBounds.left;
6424 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6426 rcBounds.left = rcBounds.right;
6427 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6428 if (PtInRect(&rcBounds, opt))
6430 lpht->iSubItem = j;
6431 break;
6436 if (select && !(uView == LVS_REPORT &&
6437 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6438 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6440 if (uView == LVS_REPORT)
6442 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6443 UnionRect(&rcBounds, &rcBounds, &rcState);
6445 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6447 return lpht->iItem = iItem;
6451 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6452 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6453 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6454 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6455 their own sort proc. when sending LVM_SORTITEMS.
6457 /* Platform SDK:
6458 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6460 LVS_SORTXXX must be specified,
6461 LVS_OWNERDRAW is not set,
6462 <item>.pszText is not LPSTR_TEXTCALLBACK.
6464 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6465 are sorted based on item text..."
6467 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6469 ITEM_INFO* lv_first = DPA_GetPtr( (HDPA)first, 0 );
6470 ITEM_INFO* lv_second = DPA_GetPtr( (HDPA)second, 0 );
6471 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6473 /* if we're sorting descending, negate the return value */
6474 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6477 /***
6478 * DESCRIPTION:
6479 * Inserts a new item in the listview control.
6481 * PARAMETER(S):
6482 * [I] infoPtr : valid pointer to the listview structure
6483 * [I] lpLVItem : item information
6484 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6486 * RETURN:
6487 * SUCCESS : new item index
6488 * FAILURE : -1
6490 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6492 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6493 INT nItem;
6494 HDPA hdpaSubItems;
6495 NMLISTVIEW nmlv;
6496 ITEM_INFO *lpItem;
6497 BOOL is_sorted, has_changed;
6498 LVITEMW item;
6499 HWND hwndSelf = infoPtr->hwndSelf;
6501 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6503 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6505 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6506 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6508 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6510 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6512 /* insert item in listview control data structure */
6513 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6514 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6516 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6517 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6519 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6521 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6522 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6523 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6524 if (nItem == -1) goto fail;
6525 infoPtr->nItemCount++;
6527 /* shift indices first so they don't get tangled */
6528 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6530 /* set the item attributes */
6531 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6533 /* full size structure expected - _WIN32IE >= 0x560 */
6534 item = *lpLVItem;
6536 else if (lpLVItem->mask & LVIF_INDENT)
6538 /* indent member expected - _WIN32IE >= 0x300 */
6539 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6541 else
6543 /* minimal structure expected */
6544 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6546 item.iItem = nItem;
6547 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6549 item.mask |= LVIF_STATE;
6550 item.stateMask |= LVIS_STATEIMAGEMASK;
6551 item.state &= ~LVIS_STATEIMAGEMASK;
6552 item.state |= INDEXTOSTATEIMAGEMASK(1);
6554 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6556 /* if we're sorted, sort the list, and update the index */
6557 if (is_sorted)
6559 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6560 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6561 assert(nItem != -1);
6564 /* make room for the position, if we are in the right mode */
6565 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6567 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6568 goto undo;
6569 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6571 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6572 goto undo;
6576 /* send LVN_INSERTITEM notification */
6577 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6578 nmlv.iItem = nItem;
6579 nmlv.lParam = lpItem->lParam;
6580 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6581 if (!IsWindow(hwndSelf))
6582 return -1;
6584 /* align items (set position of each item) */
6585 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6587 POINT pt;
6589 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6590 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6591 else
6592 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6594 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6597 /* now is the invalidation fun */
6598 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6599 return nItem;
6601 undo:
6602 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6603 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6604 infoPtr->nItemCount--;
6605 fail:
6606 DPA_DeletePtr(hdpaSubItems, 0);
6607 DPA_Destroy (hdpaSubItems);
6608 Free (lpItem);
6609 return -1;
6612 /***
6613 * DESCRIPTION:
6614 * Redraws a range of items.
6616 * PARAMETER(S):
6617 * [I] infoPtr : valid pointer to the listview structure
6618 * [I] nFirst : first item
6619 * [I] nLast : last item
6621 * RETURN:
6622 * SUCCESS : TRUE
6623 * FAILURE : FALSE
6625 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6627 INT i;
6629 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6630 max(nFirst, nLast) >= infoPtr->nItemCount)
6631 return FALSE;
6633 for (i = nFirst; i <= nLast; i++)
6634 LISTVIEW_InvalidateItem(infoPtr, i);
6636 return TRUE;
6639 /***
6640 * DESCRIPTION:
6641 * Scroll the content of a listview.
6643 * PARAMETER(S):
6644 * [I] infoPtr : valid pointer to the listview structure
6645 * [I] dx : horizontal scroll amount in pixels
6646 * [I] dy : vertical scroll amount in pixels
6648 * RETURN:
6649 * SUCCESS : TRUE
6650 * FAILURE : FALSE
6652 * COMMENTS:
6653 * If the control is in report mode (LVS_REPORT) the control can
6654 * be scrolled only in line increments. "dy" will be rounded to the
6655 * nearest number of pixels that are a whole line. Ex: if line height
6656 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6657 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6659 * For: (per experimentation with native control and CSpy ListView)
6660 * LVS_ICON dy=1 = 1 pixel (vertical only)
6661 * dx ignored
6662 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6663 * dx ignored
6664 * LVS_LIST dx=1 = 1 column (horizontal only)
6665 * but will only scroll 1 column per message
6666 * no matter what the value.
6667 * dy must be 0 or FALSE returned.
6668 * LVS_REPORT dx=1 = 1 pixel
6669 * dy= see above
6672 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6674 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6675 case LVS_REPORT:
6676 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6677 dy /= infoPtr->nItemHeight;
6678 break;
6679 case LVS_LIST:
6680 if (dy != 0) return FALSE;
6681 break;
6682 default: /* icon */
6683 dx = 0;
6684 break;
6687 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6688 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6690 return TRUE;
6693 /***
6694 * DESCRIPTION:
6695 * Sets the background color.
6697 * PARAMETER(S):
6698 * [I] infoPtr : valid pointer to the listview structure
6699 * [I] clrBk : background color
6701 * RETURN:
6702 * SUCCESS : TRUE
6703 * FAILURE : FALSE
6705 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6707 TRACE("(clrBk=%x)\n", clrBk);
6709 if(infoPtr->clrBk != clrBk) {
6710 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6711 infoPtr->clrBk = clrBk;
6712 if (clrBk == CLR_NONE)
6713 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6714 else
6715 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6716 LISTVIEW_InvalidateList(infoPtr);
6719 return TRUE;
6722 /* LISTVIEW_SetBkImage */
6724 /*** Helper for {Insert,Set}ColumnT *only* */
6725 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6726 const LVCOLUMNW *lpColumn, BOOL isW)
6728 if (lpColumn->mask & LVCF_FMT)
6730 /* format member is valid */
6731 lphdi->mask |= HDI_FORMAT;
6733 /* set text alignment (leftmost column must be left-aligned) */
6734 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6735 lphdi->fmt |= HDF_LEFT;
6736 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6737 lphdi->fmt |= HDF_RIGHT;
6738 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6739 lphdi->fmt |= HDF_CENTER;
6741 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6742 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6744 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6746 lphdi->fmt |= HDF_IMAGE;
6747 lphdi->iImage = I_IMAGECALLBACK;
6751 if (lpColumn->mask & LVCF_WIDTH)
6753 lphdi->mask |= HDI_WIDTH;
6754 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6756 /* make it fill the remainder of the controls width */
6757 RECT rcHeader;
6758 INT item_index;
6760 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6762 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6763 lphdi->cxy += rcHeader.right - rcHeader.left;
6766 /* retrieve the layout of the header */
6767 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6768 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6770 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6772 else
6773 lphdi->cxy = lpColumn->cx;
6776 if (lpColumn->mask & LVCF_TEXT)
6778 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6779 lphdi->fmt |= HDF_STRING;
6780 lphdi->pszText = lpColumn->pszText;
6781 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6784 if (lpColumn->mask & LVCF_IMAGE)
6786 lphdi->mask |= HDI_IMAGE;
6787 lphdi->iImage = lpColumn->iImage;
6790 if (lpColumn->mask & LVCF_ORDER)
6792 lphdi->mask |= HDI_ORDER;
6793 lphdi->iOrder = lpColumn->iOrder;
6798 /***
6799 * DESCRIPTION:
6800 * Inserts a new column.
6802 * PARAMETER(S):
6803 * [I] infoPtr : valid pointer to the listview structure
6804 * [I] nColumn : column index
6805 * [I] lpColumn : column information
6806 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6808 * RETURN:
6809 * SUCCESS : new column index
6810 * FAILURE : -1
6812 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6813 const LVCOLUMNW *lpColumn, BOOL isW)
6815 COLUMN_INFO *lpColumnInfo;
6816 INT nNewColumn;
6817 HDITEMW hdi;
6819 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6821 if (!lpColumn || nColumn < 0) return -1;
6822 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6824 ZeroMemory(&hdi, sizeof(HDITEMW));
6825 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6828 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6829 * (can be seen in SPY) otherwise column never gets added.
6831 if (!(lpColumn->mask & LVCF_WIDTH)) {
6832 hdi.mask |= HDI_WIDTH;
6833 hdi.cxy = 10;
6837 * when the iSubItem is available Windows copies it to the header lParam. It seems
6838 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6840 if (lpColumn->mask & LVCF_SUBITEM)
6842 hdi.mask |= HDI_LPARAM;
6843 hdi.lParam = lpColumn->iSubItem;
6846 /* insert item in header control */
6847 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6848 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6849 (WPARAM)nColumn, (LPARAM)&hdi);
6850 if (nNewColumn == -1) return -1;
6851 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6853 /* create our own column info */
6854 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6855 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6857 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6858 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6860 /* now we have to actually adjust the data */
6861 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6863 SUBITEM_INFO *lpSubItem;
6864 HDPA hdpaSubItems;
6865 INT nItem, i;
6867 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6869 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
6870 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6872 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
6873 if (lpSubItem->iSubItem >= nNewColumn)
6874 lpSubItem->iSubItem++;
6879 /* make space for the new column */
6880 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6881 LISTVIEW_UpdateItemSize(infoPtr);
6883 return nNewColumn;
6885 fail:
6886 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6887 if (lpColumnInfo)
6889 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6890 Free(lpColumnInfo);
6892 return -1;
6895 /***
6896 * DESCRIPTION:
6897 * Sets the attributes of a header item.
6899 * PARAMETER(S):
6900 * [I] infoPtr : valid pointer to the listview structure
6901 * [I] nColumn : column index
6902 * [I] lpColumn : column attributes
6903 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6905 * RETURN:
6906 * SUCCESS : TRUE
6907 * FAILURE : FALSE
6909 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6910 const LVCOLUMNW *lpColumn, BOOL isW)
6912 HDITEMW hdi, hdiget;
6913 BOOL bResult;
6915 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6917 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6919 ZeroMemory(&hdi, sizeof(HDITEMW));
6920 if (lpColumn->mask & LVCF_FMT)
6922 hdi.mask |= HDI_FORMAT;
6923 hdiget.mask = HDI_FORMAT;
6924 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6925 hdi.fmt = hdiget.fmt & HDF_STRING;
6927 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6929 /* set header item attributes */
6930 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6931 if (!bResult) return FALSE;
6933 if (lpColumn->mask & LVCF_FMT)
6935 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6936 int oldFmt = lpColumnInfo->fmt;
6938 lpColumnInfo->fmt = lpColumn->fmt;
6939 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6941 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6942 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6946 return TRUE;
6949 /***
6950 * DESCRIPTION:
6951 * Sets the column order array
6953 * PARAMETERS:
6954 * [I] infoPtr : valid pointer to the listview structure
6955 * [I] iCount : number of elements in column order array
6956 * [I] lpiArray : pointer to column order array
6958 * RETURN:
6959 * SUCCESS : TRUE
6960 * FAILURE : FALSE
6962 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6964 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6966 if (!lpiArray)
6967 return FALSE;
6969 return TRUE;
6973 /***
6974 * DESCRIPTION:
6975 * Sets the width of a column
6977 * PARAMETERS:
6978 * [I] infoPtr : valid pointer to the listview structure
6979 * [I] nColumn : column index
6980 * [I] cx : column width
6982 * RETURN:
6983 * SUCCESS : TRUE
6984 * FAILURE : FALSE
6986 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6988 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6989 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6990 INT max_cx = 0;
6991 HDITEMW hdi;
6993 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6995 /* set column width only if in report or list mode */
6996 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6998 /* take care of invalid cx values */
6999 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7000 else if (uView == LVS_LIST && cx < 1) return FALSE;
7002 /* resize all columns if in LVS_LIST mode */
7003 if(uView == LVS_LIST)
7005 infoPtr->nItemWidth = cx;
7006 LISTVIEW_InvalidateList(infoPtr);
7007 return TRUE;
7010 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7012 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7014 INT nLabelWidth;
7015 LVITEMW lvItem;
7017 lvItem.mask = LVIF_TEXT;
7018 lvItem.iItem = 0;
7019 lvItem.iSubItem = nColumn;
7020 lvItem.pszText = szDispText;
7021 lvItem.cchTextMax = DISP_TEXT_SIZE;
7022 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7024 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7025 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7026 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7028 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7029 max_cx += infoPtr->iconSize.cx;
7030 max_cx += TRAILING_LABEL_PADDING;
7033 /* autosize based on listview items width */
7034 if(cx == LVSCW_AUTOSIZE)
7035 cx = max_cx;
7036 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7038 /* if iCol is the last column make it fill the remainder of the controls width */
7039 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7041 RECT rcHeader;
7042 POINT Origin;
7044 LISTVIEW_GetOrigin(infoPtr, &Origin);
7045 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7047 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7049 else
7051 /* Despite what the MS docs say, if this is not the last
7052 column, then MS resizes the column to the width of the
7053 largest text string in the column, including headers
7054 and items. This is different from LVSCW_AUTOSIZE in that
7055 LVSCW_AUTOSIZE ignores the header string length. */
7056 cx = 0;
7058 /* retrieve header text */
7059 hdi.mask = HDI_TEXT;
7060 hdi.cchTextMax = DISP_TEXT_SIZE;
7061 hdi.pszText = szDispText;
7062 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7064 HDC hdc = GetDC(infoPtr->hwndSelf);
7065 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7066 SIZE size;
7068 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7069 cx = size.cx + TRAILING_HEADER_PADDING;
7070 /* FIXME: Take into account the header image, if one is present */
7071 SelectObject(hdc, old_font);
7072 ReleaseDC(infoPtr->hwndSelf, hdc);
7074 cx = max (cx, max_cx);
7078 if (cx < 0) return FALSE;
7080 /* call header to update the column change */
7081 hdi.mask = HDI_WIDTH;
7082 hdi.cxy = cx;
7083 TRACE("hdi.cxy=%d\n", hdi.cxy);
7084 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7087 /***
7088 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7091 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7093 HDC hdc_wnd, hdc;
7094 HBITMAP hbm_im, hbm_mask, hbm_orig;
7095 RECT rc;
7096 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7097 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7098 HIMAGELIST himl;
7100 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7101 ILC_COLOR | ILC_MASK, 2, 2);
7102 hdc_wnd = GetDC(infoPtr->hwndSelf);
7103 hdc = CreateCompatibleDC(hdc_wnd);
7104 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7105 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7106 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7108 rc.left = rc.top = 0;
7109 rc.right = GetSystemMetrics(SM_CXSMICON);
7110 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7112 hbm_orig = SelectObject(hdc, hbm_mask);
7113 FillRect(hdc, &rc, hbr_white);
7114 InflateRect(&rc, -3, -3);
7115 FillRect(hdc, &rc, hbr_black);
7117 SelectObject(hdc, hbm_im);
7118 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7119 SelectObject(hdc, hbm_orig);
7120 ImageList_Add(himl, hbm_im, hbm_mask);
7122 SelectObject(hdc, hbm_im);
7123 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7124 SelectObject(hdc, hbm_orig);
7125 ImageList_Add(himl, hbm_im, hbm_mask);
7127 DeleteObject(hbm_mask);
7128 DeleteObject(hbm_im);
7129 DeleteDC(hdc);
7131 return himl;
7134 /***
7135 * DESCRIPTION:
7136 * Sets the extended listview style.
7138 * PARAMETERS:
7139 * [I] infoPtr : valid pointer to the listview structure
7140 * [I] dwMask : mask
7141 * [I] dwStyle : style
7143 * RETURN:
7144 * SUCCESS : previous style
7145 * FAILURE : 0
7147 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7149 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7151 /* set new style */
7152 if (dwMask)
7153 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7154 else
7155 infoPtr->dwLvExStyle = dwExStyle;
7157 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7159 HIMAGELIST himl = 0;
7160 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7162 LVITEMW item;
7163 item.mask = LVIF_STATE;
7164 item.stateMask = LVIS_STATEIMAGEMASK;
7165 item.state = INDEXTOSTATEIMAGEMASK(1);
7166 LISTVIEW_SetItemState(infoPtr, -1, &item);
7168 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7170 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7173 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7175 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7176 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7177 dwStyle |= HDS_DRAGDROP;
7178 else
7179 dwStyle &= ~HDS_DRAGDROP;
7180 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7183 /* GRIDLINES adds decoration at top so changes sizes */
7184 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7186 LISTVIEW_UpdateSize(infoPtr);
7190 LISTVIEW_InvalidateList(infoPtr);
7191 return dwOldExStyle;
7194 /***
7195 * DESCRIPTION:
7196 * Sets the new hot cursor used during hot tracking and hover selection.
7198 * PARAMETER(S):
7199 * [I] infoPtr : valid pointer to the listview structure
7200 * [I] hCursor : the new hot cursor handle
7202 * RETURN:
7203 * Returns the previous hot cursor
7205 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7207 HCURSOR oldCursor = infoPtr->hHotCursor;
7209 infoPtr->hHotCursor = hCursor;
7211 return oldCursor;
7215 /***
7216 * DESCRIPTION:
7217 * Sets the hot item index.
7219 * PARAMETERS:
7220 * [I] infoPtr : valid pointer to the listview structure
7221 * [I] iIndex : index
7223 * RETURN:
7224 * SUCCESS : previous hot item index
7225 * FAILURE : -1 (no hot item)
7227 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7229 INT iOldIndex = infoPtr->nHotItem;
7231 infoPtr->nHotItem = iIndex;
7233 return iOldIndex;
7237 /***
7238 * DESCRIPTION:
7239 * Sets the amount of time the cursor must hover over an item before it is selected.
7241 * PARAMETER(S):
7242 * [I] infoPtr : valid pointer to the listview structure
7243 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7245 * RETURN:
7246 * Returns the previous hover time
7248 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7250 DWORD oldHoverTime = infoPtr->dwHoverTime;
7252 infoPtr->dwHoverTime = dwHoverTime;
7254 return oldHoverTime;
7257 /***
7258 * DESCRIPTION:
7259 * Sets spacing for icons of LVS_ICON style.
7261 * PARAMETER(S):
7262 * [I] infoPtr : valid pointer to the listview structure
7263 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7264 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7266 * RETURN:
7267 * MAKELONG(oldcx, oldcy)
7269 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7271 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7272 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7274 TRACE("requested=(%d,%d)\n", cx, cy);
7276 /* this is supported only for LVS_ICON style */
7277 if (uView != LVS_ICON) return oldspacing;
7279 /* set to defaults, if instructed to */
7280 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7281 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7283 /* if 0 then compute width
7284 * FIXME: Should scan each item and determine max width of
7285 * icon or label, then make that the width */
7286 if (cx == 0)
7287 cx = infoPtr->iconSpacing.cx;
7289 /* if 0 then compute height */
7290 if (cy == 0)
7291 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7292 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7295 infoPtr->iconSpacing.cx = cx;
7296 infoPtr->iconSpacing.cy = cy;
7298 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7299 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7300 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7301 infoPtr->ntmHeight);
7303 /* these depend on the iconSpacing */
7304 LISTVIEW_UpdateItemSize(infoPtr);
7306 return oldspacing;
7309 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7311 INT cx, cy;
7313 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7315 size->cx = cx;
7316 size->cy = cy;
7318 else
7320 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7321 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7325 /***
7326 * DESCRIPTION:
7327 * Sets image lists.
7329 * PARAMETER(S):
7330 * [I] infoPtr : valid pointer to the listview structure
7331 * [I] nType : image list type
7332 * [I] himl : image list handle
7334 * RETURN:
7335 * SUCCESS : old image list
7336 * FAILURE : NULL
7338 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7340 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7341 INT oldHeight = infoPtr->nItemHeight;
7342 HIMAGELIST himlOld = 0;
7344 TRACE("(nType=%d, himl=%p\n", nType, himl);
7346 switch (nType)
7348 case LVSIL_NORMAL:
7349 himlOld = infoPtr->himlNormal;
7350 infoPtr->himlNormal = himl;
7351 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7352 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7353 break;
7355 case LVSIL_SMALL:
7356 himlOld = infoPtr->himlSmall;
7357 infoPtr->himlSmall = himl;
7358 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7359 break;
7361 case LVSIL_STATE:
7362 himlOld = infoPtr->himlState;
7363 infoPtr->himlState = himl;
7364 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7365 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7366 break;
7368 default:
7369 ERR("Unknown icon type=%d\n", nType);
7370 return NULL;
7373 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7374 if (infoPtr->nItemHeight != oldHeight)
7375 LISTVIEW_UpdateScroll(infoPtr);
7377 return himlOld;
7380 /***
7381 * DESCRIPTION:
7382 * Preallocates memory (does *not* set the actual count of items !)
7384 * PARAMETER(S):
7385 * [I] infoPtr : valid pointer to the listview structure
7386 * [I] nItems : item count (projected number of items to allocate)
7387 * [I] dwFlags : update flags
7389 * RETURN:
7390 * SUCCESS : TRUE
7391 * FAILURE : FALSE
7393 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7395 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7397 if (infoPtr->dwStyle & LVS_OWNERDATA)
7399 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7400 INT nOldCount = infoPtr->nItemCount;
7402 if (nItems < nOldCount)
7404 RANGE range = { nItems, nOldCount };
7405 ranges_del(infoPtr->selectionRanges, range);
7406 if (infoPtr->nFocusedItem >= nItems)
7408 infoPtr->nFocusedItem = -1;
7409 SetRectEmpty(&infoPtr->rcFocus);
7413 infoPtr->nItemCount = nItems;
7414 LISTVIEW_UpdateScroll(infoPtr);
7416 /* the flags are valid only in ownerdata report and list modes */
7417 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7419 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7420 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7422 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7423 LISTVIEW_InvalidateList(infoPtr);
7424 else
7426 INT nFrom, nTo;
7427 POINT Origin;
7428 RECT rcErase;
7430 LISTVIEW_GetOrigin(infoPtr, &Origin);
7431 nFrom = min(nOldCount, nItems);
7432 nTo = max(nOldCount, nItems);
7434 if (uView == LVS_REPORT)
7436 rcErase.left = 0;
7437 rcErase.top = nFrom * infoPtr->nItemHeight;
7438 rcErase.right = infoPtr->nItemWidth;
7439 rcErase.bottom = nTo * infoPtr->nItemHeight;
7440 OffsetRect(&rcErase, Origin.x, Origin.y);
7441 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7442 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7444 else /* LVS_LIST */
7446 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7448 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7449 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7450 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7451 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7452 OffsetRect(&rcErase, Origin.x, Origin.y);
7453 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7454 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7456 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7457 rcErase.top = 0;
7458 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7459 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7460 OffsetRect(&rcErase, Origin.x, Origin.y);
7461 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7462 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7466 else
7468 /* According to MSDN for non-LVS_OWNERDATA this is just
7469 * a performance issue. The control allocates its internal
7470 * data structures for the number of items specified. It
7471 * cuts down on the number of memory allocations. Therefore
7472 * we will just issue a WARN here
7474 WARN("for non-ownerdata performance option not implemented.\n");
7477 return TRUE;
7480 /***
7481 * DESCRIPTION:
7482 * Sets the position of an item.
7484 * PARAMETER(S):
7485 * [I] infoPtr : valid pointer to the listview structure
7486 * [I] nItem : item index
7487 * [I] pt : coordinate
7489 * RETURN:
7490 * SUCCESS : TRUE
7491 * FAILURE : FALSE
7493 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7495 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7496 POINT Origin;
7498 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7500 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7501 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7503 LISTVIEW_GetOrigin(infoPtr, &Origin);
7505 /* This point value seems to be an undocumented feature.
7506 * The best guess is that it means either at the origin,
7507 * or at true beginning of the list. I will assume the origin. */
7508 if ((pt.x == -1) && (pt.y == -1))
7509 pt = Origin;
7511 if (uView == LVS_ICON)
7513 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7514 pt.y -= ICON_TOP_PADDING;
7516 pt.x -= Origin.x;
7517 pt.y -= Origin.y;
7519 infoPtr->bAutoarrange = FALSE;
7521 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7524 /***
7525 * DESCRIPTION:
7526 * Sets the state of one or many items.
7528 * PARAMETER(S):
7529 * [I] infoPtr : valid pointer to the listview structure
7530 * [I] nItem : item index
7531 * [I] lpLVItem : item or subitem info
7533 * RETURN:
7534 * SUCCESS : TRUE
7535 * FAILURE : FALSE
7537 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7539 BOOL bResult = TRUE;
7540 LVITEMW lvItem;
7542 lvItem.iItem = nItem;
7543 lvItem.iSubItem = 0;
7544 lvItem.mask = LVIF_STATE;
7545 lvItem.state = lpLVItem->state;
7546 lvItem.stateMask = lpLVItem->stateMask;
7547 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7549 if (nItem == -1)
7551 /* apply to all items */
7552 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7553 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7555 else
7556 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7559 * Update selection mark
7561 * Investigation on windows 2k showed that selection mark was updated
7562 * whenever a new selection was made, but if the selected item was
7563 * unselected it was not updated.
7565 * we are probably still not 100% accurate, but this at least sets the
7566 * proper selection mark when it is needed
7569 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7570 (infoPtr->nSelectionMark == -1))
7572 int i;
7573 for (i = 0; i < infoPtr->nItemCount; i++)
7575 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7577 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7579 infoPtr->nSelectionMark = i;
7580 break;
7583 else if (ranges_contain(infoPtr->selectionRanges, i))
7585 infoPtr->nSelectionMark = i;
7586 break;
7591 return bResult;
7594 /***
7595 * DESCRIPTION:
7596 * Sets the text of an item or subitem.
7598 * PARAMETER(S):
7599 * [I] hwnd : window handle
7600 * [I] nItem : item index
7601 * [I] lpLVItem : item or subitem info
7602 * [I] isW : TRUE if input is Unicode
7604 * RETURN:
7605 * SUCCESS : TRUE
7606 * FAILURE : FALSE
7608 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7610 LVITEMW lvItem;
7612 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7614 lvItem.iItem = nItem;
7615 lvItem.iSubItem = lpLVItem->iSubItem;
7616 lvItem.mask = LVIF_TEXT;
7617 lvItem.pszText = lpLVItem->pszText;
7618 lvItem.cchTextMax = lpLVItem->cchTextMax;
7620 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7622 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7625 /***
7626 * DESCRIPTION:
7627 * Set item index that marks the start of a multiple selection.
7629 * PARAMETER(S):
7630 * [I] infoPtr : valid pointer to the listview structure
7631 * [I] nIndex : index
7633 * RETURN:
7634 * Index number or -1 if there is no selection mark.
7636 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7638 INT nOldIndex = infoPtr->nSelectionMark;
7640 TRACE("(nIndex=%d)\n", nIndex);
7642 infoPtr->nSelectionMark = nIndex;
7644 return nOldIndex;
7647 /***
7648 * DESCRIPTION:
7649 * Sets the text background color.
7651 * PARAMETER(S):
7652 * [I] infoPtr : valid pointer to the listview structure
7653 * [I] clrTextBk : text background color
7655 * RETURN:
7656 * SUCCESS : TRUE
7657 * FAILURE : FALSE
7659 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7661 TRACE("(clrTextBk=%x)\n", clrTextBk);
7663 if (infoPtr->clrTextBk != clrTextBk)
7665 infoPtr->clrTextBk = clrTextBk;
7666 LISTVIEW_InvalidateList(infoPtr);
7669 return TRUE;
7672 /***
7673 * DESCRIPTION:
7674 * Sets the text foreground color.
7676 * PARAMETER(S):
7677 * [I] infoPtr : valid pointer to the listview structure
7678 * [I] clrText : text color
7680 * RETURN:
7681 * SUCCESS : TRUE
7682 * FAILURE : FALSE
7684 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7686 TRACE("(clrText=%x)\n", clrText);
7688 if (infoPtr->clrText != clrText)
7690 infoPtr->clrText = clrText;
7691 LISTVIEW_InvalidateList(infoPtr);
7694 return TRUE;
7697 /***
7698 * DESCRIPTION:
7699 * Determines which listview item is located at the specified position.
7701 * PARAMETER(S):
7702 * [I] infoPtr : valid pointer to the listview structure
7703 * [I] hwndNewToolTip : handle to new ToolTip
7705 * RETURN:
7706 * old tool tip
7708 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7710 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7711 infoPtr->hwndToolTip = hwndNewToolTip;
7712 return hwndOldToolTip;
7716 * DESCRIPTION:
7717 * sets the Unicode character format flag for the control
7718 * PARAMETER(S):
7719 * [I] infoPtr :valid pointer to the listview structure
7720 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7722 * RETURN:
7723 * Old Unicode Format
7725 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7727 BOOL rc = infoPtr->notifyFormat;
7728 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7729 return rc;
7732 /* LISTVIEW_SetWorkAreas */
7734 /***
7735 * DESCRIPTION:
7736 * Callback internally used by LISTVIEW_SortItems()
7738 * PARAMETER(S):
7739 * [I] first : pointer to first ITEM_INFO to compare
7740 * [I] second : pointer to second ITEM_INFO to compare
7741 * [I] lParam : HWND of control
7743 * RETURN:
7744 * if first comes before second : negative
7745 * if first comes after second : positive
7746 * if first and second are equivalent : zero
7748 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7750 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7751 ITEM_INFO* lv_first = DPA_GetPtr( (HDPA)first, 0 );
7752 ITEM_INFO* lv_second = DPA_GetPtr( (HDPA)second, 0 );
7754 /* Forward the call to the client defined callback */
7755 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7758 /***
7759 * DESCRIPTION:
7760 * Sorts the listview items.
7762 * PARAMETER(S):
7763 * [I] infoPtr : valid pointer to the listview structure
7764 * [I] pfnCompare : application-defined value
7765 * [I] lParamSort : pointer to comparison callback
7767 * RETURN:
7768 * SUCCESS : TRUE
7769 * FAILURE : FALSE
7771 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7773 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7774 HDPA hdpaSubItems;
7775 ITEM_INFO *lpItem;
7776 LPVOID selectionMarkItem;
7777 LVITEMW item;
7778 int i;
7780 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7782 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7784 if (!pfnCompare) return FALSE;
7785 if (!infoPtr->hdpaItems) return FALSE;
7787 /* if there are 0 or 1 items, there is no need to sort */
7788 if (infoPtr->nItemCount < 2) return TRUE;
7790 if (infoPtr->nFocusedItem >= 0)
7792 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7793 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7794 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7796 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7797 /* clear the lpItem->state for non-selected ones */
7798 /* remove the selection ranges */
7800 infoPtr->pfnCompare = pfnCompare;
7801 infoPtr->lParamSort = lParamSort;
7802 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7804 /* Adjust selections and indices so that they are the way they should
7805 * be after the sort (otherwise, the list items move around, but
7806 * whatever is at the item's previous original position will be
7807 * selected instead)
7809 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7810 for (i=0; i < infoPtr->nItemCount; i++)
7812 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7813 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7815 if (lpItem->state & LVIS_SELECTED)
7817 item.state = LVIS_SELECTED;
7818 item.stateMask = LVIS_SELECTED;
7819 LISTVIEW_SetItemState(infoPtr, i, &item);
7821 if (lpItem->state & LVIS_FOCUSED)
7823 infoPtr->nFocusedItem = i;
7824 lpItem->state &= ~LVIS_FOCUSED;
7827 if (selectionMarkItem != NULL)
7828 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7829 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7831 /* refresh the display */
7832 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7833 LISTVIEW_InvalidateList(infoPtr);
7835 return TRUE;
7838 /***
7839 * DESCRIPTION:
7840 * Update theme handle after a theme change.
7842 * PARAMETER(S):
7843 * [I] infoPtr : valid pointer to the listview structure
7845 * RETURN:
7846 * SUCCESS : 0
7847 * FAILURE : something else
7849 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7851 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7852 CloseThemeData(theme);
7853 OpenThemeData(infoPtr->hwndSelf, themeClass);
7854 return 0;
7857 /***
7858 * DESCRIPTION:
7859 * Updates an items or rearranges the listview control.
7861 * PARAMETER(S):
7862 * [I] infoPtr : valid pointer to the listview structure
7863 * [I] nItem : item index
7865 * RETURN:
7866 * SUCCESS : TRUE
7867 * FAILURE : FALSE
7869 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7871 TRACE("(nItem=%d)\n", nItem);
7873 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7875 /* rearrange with default alignment style */
7876 if (is_autoarrange(infoPtr))
7877 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7878 else
7879 LISTVIEW_InvalidateItem(infoPtr, nItem);
7881 return TRUE;
7884 /***
7885 * DESCRIPTION:
7886 * Draw the track line at the place defined in the infoPtr structure.
7887 * The line is drawn with a XOR pen so drawing the line for the second time
7888 * in the same place erases the line.
7890 * PARAMETER(S):
7891 * [I] infoPtr : valid pointer to the listview structure
7893 * RETURN:
7894 * SUCCESS : TRUE
7895 * FAILURE : FALSE
7897 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7899 HPEN hOldPen;
7900 HDC hdc;
7901 INT oldROP;
7903 if (infoPtr->xTrackLine == -1)
7904 return FALSE;
7906 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7907 return FALSE;
7908 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7909 oldROP = SetROP2(hdc, R2_XORPEN);
7910 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7911 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7912 SetROP2(hdc, oldROP);
7913 SelectObject(hdc, hOldPen);
7914 ReleaseDC(infoPtr->hwndSelf, hdc);
7915 return TRUE;
7918 /***
7919 * DESCRIPTION:
7920 * Called when an edit control should be displayed. This function is called after
7921 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7923 * PARAMETER(S):
7924 * [I] hwnd : Handle to the listview
7925 * [I] uMsg : WM_TIMER (ignored)
7926 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7927 * [I] dwTimer : The elapsed time (ignored)
7929 * RETURN:
7930 * None.
7932 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7934 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7935 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7937 KillTimer(hwnd, idEvent);
7938 editItem->fEnabled = FALSE;
7939 /* check if the item is still selected */
7940 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7941 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7944 /***
7945 * DESCRIPTION:
7946 * Creates the listview control - the WM_NCCREATE phase.
7948 * PARAMETER(S):
7949 * [I] hwnd : window handle
7950 * [I] lpcs : the create parameters
7952 * RETURN:
7953 * Success: TRUE
7954 * Failure: FALSE
7956 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7958 LISTVIEW_INFO *infoPtr;
7959 LOGFONTW logFont;
7961 TRACE("(lpcs=%p)\n", lpcs);
7963 /* initialize info pointer */
7964 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7965 if (!infoPtr) return FALSE;
7967 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7969 infoPtr->hwndSelf = hwnd;
7970 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7971 /* determine the type of structures to use */
7972 infoPtr->hwndNotify = lpcs->hwndParent;
7973 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7975 /* initialize color information */
7976 infoPtr->clrBk = CLR_NONE;
7977 infoPtr->clrText = CLR_DEFAULT;
7978 infoPtr->clrTextBk = CLR_DEFAULT;
7979 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7981 /* set default values */
7982 infoPtr->nFocusedItem = -1;
7983 infoPtr->nSelectionMark = -1;
7984 infoPtr->nHotItem = -1;
7985 infoPtr->bRedraw = TRUE;
7986 infoPtr->bNoItemMetrics = TRUE;
7987 infoPtr->bDoChangeNotify = TRUE;
7988 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7989 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7990 infoPtr->nEditLabelItem = -1;
7991 infoPtr->dwHoverTime = -1; /* default system hover time */
7992 infoPtr->nMeasureItemHeight = 0;
7993 infoPtr->xTrackLine = -1; /* no track line */
7994 infoPtr->itemEdit.fEnabled = FALSE;
7996 /* get default font (icon title) */
7997 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7998 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7999 infoPtr->hFont = infoPtr->hDefaultFont;
8000 LISTVIEW_SaveTextMetrics(infoPtr);
8002 /* allocate memory for the data structure */
8003 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8004 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8005 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8006 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8007 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8008 return TRUE;
8010 fail:
8011 DestroyWindow(infoPtr->hwndHeader);
8012 ranges_destroy(infoPtr->selectionRanges);
8013 DPA_Destroy(infoPtr->hdpaItems);
8014 DPA_Destroy(infoPtr->hdpaPosX);
8015 DPA_Destroy(infoPtr->hdpaPosY);
8016 DPA_Destroy(infoPtr->hdpaColumns);
8017 Free(infoPtr);
8018 return FALSE;
8021 /***
8022 * DESCRIPTION:
8023 * Creates the listview control - the WM_CREATE phase. Most of the data is
8024 * already set up in LISTVIEW_NCCreate
8026 * PARAMETER(S):
8027 * [I] hwnd : window handle
8028 * [I] lpcs : the create parameters
8030 * RETURN:
8031 * Success: 0
8032 * Failure: -1
8034 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8036 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8037 UINT uView = lpcs->style & LVS_TYPEMASK;
8038 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
8040 TRACE("(lpcs=%p)\n", lpcs);
8042 infoPtr->dwStyle = lpcs->style;
8043 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8044 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8046 /* setup creation flags */
8047 dFlags |= (LVS_NOSORTHEADER & lpcs->style) ? 0 : HDS_BUTTONS;
8048 dFlags |= (LVS_NOCOLUMNHEADER & lpcs->style) ? HDS_HIDDEN : 0;
8050 /* create header */
8051 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
8052 0, 0, 0, 0, hwnd, NULL,
8053 lpcs->hInstance, NULL);
8054 if (!infoPtr->hwndHeader) return -1;
8056 /* set header unicode format */
8057 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
8059 /* set header font */
8060 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8062 /* init item size to avoid division by 0 */
8063 LISTVIEW_UpdateItemSize (infoPtr);
8065 if (uView == LVS_REPORT)
8067 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8069 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8071 LISTVIEW_UpdateSize(infoPtr);
8072 LISTVIEW_UpdateScroll(infoPtr);
8075 OpenThemeData(hwnd, themeClass);
8077 /* initialize the icon sizes */
8078 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8079 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8080 return 0;
8083 /***
8084 * DESCRIPTION:
8085 * Destroys the listview control.
8087 * PARAMETER(S):
8088 * [I] infoPtr : valid pointer to the listview structure
8090 * RETURN:
8091 * Success: 0
8092 * Failure: -1
8094 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8096 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8097 CloseThemeData(theme);
8098 return 0;
8101 /***
8102 * DESCRIPTION:
8103 * Enables the listview control.
8105 * PARAMETER(S):
8106 * [I] infoPtr : valid pointer to the listview structure
8107 * [I] bEnable : specifies whether to enable or disable the window
8109 * RETURN:
8110 * SUCCESS : TRUE
8111 * FAILURE : FALSE
8113 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8115 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8116 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8117 return TRUE;
8120 /***
8121 * DESCRIPTION:
8122 * Erases the background of the listview control.
8124 * PARAMETER(S):
8125 * [I] infoPtr : valid pointer to the listview structure
8126 * [I] hdc : device context handle
8128 * RETURN:
8129 * SUCCESS : TRUE
8130 * FAILURE : FALSE
8132 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8134 RECT rc;
8136 TRACE("(hdc=%p)\n", hdc);
8138 if (!GetClipBox(hdc, &rc)) return FALSE;
8140 /* for double buffered controls we need to do this during refresh */
8141 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8143 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8147 /***
8148 * DESCRIPTION:
8149 * Helper function for LISTVIEW_[HV]Scroll *only*.
8150 * Performs vertical/horizontal scrolling by a give amount.
8152 * PARAMETER(S):
8153 * [I] infoPtr : valid pointer to the listview structure
8154 * [I] dx : amount of horizontal scroll
8155 * [I] dy : amount of vertical scroll
8157 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8159 /* now we can scroll the list */
8160 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8161 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8162 /* if we have focus, adjust rect */
8163 OffsetRect(&infoPtr->rcFocus, dx, dy);
8164 UpdateWindow(infoPtr->hwndSelf);
8167 /***
8168 * DESCRIPTION:
8169 * Performs vertical scrolling.
8171 * PARAMETER(S):
8172 * [I] infoPtr : valid pointer to the listview structure
8173 * [I] nScrollCode : scroll code
8174 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8175 * [I] hScrollWnd : scrollbar control window handle
8177 * RETURN:
8178 * Zero
8180 * NOTES:
8181 * SB_LINEUP/SB_LINEDOWN:
8182 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8183 * for LVS_REPORT is 1 line
8184 * for LVS_LIST cannot occur
8187 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8188 INT nScrollDiff, HWND hScrollWnd)
8190 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8191 INT nOldScrollPos, nNewScrollPos;
8192 SCROLLINFO scrollInfo;
8193 BOOL is_an_icon;
8195 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8196 debugscrollcode(nScrollCode), nScrollDiff);
8198 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8200 scrollInfo.cbSize = sizeof(SCROLLINFO);
8201 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8203 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8205 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8207 nOldScrollPos = scrollInfo.nPos;
8208 switch (nScrollCode)
8210 case SB_INTERNAL:
8211 break;
8213 case SB_LINEUP:
8214 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8215 break;
8217 case SB_LINEDOWN:
8218 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8219 break;
8221 case SB_PAGEUP:
8222 nScrollDiff = -scrollInfo.nPage;
8223 break;
8225 case SB_PAGEDOWN:
8226 nScrollDiff = scrollInfo.nPage;
8227 break;
8229 case SB_THUMBPOSITION:
8230 case SB_THUMBTRACK:
8231 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8232 break;
8234 default:
8235 nScrollDiff = 0;
8238 /* quit right away if pos isn't changing */
8239 if (nScrollDiff == 0) return 0;
8241 /* calculate new position, and handle overflows */
8242 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8243 if (nScrollDiff > 0) {
8244 if (nNewScrollPos < nOldScrollPos ||
8245 nNewScrollPos > scrollInfo.nMax)
8246 nNewScrollPos = scrollInfo.nMax;
8247 } else {
8248 if (nNewScrollPos > nOldScrollPos ||
8249 nNewScrollPos < scrollInfo.nMin)
8250 nNewScrollPos = scrollInfo.nMin;
8253 /* set the new position, and reread in case it changed */
8254 scrollInfo.fMask = SIF_POS;
8255 scrollInfo.nPos = nNewScrollPos;
8256 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8258 /* carry on only if it really changed */
8259 if (nNewScrollPos == nOldScrollPos) return 0;
8261 /* now adjust to client coordinates */
8262 nScrollDiff = nOldScrollPos - nNewScrollPos;
8263 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8265 /* and scroll the window */
8266 scroll_list(infoPtr, 0, nScrollDiff);
8268 return 0;
8271 /***
8272 * DESCRIPTION:
8273 * Performs horizontal scrolling.
8275 * PARAMETER(S):
8276 * [I] infoPtr : valid pointer to the listview structure
8277 * [I] nScrollCode : scroll code
8278 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8279 * [I] hScrollWnd : scrollbar control window handle
8281 * RETURN:
8282 * Zero
8284 * NOTES:
8285 * SB_LINELEFT/SB_LINERIGHT:
8286 * for LVS_ICON, LVS_SMALLICON 1 pixel
8287 * for LVS_REPORT is 1 pixel
8288 * for LVS_LIST is 1 column --> which is a 1 because the
8289 * scroll is based on columns not pixels
8292 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8293 INT nScrollDiff, HWND hScrollWnd)
8295 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8296 INT nOldScrollPos, nNewScrollPos;
8297 SCROLLINFO scrollInfo;
8299 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8300 debugscrollcode(nScrollCode), nScrollDiff);
8302 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8304 scrollInfo.cbSize = sizeof(SCROLLINFO);
8305 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8307 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8309 nOldScrollPos = scrollInfo.nPos;
8311 switch (nScrollCode)
8313 case SB_INTERNAL:
8314 break;
8316 case SB_LINELEFT:
8317 nScrollDiff = -1;
8318 break;
8320 case SB_LINERIGHT:
8321 nScrollDiff = 1;
8322 break;
8324 case SB_PAGELEFT:
8325 nScrollDiff = -scrollInfo.nPage;
8326 break;
8328 case SB_PAGERIGHT:
8329 nScrollDiff = scrollInfo.nPage;
8330 break;
8332 case SB_THUMBPOSITION:
8333 case SB_THUMBTRACK:
8334 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8335 break;
8337 default:
8338 nScrollDiff = 0;
8341 /* quit right away if pos isn't changing */
8342 if (nScrollDiff == 0) return 0;
8344 /* calculate new position, and handle overflows */
8345 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8346 if (nScrollDiff > 0) {
8347 if (nNewScrollPos < nOldScrollPos ||
8348 nNewScrollPos > scrollInfo.nMax)
8349 nNewScrollPos = scrollInfo.nMax;
8350 } else {
8351 if (nNewScrollPos > nOldScrollPos ||
8352 nNewScrollPos < scrollInfo.nMin)
8353 nNewScrollPos = scrollInfo.nMin;
8356 /* set the new position, and reread in case it changed */
8357 scrollInfo.fMask = SIF_POS;
8358 scrollInfo.nPos = nNewScrollPos;
8359 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8361 /* carry on only if it really changed */
8362 if (nNewScrollPos == nOldScrollPos) return 0;
8364 if(uView == LVS_REPORT)
8365 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8367 /* now adjust to client coordinates */
8368 nScrollDiff = nOldScrollPos - nNewScrollPos;
8369 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8371 /* and scroll the window */
8372 scroll_list(infoPtr, nScrollDiff, 0);
8374 return 0;
8377 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8379 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8380 INT gcWheelDelta = 0;
8381 INT pulScrollLines = 3;
8382 SCROLLINFO scrollInfo;
8384 TRACE("(wheelDelta=%d)\n", wheelDelta);
8386 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8387 gcWheelDelta -= wheelDelta;
8389 scrollInfo.cbSize = sizeof(SCROLLINFO);
8390 scrollInfo.fMask = SIF_POS;
8392 switch(uView)
8394 case LVS_ICON:
8395 case LVS_SMALLICON:
8397 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8398 * should be fixed in the future.
8400 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8401 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8402 break;
8404 case LVS_REPORT:
8405 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8407 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8408 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8409 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8411 break;
8413 case LVS_LIST:
8414 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8415 break;
8417 return 0;
8420 /***
8421 * DESCRIPTION:
8422 * ???
8424 * PARAMETER(S):
8425 * [I] infoPtr : valid pointer to the listview structure
8426 * [I] nVirtualKey : virtual key
8427 * [I] lKeyData : key data
8429 * RETURN:
8430 * Zero
8432 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8434 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8435 HWND hwndSelf = infoPtr->hwndSelf;
8436 INT nItem = -1;
8437 NMLVKEYDOWN nmKeyDown;
8439 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8441 /* send LVN_KEYDOWN notification */
8442 nmKeyDown.wVKey = nVirtualKey;
8443 nmKeyDown.flags = 0;
8444 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8445 if (!IsWindow(hwndSelf))
8446 return 0;
8448 switch (nVirtualKey)
8450 case VK_SPACE:
8451 nItem = infoPtr->nFocusedItem;
8452 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8453 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8454 break;
8456 case VK_RETURN:
8457 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8459 if (!notify(infoPtr, NM_RETURN)) return 0;
8460 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8462 break;
8464 case VK_HOME:
8465 if (infoPtr->nItemCount > 0)
8466 nItem = 0;
8467 break;
8469 case VK_END:
8470 if (infoPtr->nItemCount > 0)
8471 nItem = infoPtr->nItemCount - 1;
8472 break;
8474 case VK_LEFT:
8475 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8476 break;
8478 case VK_UP:
8479 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8480 break;
8482 case VK_RIGHT:
8483 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8484 break;
8486 case VK_DOWN:
8487 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8488 break;
8490 case VK_PRIOR:
8491 if (uView == LVS_REPORT)
8493 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8494 if (infoPtr->nFocusedItem == topidx)
8495 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8496 else
8497 nItem = topidx;
8499 else
8500 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8501 * LISTVIEW_GetCountPerRow(infoPtr);
8502 if(nItem < 0) nItem = 0;
8503 break;
8505 case VK_NEXT:
8506 if (uView == LVS_REPORT)
8508 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8509 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8510 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8511 nItem = infoPtr->nFocusedItem + cnt - 1;
8512 else
8513 nItem = topidx + cnt - 1;
8515 else
8516 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8517 * LISTVIEW_GetCountPerRow(infoPtr);
8518 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8519 break;
8522 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8523 LISTVIEW_KeySelection(infoPtr, nItem);
8525 return 0;
8528 /***
8529 * DESCRIPTION:
8530 * Kills the focus.
8532 * PARAMETER(S):
8533 * [I] infoPtr : valid pointer to the listview structure
8535 * RETURN:
8536 * Zero
8538 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8540 TRACE("()\n");
8542 /* if we did not have the focus, there's nothing to do */
8543 if (!infoPtr->bFocus) return 0;
8545 /* send NM_KILLFOCUS notification */
8546 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8548 /* if we have a focus rectagle, get rid of it */
8549 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8551 /* set window focus flag */
8552 infoPtr->bFocus = FALSE;
8554 /* invalidate the selected items before resetting focus flag */
8555 LISTVIEW_InvalidateSelectedItems(infoPtr);
8557 return 0;
8560 /***
8561 * DESCRIPTION:
8562 * Processes double click messages (left mouse button).
8564 * PARAMETER(S):
8565 * [I] infoPtr : valid pointer to the listview structure
8566 * [I] wKey : key flag
8567 * [I] x,y : mouse coordinate
8569 * RETURN:
8570 * Zero
8572 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8574 LVHITTESTINFO htInfo;
8576 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8578 /* Cancel the item edition if any */
8579 if (infoPtr->itemEdit.fEnabled)
8581 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8582 infoPtr->itemEdit.fEnabled = FALSE;
8585 /* send NM_RELEASEDCAPTURE notification */
8586 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8588 htInfo.pt.x = x;
8589 htInfo.pt.y = y;
8591 /* send NM_DBLCLK notification */
8592 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8593 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8595 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8596 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8598 return 0;
8601 /***
8602 * DESCRIPTION:
8603 * Processes mouse down messages (left mouse button).
8605 * PARAMETERS:
8606 * infoPtr [I ] valid pointer to the listview structure
8607 * wKey [I ] key flag
8608 * x,y [I ] mouse coordinate
8610 * RETURN:
8611 * Zero
8613 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8615 LVHITTESTINFO lvHitTestInfo;
8616 static BOOL bGroupSelect = TRUE;
8617 BOOL bReceivedFocus = FALSE;
8618 POINT pt = { x, y };
8619 INT nItem;
8621 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8623 /* send NM_RELEASEDCAPTURE notification */
8624 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8626 if (!infoPtr->bFocus)
8627 bReceivedFocus = TRUE;
8629 /* set left button down flag and record the click position */
8630 infoPtr->bLButtonDown = TRUE;
8631 infoPtr->ptClickPos = pt;
8632 infoPtr->bDragging = FALSE;
8634 lvHitTestInfo.pt.x = x;
8635 lvHitTestInfo.pt.y = y;
8637 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8638 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8639 infoPtr->nEditLabelItem = -1;
8640 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8642 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8644 toggle_checkbox_state(infoPtr, nItem);
8645 return 0;
8648 if (infoPtr->dwStyle & LVS_SINGLESEL)
8650 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8651 infoPtr->nEditLabelItem = nItem;
8652 else
8653 LISTVIEW_SetSelection(infoPtr, nItem);
8655 else
8657 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8659 if (bGroupSelect)
8661 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8662 LISTVIEW_SetItemFocus(infoPtr, nItem);
8663 infoPtr->nSelectionMark = nItem;
8665 else
8667 LVITEMW item;
8669 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8670 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8672 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8673 infoPtr->nSelectionMark = nItem;
8676 else if (wKey & MK_CONTROL)
8678 LVITEMW item;
8680 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8682 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8683 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8684 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8685 infoPtr->nSelectionMark = nItem;
8687 else if (wKey & MK_SHIFT)
8689 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8691 else
8693 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8694 infoPtr->nEditLabelItem = nItem;
8696 /* set selection (clears other pre-existing selections) */
8697 LISTVIEW_SetSelection(infoPtr, nItem);
8701 else
8703 /* remove all selections */
8704 LISTVIEW_DeselectAll(infoPtr);
8705 ReleaseCapture();
8708 if (bReceivedFocus)
8709 infoPtr->nEditLabelItem = -1;
8711 return 0;
8714 /***
8715 * DESCRIPTION:
8716 * Processes mouse up messages (left mouse button).
8718 * PARAMETERS:
8719 * infoPtr [I ] valid pointer to the listview structure
8720 * wKey [I ] key flag
8721 * x,y [I ] mouse coordinate
8723 * RETURN:
8724 * Zero
8726 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8728 LVHITTESTINFO lvHitTestInfo;
8730 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8732 if (!infoPtr->bLButtonDown) return 0;
8734 lvHitTestInfo.pt.x = x;
8735 lvHitTestInfo.pt.y = y;
8737 /* send NM_CLICK notification */
8738 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8739 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8741 /* set left button flag */
8742 infoPtr->bLButtonDown = FALSE;
8744 if (infoPtr->bDragging)
8746 infoPtr->bDragging = FALSE;
8747 return 0;
8750 /* if we clicked on a selected item, edit the label */
8751 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8753 /* we want to make sure the user doesn't want to do a double click. So we will
8754 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8756 infoPtr->itemEdit.fEnabled = TRUE;
8757 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8758 SetTimer(infoPtr->hwndSelf,
8759 (UINT_PTR)&infoPtr->itemEdit,
8760 GetDoubleClickTime(),
8761 LISTVIEW_DelayedEditItem);
8764 if (!infoPtr->bFocus)
8765 SetFocus(infoPtr->hwndSelf);
8767 return 0;
8770 /***
8771 * DESCRIPTION:
8772 * Destroys the listview control (called after WM_DESTROY).
8774 * PARAMETER(S):
8775 * [I] infoPtr : valid pointer to the listview structure
8777 * RETURN:
8778 * Zero
8780 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8782 TRACE("()\n");
8784 /* delete all items */
8785 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8787 /* destroy data structure */
8788 DPA_Destroy(infoPtr->hdpaItems);
8789 DPA_Destroy(infoPtr->hdpaPosX);
8790 DPA_Destroy(infoPtr->hdpaPosY);
8791 DPA_Destroy(infoPtr->hdpaColumns);
8792 ranges_destroy(infoPtr->selectionRanges);
8794 /* destroy image lists */
8795 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8797 if (infoPtr->himlNormal)
8798 ImageList_Destroy(infoPtr->himlNormal);
8799 if (infoPtr->himlSmall)
8800 ImageList_Destroy(infoPtr->himlSmall);
8801 if (infoPtr->himlState)
8802 ImageList_Destroy(infoPtr->himlState);
8805 /* destroy font, bkgnd brush */
8806 infoPtr->hFont = 0;
8807 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8808 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8810 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8812 /* free listview info pointer*/
8813 Free(infoPtr);
8815 return 0;
8818 /***
8819 * DESCRIPTION:
8820 * Handles notifications from header.
8822 * PARAMETER(S):
8823 * [I] infoPtr : valid pointer to the listview structure
8824 * [I] nCtrlId : control identifier
8825 * [I] lpnmh : notification information
8827 * RETURN:
8828 * Zero
8830 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8832 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8833 HWND hwndSelf = infoPtr->hwndSelf;
8835 TRACE("(lpnmh=%p)\n", lpnmh);
8837 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8839 switch (lpnmh->hdr.code)
8841 case HDN_TRACKW:
8842 case HDN_TRACKA:
8844 COLUMN_INFO *lpColumnInfo;
8845 POINT ptOrigin;
8846 INT x;
8848 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8849 break;
8851 /* remove the old line (if any) */
8852 LISTVIEW_DrawTrackLine(infoPtr);
8854 /* compute & draw the new line */
8855 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8856 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8857 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8858 infoPtr->xTrackLine = x + ptOrigin.x;
8859 LISTVIEW_DrawTrackLine(infoPtr);
8860 break;
8863 case HDN_ENDTRACKA:
8864 case HDN_ENDTRACKW:
8865 /* remove the track line (if any) */
8866 LISTVIEW_DrawTrackLine(infoPtr);
8867 infoPtr->xTrackLine = -1;
8868 break;
8870 case HDN_ENDDRAG:
8871 FIXME("Changing column order not implemented\n");
8872 return TRUE;
8874 case HDN_ITEMCHANGINGW:
8875 case HDN_ITEMCHANGINGA:
8876 return notify_forward_header(infoPtr, lpnmh);
8878 case HDN_ITEMCHANGEDW:
8879 case HDN_ITEMCHANGEDA:
8881 COLUMN_INFO *lpColumnInfo;
8882 INT dx, cxy;
8884 notify_forward_header(infoPtr, lpnmh);
8885 if (!IsWindow(hwndSelf))
8886 break;
8888 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8890 HDITEMW hdi;
8892 hdi.mask = HDI_WIDTH;
8893 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8894 cxy = hdi.cxy;
8896 else
8897 cxy = lpnmh->pitem->cxy;
8899 /* determine how much we change since the last know position */
8900 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8901 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8902 if (dx != 0)
8904 lpColumnInfo->rcHeader.right += dx;
8905 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8906 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8907 else
8909 /* only needs to update the scrolls */
8910 infoPtr->nItemWidth += dx;
8911 LISTVIEW_UpdateScroll(infoPtr);
8913 LISTVIEW_UpdateItemSize(infoPtr);
8914 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8916 POINT ptOrigin;
8917 RECT rcCol = lpColumnInfo->rcHeader;
8919 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8920 OffsetRect(&rcCol, ptOrigin.x, 0);
8922 rcCol.top = infoPtr->rcList.top;
8923 rcCol.bottom = infoPtr->rcList.bottom;
8925 /* resizing left-aligned columns leaves most of the left side untouched */
8926 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8928 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8929 if (dx > 0)
8930 nMaxDirty += dx;
8931 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8934 /* when shrinking the last column clear the now unused field */
8935 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8936 rcCol.right -= dx;
8938 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8942 break;
8944 case HDN_ITEMCLICKW:
8945 case HDN_ITEMCLICKA:
8947 /* Handle sorting by Header Column */
8948 NMLISTVIEW nmlv;
8950 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8951 nmlv.iItem = -1;
8952 nmlv.iSubItem = lpnmh->iItem;
8953 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8955 break;
8957 case HDN_DIVIDERDBLCLICKW:
8958 case HDN_DIVIDERDBLCLICKA:
8959 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8960 break;
8963 return 0;
8966 /***
8967 * DESCRIPTION:
8968 * Paint non-client area of control.
8970 * PARAMETER(S):
8971 * [I] infoPtr : valid pointer to the listview structureof the sender
8972 * [I] region : update region
8974 * RETURN:
8975 * TRUE - frame was painted
8976 * FALSE - call default window proc
8978 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8980 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8981 HDC dc;
8982 RECT r;
8983 HRGN cliprgn;
8984 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8985 cyEdge = GetSystemMetrics (SM_CYEDGE);
8987 if (!theme) return FALSE;
8989 GetWindowRect(infoPtr->hwndSelf, &r);
8991 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8992 r.right - cxEdge, r.bottom - cyEdge);
8993 if (region != (HRGN)1)
8994 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8995 OffsetRect(&r, -r.left, -r.top);
8997 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8998 OffsetRect(&r, -r.left, -r.top);
9000 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9001 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9002 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9003 ReleaseDC(infoPtr->hwndSelf, dc);
9005 /* Call default proc to get the scrollbars etc. painted */
9006 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9008 return TRUE;
9011 /***
9012 * DESCRIPTION:
9013 * Determines the type of structure to use.
9015 * PARAMETER(S):
9016 * [I] infoPtr : valid pointer to the listview structureof the sender
9017 * [I] hwndFrom : listview window handle
9018 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9020 * RETURN:
9021 * Zero
9023 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9025 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9027 if (nCommand == NF_REQUERY)
9028 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9030 return infoPtr->notifyFormat;
9033 /***
9034 * DESCRIPTION:
9035 * Paints/Repaints the listview control.
9037 * PARAMETER(S):
9038 * [I] infoPtr : valid pointer to the listview structure
9039 * [I] hdc : device context handle
9041 * RETURN:
9042 * Zero
9044 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9046 TRACE("(hdc=%p)\n", hdc);
9048 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9050 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9052 infoPtr->bNoItemMetrics = FALSE;
9053 LISTVIEW_UpdateItemSize(infoPtr);
9054 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9055 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9056 LISTVIEW_UpdateScroll(infoPtr);
9059 UpdateWindow(infoPtr->hwndHeader);
9061 if (hdc)
9062 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9063 else
9065 PAINTSTRUCT ps;
9067 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9068 if (!hdc) return 1;
9069 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9070 EndPaint(infoPtr->hwndSelf, &ps);
9073 return 0;
9077 /***
9078 * DESCRIPTION:
9079 * Paints/Repaints the listview control.
9081 * PARAMETER(S):
9082 * [I] infoPtr : valid pointer to the listview structure
9083 * [I] hdc : device context handle
9084 * [I] options : drawing options
9086 * RETURN:
9087 * Zero
9089 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9091 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9093 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9094 return 0;
9096 if (options & PRF_ERASEBKGND)
9097 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9099 if (options & PRF_CLIENT)
9100 LISTVIEW_Paint(infoPtr, hdc);
9102 return 0;
9106 /***
9107 * DESCRIPTION:
9108 * Processes double click messages (right mouse button).
9110 * PARAMETER(S):
9111 * [I] infoPtr : valid pointer to the listview structure
9112 * [I] wKey : key flag
9113 * [I] x,y : mouse coordinate
9115 * RETURN:
9116 * Zero
9118 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9120 LVHITTESTINFO lvHitTestInfo;
9122 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9124 /* send NM_RELEASEDCAPTURE notification */
9125 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9127 /* send NM_RDBLCLK notification */
9128 lvHitTestInfo.pt.x = x;
9129 lvHitTestInfo.pt.y = y;
9130 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9131 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9133 return 0;
9136 /***
9137 * DESCRIPTION:
9138 * Processes mouse down messages (right mouse button).
9140 * PARAMETER(S):
9141 * [I] infoPtr : valid pointer to the listview structure
9142 * [I] wKey : key flag
9143 * [I] x,y : mouse coordinate
9145 * RETURN:
9146 * Zero
9148 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9150 LVHITTESTINFO lvHitTestInfo;
9151 INT nItem;
9153 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9155 /* send NM_RELEASEDCAPTURE notification */
9156 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9158 /* make sure the listview control window has the focus */
9159 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9161 /* set right button down flag */
9162 infoPtr->bRButtonDown = TRUE;
9164 /* determine the index of the selected item */
9165 lvHitTestInfo.pt.x = x;
9166 lvHitTestInfo.pt.y = y;
9167 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9169 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9171 LISTVIEW_SetItemFocus(infoPtr, nItem);
9172 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9173 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9174 LISTVIEW_SetSelection(infoPtr, nItem);
9176 else
9178 LISTVIEW_DeselectAll(infoPtr);
9181 return 0;
9184 /***
9185 * DESCRIPTION:
9186 * Processes mouse up messages (right mouse button).
9188 * PARAMETER(S):
9189 * [I] infoPtr : valid pointer to the listview structure
9190 * [I] wKey : key flag
9191 * [I] x,y : mouse coordinate
9193 * RETURN:
9194 * Zero
9196 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9198 LVHITTESTINFO lvHitTestInfo;
9199 POINT pt;
9201 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9203 if (!infoPtr->bRButtonDown) return 0;
9205 /* set button flag */
9206 infoPtr->bRButtonDown = FALSE;
9208 /* Send NM_RClICK notification */
9209 lvHitTestInfo.pt.x = x;
9210 lvHitTestInfo.pt.y = y;
9211 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9212 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9214 /* Change to screen coordinate for WM_CONTEXTMENU */
9215 pt = lvHitTestInfo.pt;
9216 ClientToScreen(infoPtr->hwndSelf, &pt);
9218 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9219 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9220 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9222 return 0;
9226 /***
9227 * DESCRIPTION:
9228 * Sets the cursor.
9230 * PARAMETER(S):
9231 * [I] infoPtr : valid pointer to the listview structure
9232 * [I] hwnd : window handle of window containing the cursor
9233 * [I] nHittest : hit-test code
9234 * [I] wMouseMsg : ideintifier of the mouse message
9236 * RETURN:
9237 * TRUE if cursor is set
9238 * FALSE otherwise
9240 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9242 LVHITTESTINFO lvHitTestInfo;
9244 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9246 if(!infoPtr->hHotCursor) return FALSE;
9248 GetCursorPos(&lvHitTestInfo.pt);
9249 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9251 SetCursor(infoPtr->hHotCursor);
9253 return TRUE;
9256 /***
9257 * DESCRIPTION:
9258 * Sets the focus.
9260 * PARAMETER(S):
9261 * [I] infoPtr : valid pointer to the listview structure
9262 * [I] hwndLoseFocus : handle of previously focused window
9264 * RETURN:
9265 * Zero
9267 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9269 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9271 /* if we have the focus already, there's nothing to do */
9272 if (infoPtr->bFocus) return 0;
9274 /* send NM_SETFOCUS notification */
9275 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9277 /* set window focus flag */
9278 infoPtr->bFocus = TRUE;
9280 /* put the focus rect back on */
9281 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9283 /* redraw all visible selected items */
9284 LISTVIEW_InvalidateSelectedItems(infoPtr);
9286 return 0;
9289 /***
9290 * DESCRIPTION:
9291 * Sets the font.
9293 * PARAMETER(S):
9294 * [I] infoPtr : valid pointer to the listview structure
9295 * [I] fRedraw : font handle
9296 * [I] fRedraw : redraw flag
9298 * RETURN:
9299 * Zero
9301 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9303 HFONT oldFont = infoPtr->hFont;
9305 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9307 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9308 if (infoPtr->hFont == oldFont) return 0;
9310 LISTVIEW_SaveTextMetrics(infoPtr);
9312 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9314 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9315 LISTVIEW_UpdateSize(infoPtr);
9316 LISTVIEW_UpdateScroll(infoPtr);
9319 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9321 return 0;
9324 /***
9325 * DESCRIPTION:
9326 * Message handling for WM_SETREDRAW.
9327 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9329 * PARAMETER(S):
9330 * [I] infoPtr : valid pointer to the listview structure
9331 * [I] bRedraw: state of redraw flag
9333 * RETURN:
9334 * DefWinProc return value
9336 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9338 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9340 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9341 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9343 infoPtr->bRedraw = bRedraw;
9345 if(!bRedraw) return 0;
9347 if (is_autoarrange(infoPtr))
9348 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9349 LISTVIEW_UpdateScroll(infoPtr);
9351 /* despite what the WM_SETREDRAW docs says, apps expect us
9352 * to invalidate the listview here... stupid! */
9353 LISTVIEW_InvalidateList(infoPtr);
9355 return 0;
9358 /***
9359 * DESCRIPTION:
9360 * Resizes the listview control. This function processes WM_SIZE
9361 * messages. At this time, the width and height are not used.
9363 * PARAMETER(S):
9364 * [I] infoPtr : valid pointer to the listview structure
9365 * [I] Width : new width
9366 * [I] Height : new height
9368 * RETURN:
9369 * Zero
9371 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9373 RECT rcOld = infoPtr->rcList;
9375 TRACE("(width=%d, height=%d)\n", Width, Height);
9377 LISTVIEW_UpdateSize(infoPtr);
9378 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9380 /* do not bother with display related stuff if we're not redrawing */
9381 if (!is_redrawing(infoPtr)) return 0;
9383 if (is_autoarrange(infoPtr))
9384 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9386 LISTVIEW_UpdateScroll(infoPtr);
9388 /* refresh all only for lists whose height changed significantly */
9389 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9390 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9391 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9392 LISTVIEW_InvalidateList(infoPtr);
9394 return 0;
9397 /***
9398 * DESCRIPTION:
9399 * Sets the size information.
9401 * PARAMETER(S):
9402 * [I] infoPtr : valid pointer to the listview structure
9404 * RETURN:
9405 * None
9407 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9409 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9411 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9413 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9415 if (uView == LVS_LIST)
9417 /* Apparently the "LIST" style is supposed to have the same
9418 * number of items in a column even if there is no scroll bar.
9419 * Since if a scroll bar already exists then the bottom is already
9420 * reduced, only reduce if the scroll bar does not currently exist.
9421 * The "2" is there to mimic the native control. I think it may be
9422 * related to either padding or edges. (GLA 7/2002)
9424 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9425 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9426 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9428 else if (uView == LVS_REPORT)
9430 HDLAYOUT hl;
9431 WINDOWPOS wp;
9433 hl.prc = &infoPtr->rcList;
9434 hl.pwpos = &wp;
9435 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9436 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9437 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9438 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9439 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9440 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9442 infoPtr->rcList.top = max(wp.cy, 0);
9443 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9446 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9449 /***
9450 * DESCRIPTION:
9451 * Processes WM_STYLECHANGED messages.
9453 * PARAMETER(S):
9454 * [I] infoPtr : valid pointer to the listview structure
9455 * [I] wStyleType : window style type (normal or extended)
9456 * [I] lpss : window style information
9458 * RETURN:
9459 * Zero
9461 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9462 const STYLESTRUCT *lpss)
9464 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9465 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9466 UINT style;
9468 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9469 wStyleType, lpss->styleOld, lpss->styleNew);
9471 if (wStyleType != GWL_STYLE) return 0;
9473 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9474 /* what if LVS_OWNERDATA changed? */
9475 /* or LVS_SINGLESEL */
9476 /* or LVS_SORT{AS,DES}CENDING */
9478 infoPtr->dwStyle = lpss->styleNew;
9480 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9481 ((lpss->styleNew & WS_HSCROLL) == 0))
9482 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9484 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9485 ((lpss->styleNew & WS_VSCROLL) == 0))
9486 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9488 if (uNewView != uOldView)
9490 SIZE oldIconSize = infoPtr->iconSize;
9491 HIMAGELIST himl;
9493 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9494 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9496 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9497 SetRectEmpty(&infoPtr->rcFocus);
9499 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9500 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9502 if (uNewView == LVS_ICON)
9504 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9506 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9507 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9508 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9511 else if (uNewView == LVS_REPORT)
9513 HDLAYOUT hl;
9514 WINDOWPOS wp;
9516 hl.prc = &infoPtr->rcList;
9517 hl.pwpos = &wp;
9518 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9519 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9520 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9521 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9524 LISTVIEW_UpdateItemSize(infoPtr);
9527 if (uNewView == LVS_REPORT)
9529 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9531 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9533 /* Turn off the header control */
9534 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9535 TRACE("Hide header control, was 0x%08x\n", style);
9536 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9537 } else {
9538 /* Turn on the header control */
9539 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9541 TRACE("Show header control, was 0x%08x\n", style);
9542 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9548 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9549 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9550 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9552 /* update the size of the client area */
9553 LISTVIEW_UpdateSize(infoPtr);
9555 /* add scrollbars if needed */
9556 LISTVIEW_UpdateScroll(infoPtr);
9558 /* invalidate client area + erase background */
9559 LISTVIEW_InvalidateList(infoPtr);
9561 return 0;
9564 /***
9565 * DESCRIPTION:
9566 * Window procedure of the listview control.
9569 static LRESULT WINAPI
9570 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9572 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9574 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9576 if (!infoPtr && (uMsg != WM_NCCREATE))
9577 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9579 switch (uMsg)
9581 case LVM_APPROXIMATEVIEWRECT:
9582 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9583 LOWORD(lParam), HIWORD(lParam));
9584 case LVM_ARRANGE:
9585 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9587 /* case LVM_CANCELEDITLABEL: */
9589 case LVM_CREATEDRAGIMAGE:
9590 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9592 case LVM_DELETEALLITEMS:
9593 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9595 case LVM_DELETECOLUMN:
9596 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9598 case LVM_DELETEITEM:
9599 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9601 case LVM_EDITLABELW:
9602 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9604 case LVM_EDITLABELA:
9605 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9607 /* case LVM_ENABLEGROUPVIEW: */
9609 case LVM_ENSUREVISIBLE:
9610 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9612 case LVM_FINDITEMW:
9613 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9615 case LVM_FINDITEMA:
9616 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9618 case LVM_GETBKCOLOR:
9619 return infoPtr->clrBk;
9621 /* case LVM_GETBKIMAGE: */
9623 case LVM_GETCALLBACKMASK:
9624 return infoPtr->uCallbackMask;
9626 case LVM_GETCOLUMNA:
9627 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9629 case LVM_GETCOLUMNW:
9630 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9632 case LVM_GETCOLUMNORDERARRAY:
9633 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9635 case LVM_GETCOLUMNWIDTH:
9636 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9638 case LVM_GETCOUNTPERPAGE:
9639 return LISTVIEW_GetCountPerPage(infoPtr);
9641 case LVM_GETEDITCONTROL:
9642 return (LRESULT)infoPtr->hwndEdit;
9644 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9645 return infoPtr->dwLvExStyle;
9647 /* case LVM_GETGROUPINFO: */
9649 /* case LVM_GETGROUPMETRICS: */
9651 case LVM_GETHEADER:
9652 return (LRESULT)infoPtr->hwndHeader;
9654 case LVM_GETHOTCURSOR:
9655 return (LRESULT)infoPtr->hHotCursor;
9657 case LVM_GETHOTITEM:
9658 return infoPtr->nHotItem;
9660 case LVM_GETHOVERTIME:
9661 return infoPtr->dwHoverTime;
9663 case LVM_GETIMAGELIST:
9664 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9666 /* case LVM_GETINSERTMARK: */
9668 /* case LVM_GETINSERTMARKCOLOR: */
9670 /* case LVM_GETINSERTMARKRECT: */
9672 case LVM_GETISEARCHSTRINGA:
9673 case LVM_GETISEARCHSTRINGW:
9674 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9675 return FALSE;
9677 case LVM_GETITEMA:
9678 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9680 case LVM_GETITEMW:
9681 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9683 case LVM_GETITEMCOUNT:
9684 return infoPtr->nItemCount;
9686 case LVM_GETITEMPOSITION:
9687 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9689 case LVM_GETITEMRECT:
9690 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9692 case LVM_GETITEMSPACING:
9693 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9695 case LVM_GETITEMSTATE:
9696 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9698 case LVM_GETITEMTEXTA:
9699 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9701 case LVM_GETITEMTEXTW:
9702 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9704 case LVM_GETNEXTITEM:
9705 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9707 case LVM_GETNUMBEROFWORKAREAS:
9708 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9709 return 1;
9711 case LVM_GETORIGIN:
9712 if (!lParam) return FALSE;
9713 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9714 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9715 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9716 return TRUE;
9718 /* case LVM_GETOUTLINECOLOR: */
9720 /* case LVM_GETSELECTEDCOLUMN: */
9722 case LVM_GETSELECTEDCOUNT:
9723 return LISTVIEW_GetSelectedCount(infoPtr);
9725 case LVM_GETSELECTIONMARK:
9726 return infoPtr->nSelectionMark;
9728 case LVM_GETSTRINGWIDTHA:
9729 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9731 case LVM_GETSTRINGWIDTHW:
9732 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9734 case LVM_GETSUBITEMRECT:
9735 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9737 case LVM_GETTEXTBKCOLOR:
9738 return infoPtr->clrTextBk;
9740 case LVM_GETTEXTCOLOR:
9741 return infoPtr->clrText;
9743 /* case LVM_GETTILEINFO: */
9745 /* case LVM_GETTILEVIEWINFO: */
9747 case LVM_GETTOOLTIPS:
9748 if( !infoPtr->hwndToolTip )
9749 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9750 return (LRESULT)infoPtr->hwndToolTip;
9752 case LVM_GETTOPINDEX:
9753 return LISTVIEW_GetTopIndex(infoPtr);
9755 case LVM_GETUNICODEFORMAT:
9756 return (infoPtr->notifyFormat == NFR_UNICODE);
9758 /* case LVM_GETVIEW: */
9760 case LVM_GETVIEWRECT:
9761 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9763 case LVM_GETWORKAREAS:
9764 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9765 return FALSE;
9767 /* case LVM_HASGROUP: */
9769 case LVM_HITTEST:
9770 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9772 case LVM_INSERTCOLUMNA:
9773 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9775 case LVM_INSERTCOLUMNW:
9776 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9778 /* case LVM_INSERTGROUP: */
9780 /* case LVM_INSERTGROUPSORTED: */
9782 case LVM_INSERTITEMA:
9783 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9785 case LVM_INSERTITEMW:
9786 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9788 /* case LVM_INSERTMARKHITTEST: */
9790 /* case LVM_ISGROUPVIEWENABLED: */
9792 /* case LVM_MAPIDTOINDEX: */
9794 /* case LVM_MAPINDEXTOID: */
9796 /* case LVM_MOVEGROUP: */
9798 /* case LVM_MOVEITEMTOGROUP: */
9800 case LVM_REDRAWITEMS:
9801 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9803 /* case LVM_REMOVEALLGROUPS: */
9805 /* case LVM_REMOVEGROUP: */
9807 case LVM_SCROLL:
9808 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9810 case LVM_SETBKCOLOR:
9811 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9813 /* case LVM_SETBKIMAGE: */
9815 case LVM_SETCALLBACKMASK:
9816 infoPtr->uCallbackMask = (UINT)wParam;
9817 return TRUE;
9819 case LVM_SETCOLUMNA:
9820 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9822 case LVM_SETCOLUMNW:
9823 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9825 case LVM_SETCOLUMNORDERARRAY:
9826 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9828 case LVM_SETCOLUMNWIDTH:
9829 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9831 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9832 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9834 /* case LVM_SETGROUPINFO: */
9836 /* case LVM_SETGROUPMETRICS: */
9838 case LVM_SETHOTCURSOR:
9839 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9841 case LVM_SETHOTITEM:
9842 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9844 case LVM_SETHOVERTIME:
9845 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9847 case LVM_SETICONSPACING:
9848 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9850 case LVM_SETIMAGELIST:
9851 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9853 /* case LVM_SETINFOTIP: */
9855 /* case LVM_SETINSERTMARK: */
9857 /* case LVM_SETINSERTMARKCOLOR: */
9859 case LVM_SETITEMA:
9860 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9862 case LVM_SETITEMW:
9863 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9865 case LVM_SETITEMCOUNT:
9866 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9868 case LVM_SETITEMPOSITION:
9870 POINT pt;
9871 pt.x = (short)LOWORD(lParam);
9872 pt.y = (short)HIWORD(lParam);
9873 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9876 case LVM_SETITEMPOSITION32:
9877 if (lParam == 0) return FALSE;
9878 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9880 case LVM_SETITEMSTATE:
9881 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9883 case LVM_SETITEMTEXTA:
9884 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9886 case LVM_SETITEMTEXTW:
9887 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9889 /* case LVM_SETOUTLINECOLOR: */
9891 /* case LVM_SETSELECTEDCOLUMN: */
9893 case LVM_SETSELECTIONMARK:
9894 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9896 case LVM_SETTEXTBKCOLOR:
9897 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9899 case LVM_SETTEXTCOLOR:
9900 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9902 /* case LVM_SETTILEINFO: */
9904 /* case LVM_SETTILEVIEWINFO: */
9906 /* case LVM_SETTILEWIDTH: */
9908 case LVM_SETTOOLTIPS:
9909 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9911 case LVM_SETUNICODEFORMAT:
9912 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9914 /* case LVM_SETVIEW: */
9916 /* case LVM_SETWORKAREAS: */
9918 /* case LVM_SORTGROUPS: */
9920 case LVM_SORTITEMS:
9921 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9923 /* LVM_SORTITEMSEX: */
9925 case LVM_SUBITEMHITTEST:
9926 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9928 case LVM_UPDATE:
9929 return LISTVIEW_Update(infoPtr, (INT)wParam);
9931 case WM_CHAR:
9932 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9934 case WM_COMMAND:
9935 return LISTVIEW_Command(infoPtr, wParam, lParam);
9937 case WM_NCCREATE:
9938 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9940 case WM_CREATE:
9941 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9943 case WM_DESTROY:
9944 return LISTVIEW_Destroy(infoPtr);
9946 case WM_ENABLE:
9947 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9949 case WM_ERASEBKGND:
9950 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9952 case WM_GETDLGCODE:
9953 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9955 case WM_GETFONT:
9956 return (LRESULT)infoPtr->hFont;
9958 case WM_HSCROLL:
9959 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9961 case WM_KEYDOWN:
9962 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9964 case WM_KILLFOCUS:
9965 return LISTVIEW_KillFocus(infoPtr);
9967 case WM_LBUTTONDBLCLK:
9968 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9970 case WM_LBUTTONDOWN:
9971 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9973 case WM_LBUTTONUP:
9974 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9976 case WM_MOUSEMOVE:
9977 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9979 case WM_MOUSEHOVER:
9980 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9982 case WM_NCDESTROY:
9983 return LISTVIEW_NCDestroy(infoPtr);
9985 case WM_NCPAINT:
9986 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9987 return 0;
9988 goto fwd_msg;
9990 case WM_NOTIFY:
9991 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9992 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9993 else return 0;
9995 case WM_NOTIFYFORMAT:
9996 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9998 case WM_PRINTCLIENT:
9999 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10001 case WM_PAINT:
10002 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10004 case WM_RBUTTONDBLCLK:
10005 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10007 case WM_RBUTTONDOWN:
10008 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10010 case WM_RBUTTONUP:
10011 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10013 case WM_SETCURSOR:
10014 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10015 return TRUE;
10016 goto fwd_msg;
10018 case WM_SETFOCUS:
10019 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10021 case WM_SETFONT:
10022 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10024 case WM_SETREDRAW:
10025 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10027 case WM_SIZE:
10028 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10030 case WM_STYLECHANGED:
10031 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10033 case WM_SYSCOLORCHANGE:
10034 COMCTL32_RefreshSysColors();
10035 return 0;
10037 /* case WM_TIMER: */
10038 case WM_THEMECHANGED:
10039 return LISTVIEW_ThemeChanged(infoPtr);
10041 case WM_VSCROLL:
10042 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10044 case WM_MOUSEWHEEL:
10045 if (wParam & (MK_SHIFT | MK_CONTROL))
10046 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10047 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10049 case WM_WINDOWPOSCHANGED:
10050 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10052 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10053 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10054 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10056 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10058 MEASUREITEMSTRUCT mis;
10059 mis.CtlType = ODT_LISTVIEW;
10060 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10061 mis.itemID = -1;
10062 mis.itemWidth = 0;
10063 mis.itemData = 0;
10064 mis.itemHeight= infoPtr->nItemHeight;
10065 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10066 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10067 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10070 LISTVIEW_UpdateSize(infoPtr);
10071 LISTVIEW_UpdateScroll(infoPtr);
10073 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10075 /* case WM_WININICHANGE: */
10077 default:
10078 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10079 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10081 fwd_msg:
10082 /* call default window procedure */
10083 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10088 /***
10089 * DESCRIPTION:
10090 * Registers the window class.
10092 * PARAMETER(S):
10093 * None
10095 * RETURN:
10096 * None
10098 void LISTVIEW_Register(void)
10100 WNDCLASSW wndClass;
10102 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10103 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10104 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10105 wndClass.cbClsExtra = 0;
10106 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10107 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10108 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10109 wndClass.lpszClassName = WC_LISTVIEWW;
10110 RegisterClassW(&wndClass);
10113 /***
10114 * DESCRIPTION:
10115 * Unregisters the window class.
10117 * PARAMETER(S):
10118 * None
10120 * RETURN:
10121 * None
10123 void LISTVIEW_Unregister(void)
10125 UnregisterClassW(WC_LISTVIEWW, NULL);
10128 /***
10129 * DESCRIPTION:
10130 * Handle any WM_COMMAND messages
10132 * PARAMETER(S):
10133 * [I] infoPtr : valid pointer to the listview structure
10134 * [I] wParam : the first message parameter
10135 * [I] lParam : the second message parameter
10137 * RETURN:
10138 * Zero.
10140 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10142 switch (HIWORD(wParam))
10144 case EN_UPDATE:
10147 * Adjust the edit window size
10149 WCHAR buffer[1024];
10150 HDC hdc = GetDC(infoPtr->hwndEdit);
10151 HFONT hFont, hOldFont = 0;
10152 RECT rect;
10153 SIZE sz;
10155 if (!infoPtr->hwndEdit || !hdc) return 0;
10156 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10157 GetWindowRect(infoPtr->hwndEdit, &rect);
10159 /* Select font to get the right dimension of the string */
10160 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10161 if(hFont != 0)
10163 hOldFont = SelectObject(hdc, hFont);
10166 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10168 TEXTMETRICW textMetric;
10170 /* Add Extra spacing for the next character */
10171 GetTextMetricsW(hdc, &textMetric);
10172 sz.cx += (textMetric.tmMaxCharWidth * 2);
10174 SetWindowPos (
10175 infoPtr->hwndEdit,
10176 HWND_TOP,
10179 sz.cx,
10180 rect.bottom - rect.top,
10181 SWP_DRAWFRAME|SWP_NOMOVE);
10183 if(hFont != 0)
10184 SelectObject(hdc, hOldFont);
10186 ReleaseDC(infoPtr->hwndEdit, hdc);
10188 break;
10191 default:
10192 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10195 return 0;
10199 /***
10200 * DESCRIPTION:
10201 * Subclassed edit control windproc function
10203 * PARAMETER(S):
10204 * [I] hwnd : the edit window handle
10205 * [I] uMsg : the message that is to be processed
10206 * [I] wParam : first message parameter
10207 * [I] lParam : second message parameter
10208 * [I] isW : TRUE if input is Unicode
10210 * RETURN:
10211 * Zero.
10213 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10215 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10216 BOOL cancel = FALSE;
10218 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10219 hwnd, uMsg, wParam, lParam, isW);
10221 switch (uMsg)
10223 case WM_GETDLGCODE:
10224 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10226 case WM_KILLFOCUS:
10227 break;
10229 case WM_DESTROY:
10231 WNDPROC editProc = infoPtr->EditWndProc;
10232 infoPtr->EditWndProc = 0;
10233 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10234 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10237 case WM_KEYDOWN:
10238 if (VK_ESCAPE == (INT)wParam)
10240 cancel = TRUE;
10241 break;
10243 else if (VK_RETURN == (INT)wParam)
10244 break;
10246 default:
10247 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10250 /* kill the edit */
10251 if (infoPtr->hwndEdit)
10253 LPWSTR buffer = NULL;
10255 infoPtr->hwndEdit = 0;
10256 if (!cancel)
10258 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10260 if (len)
10262 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10264 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10265 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10269 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10271 Free(buffer);
10274 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10275 return 0;
10278 /***
10279 * DESCRIPTION:
10280 * Subclassed edit control Unicode windproc function
10282 * PARAMETER(S):
10283 * [I] hwnd : the edit window handle
10284 * [I] uMsg : the message that is to be processed
10285 * [I] wParam : first message parameter
10286 * [I] lParam : second message parameter
10288 * RETURN:
10290 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10292 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10295 /***
10296 * DESCRIPTION:
10297 * Subclassed edit control ANSI windproc function
10299 * PARAMETER(S):
10300 * [I] hwnd : the edit window handle
10301 * [I] uMsg : the message that is to be processed
10302 * [I] wParam : first message parameter
10303 * [I] lParam : second message parameter
10305 * RETURN:
10307 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10309 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10312 /***
10313 * DESCRIPTION:
10314 * Creates a subclassed edit control
10316 * PARAMETER(S):
10317 * [I] infoPtr : valid pointer to the listview structure
10318 * [I] text : initial text for the edit
10319 * [I] style : the window style
10320 * [I] isW : TRUE if input is Unicode
10322 * RETURN:
10324 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10325 INT x, INT y, INT width, INT height, BOOL isW)
10327 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10328 HWND hedit;
10329 SIZE sz;
10330 HDC hdc;
10331 HDC hOldFont=0;
10332 TEXTMETRICW textMetric;
10333 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10335 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10337 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10338 hdc = GetDC(infoPtr->hwndSelf);
10340 /* Select the font to get appropriate metric dimensions */
10341 if(infoPtr->hFont != 0)
10342 hOldFont = SelectObject(hdc, infoPtr->hFont);
10344 /*Get String Length in pixels */
10345 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10347 /*Add Extra spacing for the next character */
10348 GetTextMetricsW(hdc, &textMetric);
10349 sz.cx += (textMetric.tmMaxCharWidth * 2);
10351 if(infoPtr->hFont != 0)
10352 SelectObject(hdc, hOldFont);
10354 ReleaseDC(infoPtr->hwndSelf, hdc);
10355 if (isW)
10356 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10357 else
10358 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10360 if (!hedit) return 0;
10362 infoPtr->EditWndProc = (WNDPROC)
10363 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10364 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10366 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10368 return hedit;