comctl32: Cast-qual warnings fix.
[wine.git] / dlls / comctl32 / listview.c
blob68a1ddbf6958f975393d3567f873e716f1efa1ba
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_GRIDLINES
95 * -- LVS_EX_HEADERDRAGDROP
96 * -- LVS_EX_INFOTIP
97 * -- LVS_EX_LABELTIP
98 * -- LVS_EX_MULTIWORKAREAS
99 * -- LVS_EX_ONECLICKACTIVATE
100 * -- LVS_EX_REGIONAL
101 * -- LVS_EX_SIMPLESELECT
102 * -- LVS_EX_TRACKSELECT
103 * -- LVS_EX_TWOCLICKACTIVATE
104 * -- LVS_EX_UNDERLINECOLD
105 * -- LVS_EX_UNDERLINEHOT
107 * Notifications:
108 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
109 * -- LVN_GETINFOTIP
110 * -- LVN_HOTTRACK
111 * -- LVN_MARQUEEBEGIN
112 * -- LVN_ODFINDITEM
113 * -- LVN_SETDISPINFO
114 * -- NM_HOVER
115 * -- LVN_BEGINRDRAG
117 * Messages:
118 * -- LVM_CANCELEDITLABEL
119 * -- LVM_ENABLEGROUPVIEW
120 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
121 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
122 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
123 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
124 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
125 * -- LVM_GETINSERTMARKRECT
126 * -- LVM_GETNUMBEROFWORKAREAS
127 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
128 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
129 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
130 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
131 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
132 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
133 * -- LVM_GETVIEW, LVM_SETVIEW
134 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
135 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
136 * -- LVM_INSERTGROUPSORTED
137 * -- LVM_INSERTMARKHITTEST
138 * -- LVM_ISGROUPVIEWENABLED
139 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
140 * -- LVM_MOVEGROUP
141 * -- LVM_MOVEITEMTOGROUP
142 * -- LVM_SETINFOTIP
143 * -- LVM_SETTILEWIDTH
144 * -- LVM_SORTGROUPS
145 * -- LVM_SORTITEMSEX
147 * Macros:
148 * -- ListView_GetCheckSate, ListView_SetCheckState
149 * -- ListView_GetHoverTime, ListView_SetHoverTime
150 * -- ListView_GetISearchString
151 * -- ListView_GetNumberOfWorkAreas
152 * -- ListView_GetOrigin
153 * -- ListView_GetTextBkColor
154 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
155 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
156 * -- ListView_SortItemsEx
158 * Functions:
159 * -- LVGroupComparE
161 * Known differences in message stream from native control (not known if
162 * these differences cause problems):
163 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
164 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
165 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
166 * processing for "USEDOUBLECLICKTIME".
169 #include "config.h"
170 #include "wine/port.h"
172 #include <assert.h>
173 #include <ctype.h>
174 #include <string.h>
175 #include <stdlib.h>
176 #include <stdarg.h>
177 #include <stdio.h>
179 #include "windef.h"
180 #include "winbase.h"
181 #include "winnt.h"
182 #include "wingdi.h"
183 #include "winuser.h"
184 #include "winnls.h"
185 #include "commctrl.h"
186 #include "comctl32.h"
187 #include "uxtheme.h"
189 #include "wine/debug.h"
190 #include "wine/unicode.h"
192 WINE_DEFAULT_DEBUG_CHANNEL(listview);
194 /* make sure you set this to 0 for production use! */
195 #define DEBUG_RANGES 1
197 typedef struct tagCOLUMN_INFO
199 RECT rcHeader; /* tracks the header's rectangle */
200 int fmt; /* same as LVCOLUMN.fmt */
201 } COLUMN_INFO;
203 typedef struct tagITEMHDR
205 LPWSTR pszText;
206 INT iImage;
207 } ITEMHDR, *LPITEMHDR;
209 typedef struct tagSUBITEM_INFO
211 ITEMHDR hdr;
212 INT iSubItem;
213 } SUBITEM_INFO;
215 typedef struct tagITEM_INFO
217 ITEMHDR hdr;
218 UINT state;
219 LPARAM lParam;
220 INT iIndent;
221 } ITEM_INFO;
223 typedef struct tagRANGE
225 INT lower;
226 INT upper;
227 } RANGE;
229 typedef struct tagRANGES
231 HDPA hdpa;
232 } *RANGES;
234 typedef struct tagITERATOR
236 INT nItem;
237 INT nSpecial;
238 RANGE range;
239 RANGES ranges;
240 INT index;
241 } ITERATOR;
243 typedef struct tagDELAYED_ITEM_EDIT
245 BOOL fEnabled;
246 INT iItem;
247 } DELAYED_ITEM_EDIT;
249 typedef struct tagLISTVIEW_INFO
251 HWND hwndSelf;
252 HBRUSH hBkBrush;
253 COLORREF clrBk;
254 COLORREF clrText;
255 COLORREF clrTextBk;
256 HIMAGELIST himlNormal;
257 HIMAGELIST himlSmall;
258 HIMAGELIST himlState;
259 BOOL bLButtonDown;
260 BOOL bRButtonDown;
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 betwen 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, strlenW(s1)), strlenW(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\n",
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,
739 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
741 TRACE(" <= %ld\n", result);
743 return result;
746 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
748 NMHDR nmh;
749 HWND hwnd = infoPtr->hwndSelf;
750 notify_hdr(infoPtr, code, &nmh);
751 return IsWindow(hwnd);
754 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
756 NMITEMACTIVATE nmia;
757 LVITEMW item;
759 if (htInfo) {
760 nmia.uNewState = 0;
761 nmia.uOldState = 0;
762 nmia.uChanged = 0;
763 nmia.uKeyFlags = 0;
765 item.mask = LVIF_PARAM|LVIF_STATE;
766 item.iItem = htInfo->iItem;
767 item.iSubItem = 0;
768 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
769 nmia.lParam = item.lParam;
770 nmia.uOldState = item.state;
771 nmia.uNewState = item.state | LVIS_ACTIVATING;
772 nmia.uChanged = LVIF_STATE;
775 nmia.iItem = htInfo->iItem;
776 nmia.iSubItem = htInfo->iSubItem;
777 nmia.ptAction = htInfo->pt;
779 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
780 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
781 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
783 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
786 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
788 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
789 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
792 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
794 NMLISTVIEW nmlv;
795 LVITEMW item;
796 HWND hwnd = infoPtr->hwndSelf;
798 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
799 ZeroMemory(&nmlv, sizeof(nmlv));
800 nmlv.iItem = lvht->iItem;
801 nmlv.iSubItem = lvht->iSubItem;
802 nmlv.ptAction = lvht->pt;
803 item.mask = LVIF_PARAM;
804 item.iItem = lvht->iItem;
805 item.iSubItem = 0;
806 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
807 notify_listview(infoPtr, code, &nmlv);
808 return IsWindow(hwnd);
811 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
813 NMLISTVIEW nmlv;
814 LVITEMW item;
815 HWND hwnd = infoPtr->hwndSelf;
817 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
818 nmlv.iItem = nItem;
819 item.mask = LVIF_PARAM;
820 item.iItem = nItem;
821 item.iSubItem = 0;
822 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
823 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
824 return IsWindow(hwnd);
827 static int get_ansi_notification(INT unicodeNotificationCode)
829 switch (unicodeNotificationCode)
831 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
832 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
833 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
834 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
835 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
836 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
838 ERR("unknown notification %x\n", unicodeNotificationCode);
839 assert(FALSE);
840 return 0;
844 Send notification. depends on dispinfoW having same
845 structure as dispinfoA.
846 infoPtr : listview struct
847 notificationCode : *Unicode* notification code
848 pdi : dispinfo structure (can be unicode or ansi)
849 isW : TRUE if dispinfo is Unicode
851 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
853 BOOL bResult = FALSE;
854 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
855 INT cchTempBufMax = 0, savCchTextMax = 0, 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)
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 /* apprently, for selected items, we have to override the returned values */
955 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
957 if (infoPtr->bFocus)
959 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
960 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
962 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
964 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
965 lpnmlvcd->clrText = comctl32_color.clrBtnText;
969 /* Set the text attributes */
970 if (lpnmlvcd->clrTextBk != CLR_NONE)
972 SetBkMode(hdc, OPAQUE);
973 SetBkColor(hdc,lpnmlvcd->clrTextBk);
975 else
976 SetBkMode(hdc, TRANSPARENT);
977 SetTextColor(hdc, lpnmlvcd->clrText);
980 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
982 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
985 /******** Item iterator functions **********************************/
987 static RANGES ranges_create(int count);
988 static void ranges_destroy(RANGES ranges);
989 static BOOL ranges_add(RANGES ranges, RANGE range);
990 static BOOL ranges_del(RANGES ranges, RANGE range);
991 static void ranges_dump(RANGES ranges);
993 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
995 RANGE range = { nItem, nItem + 1 };
997 return ranges_add(ranges, range);
1000 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1002 RANGE range = { nItem, nItem + 1 };
1004 return ranges_del(ranges, range);
1007 /***
1008 * ITERATOR DOCUMENTATION
1010 * The iterator functions allow for easy, and convenient iteration
1011 * over items of iterest in the list. Typically, you create a
1012 * iterator, use it, and destroy it, as such:
1013 * ITERATOR i;
1015 * iterator_xxxitems(&i, ...);
1016 * while (iterator_{prev,next}(&i)
1018 * //code which uses i.nItem
1020 * iterator_destroy(&i);
1022 * where xxx is either: framed, or visible.
1023 * Note that it is important that the code destroys the iterator
1024 * after it's done with it, as the creation of the iterator may
1025 * allocate memory, which thus needs to be freed.
1027 * You can iterate both forwards, and backwards through the list,
1028 * by using iterator_next or iterator_prev respectively.
1030 * Lower numbered items are draw on top of higher number items in
1031 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1032 * items may overlap). So, to test items, you should use
1033 * iterator_next
1034 * which lists the items top to bottom (in Z-order).
1035 * For drawing items, you should use
1036 * iterator_prev
1037 * which lists the items bottom to top (in Z-order).
1038 * If you keep iterating over the items after the end-of-items
1039 * marker (-1) is returned, the iterator will start from the
1040 * beginning. Typically, you don't need to test for -1,
1041 * because iterator_{next,prev} will return TRUE if more items
1042 * are to be iterated over, or FALSE otherwise.
1044 * Note: the iterator is defined to be bidirectional. That is,
1045 * any number of prev followed by any number of next, or
1046 * five versa, should leave the iterator at the same item:
1047 * prev * n, next * n = next * n, prev * n
1049 * The iterator has a notion of an out-of-order, special item,
1050 * which sits at the start of the list. This is used in
1051 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1052 * which needs to be first, as it may overlap other items.
1054 * The code is a bit messy because we have:
1055 * - a special item to deal with
1056 * - simple range, or composite range
1057 * - empty range.
1058 * If you find bugs, or want to add features, please make sure you
1059 * always check/modify *both* iterator_prev, and iterator_next.
1062 /****
1063 * This function iterates through the items in increasing order,
1064 * but prefixed by the special item, then -1. That is:
1065 * special, 1, 2, 3, ..., n, -1.
1066 * Each item is listed only once.
1068 static inline BOOL iterator_next(ITERATOR* i)
1070 if (i->nItem == -1)
1072 i->nItem = i->nSpecial;
1073 if (i->nItem != -1) return TRUE;
1075 if (i->nItem == i->nSpecial)
1077 if (i->ranges) i->index = 0;
1078 goto pickarange;
1081 i->nItem++;
1082 testitem:
1083 if (i->nItem == i->nSpecial) i->nItem++;
1084 if (i->nItem < i->range.upper) return TRUE;
1086 pickarange:
1087 if (i->ranges)
1089 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1090 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1091 else goto end;
1093 else if (i->nItem >= i->range.upper) goto end;
1095 i->nItem = i->range.lower;
1096 if (i->nItem >= 0) goto testitem;
1097 end:
1098 i->nItem = -1;
1099 return FALSE;
1102 /****
1103 * This function iterates through the items in decreasing order,
1104 * followed by the special item, then -1. That is:
1105 * n, n-1, ..., 3, 2, 1, special, -1.
1106 * Each item is listed only once.
1108 static inline BOOL iterator_prev(ITERATOR* i)
1110 BOOL start = FALSE;
1112 if (i->nItem == -1)
1114 start = TRUE;
1115 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1116 goto pickarange;
1118 if (i->nItem == i->nSpecial)
1120 i->nItem = -1;
1121 return FALSE;
1124 testitem:
1125 i->nItem--;
1126 if (i->nItem == i->nSpecial) i->nItem--;
1127 if (i->nItem >= i->range.lower) return TRUE;
1129 pickarange:
1130 if (i->ranges)
1132 if (i->index > 0)
1133 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1134 else goto end;
1136 else if (!start && i->nItem < i->range.lower) goto end;
1138 i->nItem = i->range.upper;
1139 if (i->nItem > 0) goto testitem;
1140 end:
1141 return (i->nItem = i->nSpecial) != -1;
1144 static RANGE iterator_range(const ITERATOR *i)
1146 RANGE range;
1148 if (!i->ranges) return i->range;
1150 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1152 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1153 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1155 else range.lower = range.upper = 0;
1157 return range;
1160 /***
1161 * Releases resources associated with this ierator.
1163 static inline void iterator_destroy(const ITERATOR *i)
1165 ranges_destroy(i->ranges);
1168 /***
1169 * Create an empty iterator.
1171 static inline BOOL iterator_empty(ITERATOR* i)
1173 ZeroMemory(i, sizeof(*i));
1174 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1175 return TRUE;
1178 /***
1179 * Create an iterator over a range.
1181 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1183 iterator_empty(i);
1184 i->range = range;
1185 return TRUE;
1188 /***
1189 * Create an iterator over a bunch of ranges.
1190 * Please note that the iterator will take ownership of the ranges,
1191 * and will free them upon destruction.
1193 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1195 iterator_empty(i);
1196 i->ranges = ranges;
1197 return TRUE;
1200 /***
1201 * Creates an iterator over the items which intersect lprc.
1203 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1205 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1206 RECT frame = *lprc, rcItem, rcTemp;
1207 POINT Origin;
1209 /* in case we fail, we want to return an empty iterator */
1210 if (!iterator_empty(i)) return FALSE;
1212 LISTVIEW_GetOrigin(infoPtr, &Origin);
1214 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1215 OffsetRect(&frame, -Origin.x, -Origin.y);
1217 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1219 INT nItem;
1221 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1223 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1224 if (IntersectRect(&rcTemp, &rcItem, lprc))
1225 i->nSpecial = infoPtr->nFocusedItem;
1227 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1228 /* to do better here, we need to have PosX, and PosY sorted */
1229 TRACE("building icon ranges:\n");
1230 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1232 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1233 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1234 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1235 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1236 if (IntersectRect(&rcTemp, &rcItem, &frame))
1237 ranges_additem(i->ranges, nItem);
1239 return TRUE;
1241 else if (uView == LVS_REPORT)
1243 RANGE range;
1245 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1246 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1248 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1249 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1250 if (range.upper <= range.lower) return TRUE;
1251 if (!iterator_rangeitems(i, range)) return FALSE;
1252 TRACE(" report=%s\n", debugrange(&i->range));
1254 else
1256 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1257 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1258 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1259 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1260 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1261 INT lower = nFirstCol * nPerCol + nFirstRow;
1262 RANGE item_range;
1263 INT nCol;
1265 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1266 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1268 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1270 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1271 TRACE("building list ranges:\n");
1272 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1274 item_range.lower = nCol * nPerCol + nFirstRow;
1275 if(item_range.lower >= infoPtr->nItemCount) break;
1276 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1277 TRACE(" list=%s\n", debugrange(&item_range));
1278 ranges_add(i->ranges, item_range);
1282 return TRUE;
1285 /***
1286 * Creates an iterator over the items which intersect the visible region of hdc.
1288 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1290 POINT Origin, Position;
1291 RECT rcItem, rcClip;
1292 INT rgntype;
1294 rgntype = GetClipBox(hdc, &rcClip);
1295 if (rgntype == NULLREGION) return iterator_empty(i);
1296 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1297 if (rgntype == SIMPLEREGION) return TRUE;
1299 /* first deal with the special item */
1300 if (i->nSpecial != -1)
1302 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1303 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1306 /* if we can't deal with the region, we'll just go with the simple range */
1307 LISTVIEW_GetOrigin(infoPtr, &Origin);
1308 TRACE("building visible range:\n");
1309 if (!i->ranges && i->range.lower < i->range.upper)
1311 if (!(i->ranges = ranges_create(50))) return TRUE;
1312 if (!ranges_add(i->ranges, i->range))
1314 ranges_destroy(i->ranges);
1315 i->ranges = 0;
1316 return TRUE;
1320 /* now delete the invisible items from the list */
1321 while(iterator_next(i))
1323 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1324 rcItem.left = Position.x + Origin.x;
1325 rcItem.top = Position.y + Origin.y;
1326 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1327 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1328 if (!RectVisible(hdc, &rcItem))
1329 ranges_delitem(i->ranges, i->nItem);
1331 /* the iterator should restart on the next iterator_next */
1332 TRACE("done\n");
1334 return TRUE;
1337 /******** Misc helper functions ************************************/
1339 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1340 WPARAM wParam, LPARAM lParam, BOOL isW)
1342 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1343 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1346 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1348 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1350 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1351 (uView == LVS_ICON || uView == LVS_SMALLICON);
1354 /******** Internal API functions ************************************/
1356 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1358 static COLUMN_INFO mainItem;
1360 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1361 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1362 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1365 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1367 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1370 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1372 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1375 /* Listview invalidation functions: use _only_ these functions to invalidate */
1377 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1379 return infoPtr->bRedraw;
1382 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1384 if(!is_redrawing(infoPtr)) return;
1385 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1386 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1389 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1391 RECT rcBox;
1393 if(!is_redrawing(infoPtr)) return;
1394 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1395 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1398 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1400 POINT Origin, Position;
1401 RECT rcBox;
1403 if(!is_redrawing(infoPtr)) return;
1404 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1405 LISTVIEW_GetOrigin(infoPtr, &Origin);
1406 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1407 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1408 rcBox.top = 0;
1409 rcBox.bottom = infoPtr->nItemHeight;
1410 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1411 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1414 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1416 LISTVIEW_InvalidateRect(infoPtr, NULL);
1419 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1421 RECT rcCol;
1423 if(!is_redrawing(infoPtr)) return;
1424 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1425 rcCol.top = infoPtr->rcList.top;
1426 rcCol.bottom = infoPtr->rcList.bottom;
1427 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1430 /***
1431 * DESCRIPTION:
1432 * Retrieves the number of items that can fit vertically in the client area.
1434 * PARAMETER(S):
1435 * [I] infoPtr : valid pointer to the listview structure
1437 * RETURN:
1438 * Number of items per row.
1440 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1442 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1444 return max(nListWidth/infoPtr->nItemWidth, 1);
1447 /***
1448 * DESCRIPTION:
1449 * Retrieves the number of items that can fit horizontally in the client
1450 * area.
1452 * PARAMETER(S):
1453 * [I] infoPtr : valid pointer to the listview structure
1455 * RETURN:
1456 * Number of items per column.
1458 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1460 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1462 return max(nListHeight / infoPtr->nItemHeight, 1);
1466 /*************************************************************************
1467 * LISTVIEW_ProcessLetterKeys
1469 * Processes keyboard messages generated by pressing the letter keys
1470 * on the keyboard.
1471 * What this does is perform a case insensitive search from the
1472 * current position with the following quirks:
1473 * - If two chars or more are pressed in quick succession we search
1474 * for the corresponding string (e.g. 'abc').
1475 * - If there is a delay we wipe away the current search string and
1476 * restart with just that char.
1477 * - If the user keeps pressing the same character, whether slowly or
1478 * fast, so that the search string is entirely composed of this
1479 * character ('aaaaa' for instance), then we search for first item
1480 * that starting with that character.
1481 * - If the user types the above character in quick succession, then
1482 * we must also search for the corresponding string ('aaaaa'), and
1483 * go to that string if there is a match.
1485 * PARAMETERS
1486 * [I] hwnd : handle to the window
1487 * [I] charCode : the character code, the actual character
1488 * [I] keyData : key data
1490 * RETURNS
1492 * Zero.
1494 * BUGS
1496 * - The current implementation has a list of characters it will
1497 * accept and it ignores averything else. In particular it will
1498 * ignore accentuated characters which seems to match what
1499 * Windows does. But I'm not sure it makes sense to follow
1500 * Windows there.
1501 * - We don't sound a beep when the search fails.
1503 * SEE ALSO
1505 * TREEVIEW_ProcessLetterKeys
1507 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1509 INT nItem;
1510 INT endidx,idx;
1511 LVITEMW item;
1512 WCHAR buffer[MAX_PATH];
1513 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1515 /* simple parameter checking */
1516 if (!charCode || !keyData) return 0;
1518 /* only allow the valid WM_CHARs through */
1519 if (!isalnum(charCode) &&
1520 charCode != '.' && charCode != '`' && charCode != '!' &&
1521 charCode != '@' && charCode != '#' && charCode != '$' &&
1522 charCode != '%' && charCode != '^' && charCode != '&' &&
1523 charCode != '*' && charCode != '(' && charCode != ')' &&
1524 charCode != '-' && charCode != '_' && charCode != '+' &&
1525 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1526 charCode != '}' && charCode != '[' && charCode != '{' &&
1527 charCode != '/' && charCode != '?' && charCode != '>' &&
1528 charCode != '<' && charCode != ',' && charCode != '~')
1529 return 0;
1531 /* if there's one item or less, there is no where to go */
1532 if (infoPtr->nItemCount <= 1) return 0;
1534 /* update the search parameters */
1535 infoPtr->lastKeyPressTimestamp = GetTickCount();
1536 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1537 if (infoPtr->nSearchParamLength < MAX_PATH)
1538 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1539 if (infoPtr->charCode != charCode)
1540 infoPtr->charCode = charCode = 0;
1541 } else {
1542 infoPtr->charCode=charCode;
1543 infoPtr->szSearchParam[0]=charCode;
1544 infoPtr->nSearchParamLength=1;
1545 /* Redundant with the 1 char string */
1546 charCode=0;
1549 /* and search from the current position */
1550 nItem=-1;
1551 if (infoPtr->nFocusedItem >= 0) {
1552 endidx=infoPtr->nFocusedItem;
1553 idx=endidx;
1554 /* if looking for single character match,
1555 * then we must always move forward
1557 if (infoPtr->nSearchParamLength == 1)
1558 idx++;
1559 } else {
1560 endidx=infoPtr->nItemCount;
1561 idx=0;
1563 do {
1564 if (idx == infoPtr->nItemCount) {
1565 if (endidx == infoPtr->nItemCount || endidx == 0)
1566 break;
1567 idx=0;
1570 /* get item */
1571 item.mask = LVIF_TEXT;
1572 item.iItem = idx;
1573 item.iSubItem = 0;
1574 item.pszText = buffer;
1575 item.cchTextMax = MAX_PATH;
1576 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1578 /* check for a match */
1579 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1580 nItem=idx;
1581 break;
1582 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1583 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1584 /* This would work but we must keep looking for a longer match */
1585 nItem=idx;
1587 idx++;
1588 } while (idx != endidx);
1590 if (nItem != -1)
1591 LISTVIEW_KeySelection(infoPtr, nItem);
1593 return 0;
1596 /*************************************************************************
1597 * LISTVIEW_UpdateHeaderSize [Internal]
1599 * Function to resize the header control
1601 * PARAMS
1602 * [I] hwnd : handle to a window
1603 * [I] nNewScrollPos : scroll pos to set
1605 * RETURNS
1606 * None.
1608 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1610 RECT winRect;
1611 POINT point[2];
1613 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1615 GetWindowRect(infoPtr->hwndHeader, &winRect);
1616 point[0].x = winRect.left;
1617 point[0].y = winRect.top;
1618 point[1].x = winRect.right;
1619 point[1].y = winRect.bottom;
1621 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1622 point[0].x = -nNewScrollPos;
1623 point[1].x += nNewScrollPos;
1625 SetWindowPos(infoPtr->hwndHeader,0,
1626 point[0].x,point[0].y,point[1].x,point[1].y,
1627 SWP_NOZORDER | SWP_NOACTIVATE);
1630 /***
1631 * DESCRIPTION:
1632 * Update the scrollbars. This functions should be called whenever
1633 * the content, size or view changes.
1635 * PARAMETER(S):
1636 * [I] infoPtr : valid pointer to the listview structure
1638 * RETURN:
1639 * None
1641 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1643 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1644 SCROLLINFO horzInfo, vertInfo;
1645 INT dx, dy;
1647 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1649 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1650 horzInfo.cbSize = sizeof(SCROLLINFO);
1651 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1653 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1654 if (uView == LVS_LIST)
1656 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1657 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1659 /* scroll by at least one column per page */
1660 if(horzInfo.nPage < infoPtr->nItemWidth)
1661 horzInfo.nPage = infoPtr->nItemWidth;
1663 horzInfo.nPage /= infoPtr->nItemWidth;
1665 else if (uView == LVS_REPORT)
1667 horzInfo.nMax = infoPtr->nItemWidth;
1669 else /* LVS_ICON, or LVS_SMALLICON */
1671 RECT rcView;
1673 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1676 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1677 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1678 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1679 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1680 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1682 /* Setting the horizontal scroll can change the listview size
1683 * (and potentially everything else) so we need to recompute
1684 * everything again for the vertical scroll
1687 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1688 vertInfo.cbSize = sizeof(SCROLLINFO);
1689 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1691 if (uView == LVS_REPORT)
1693 vertInfo.nMax = infoPtr->nItemCount;
1695 /* scroll by at least one page */
1696 if(vertInfo.nPage < infoPtr->nItemHeight)
1697 vertInfo.nPage = infoPtr->nItemHeight;
1699 vertInfo.nPage /= infoPtr->nItemHeight;
1701 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1703 RECT rcView;
1705 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1708 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1709 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1710 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1711 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1712 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1714 /* Change of the range may have changed the scroll pos. If so move the content */
1715 if (dx != 0 || dy != 0)
1717 RECT listRect;
1718 listRect = infoPtr->rcList;
1719 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1720 SW_ERASE | SW_INVALIDATE);
1723 /* Update the Header Control */
1724 if (uView == LVS_REPORT)
1726 horzInfo.fMask = SIF_POS;
1727 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1728 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1733 /***
1734 * DESCRIPTION:
1735 * Shows/hides the focus rectangle.
1737 * PARAMETER(S):
1738 * [I] infoPtr : valid pointer to the listview structure
1739 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1741 * RETURN:
1742 * None
1744 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1746 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1747 HDC hdc;
1749 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1751 if (infoPtr->nFocusedItem < 0) return;
1753 /* we need some gymnastics in ICON mode to handle large items */
1754 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1756 RECT rcBox;
1758 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1759 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1761 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1762 return;
1766 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1768 /* for some reason, owner draw should work only in report mode */
1769 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1771 DRAWITEMSTRUCT dis;
1772 LVITEMW item;
1774 item.iItem = infoPtr->nFocusedItem;
1775 item.iSubItem = 0;
1776 item.mask = LVIF_PARAM;
1777 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1779 ZeroMemory(&dis, sizeof(dis));
1780 dis.CtlType = ODT_LISTVIEW;
1781 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1782 dis.itemID = item.iItem;
1783 dis.itemAction = ODA_FOCUS;
1784 if (fShow) dis.itemState |= ODS_FOCUS;
1785 dis.hwndItem = infoPtr->hwndSelf;
1786 dis.hDC = hdc;
1787 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1788 dis.itemData = item.lParam;
1790 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1792 else
1794 DrawFocusRect(hdc, &infoPtr->rcFocus);
1796 done:
1797 ReleaseDC(infoPtr->hwndSelf, hdc);
1800 /***
1801 * Invalidates all visible selected items.
1803 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1805 ITERATOR i;
1807 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1808 while(iterator_next(&i))
1810 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1811 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1813 iterator_destroy(&i);
1817 /***
1818 * DESCRIPTION: [INTERNAL]
1819 * Computes an item's (left,top) corner, relative to rcView.
1820 * That is, the position has NOT been made relative to the Origin.
1821 * This is deliberate, to avoid computing the Origin over, and
1822 * over again, when this function is call in a loop. Instead,
1823 * one ca factor the computation of the Origin before the loop,
1824 * and offset the value retured by this function, on every iteration.
1826 * PARAMETER(S):
1827 * [I] infoPtr : valid pointer to the listview structure
1828 * [I] nItem : item number
1829 * [O] lpptOrig : item top, left corner
1831 * RETURN:
1832 * None.
1834 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1836 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1838 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1840 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1842 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1843 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1845 else if (uView == LVS_LIST)
1847 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1848 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1849 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1851 else /* LVS_REPORT */
1853 lpptPosition->x = 0;
1854 lpptPosition->y = nItem * infoPtr->nItemHeight;
1858 /***
1859 * DESCRIPTION: [INTERNAL]
1860 * Compute the rectangles of an item. This is to localize all
1861 * the computations in one place. If you are not interested in some
1862 * of these values, simply pass in a NULL -- the fucntion is smart
1863 * enough to compute only what's necessary. The function computes
1864 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1865 * one, the BOX rectangle. This rectangle is very cheap to compute,
1866 * and is guaranteed to contain all the other rectangles. Computing
1867 * the ICON rect is also cheap, but all the others are potentaily
1868 * expensive. This gives an easy and effective optimization when
1869 * searching (like point inclusion, or rectangle intersection):
1870 * first test against the BOX, and if TRUE, test agains the desired
1871 * rectangle.
1872 * If the function does not have all the necessary information
1873 * to computed the requested rectangles, will crash with a
1874 * failed assertion. This is done so we catch all programming
1875 * errors, given that the function is called only from our code.
1877 * We have the following 'special' meanings for a few fields:
1878 * * If LVIS_FOCUSED is set, we assume the item has the focus
1879 * This is important in ICON mode, where it might get a larger
1880 * then usual rectange
1882 * Please note that subitem support works only in REPORT mode.
1884 * PARAMETER(S):
1885 * [I] infoPtr : valid pointer to the listview structure
1886 * [I] lpLVItem : item to compute the measures for
1887 * [O] lprcBox : ptr to Box rectangle
1888 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1889 * [0] lprcSelectBox : ptr to select box rectangle
1890 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1891 * [O] lprcIcon : ptr to Icon rectangle
1892 * Same as LVM_GETITEMRECT with LVIR_ICON
1893 * [O] lprcStateIcon: ptr to State Icon rectangle
1894 * [O] lprcLabel : ptr to Label rectangle
1895 * Same as LVM_GETITEMRECT with LVIR_LABEL
1897 * RETURN:
1898 * None.
1900 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1901 LPRECT lprcBox, LPRECT lprcSelectBox,
1902 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1904 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1905 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1906 RECT Box, SelectBox, Icon, Label;
1907 COLUMN_INFO *lpColumnInfo = NULL;
1908 SIZE labelSize = { 0, 0 };
1910 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1912 /* Be smart and try to figure out the minimum we have to do */
1913 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1914 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1916 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1917 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1919 if (lprcSelectBox) doSelectBox = TRUE;
1920 if (lprcLabel) doLabel = TRUE;
1921 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1922 if (doSelectBox)
1924 doIcon = TRUE;
1925 doLabel = TRUE;
1928 /************************************************************/
1929 /* compute the box rectangle (it should be cheap to do) */
1930 /************************************************************/
1931 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1932 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1934 if (lpLVItem->iSubItem)
1936 Box = lpColumnInfo->rcHeader;
1938 else
1940 Box.left = 0;
1941 Box.right = infoPtr->nItemWidth;
1943 Box.top = 0;
1944 Box.bottom = infoPtr->nItemHeight;
1946 /******************************************************************/
1947 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1948 /******************************************************************/
1949 if (doIcon)
1951 LONG state_width = 0;
1953 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1954 state_width = infoPtr->iconStateSize.cx;
1956 if (uView == LVS_ICON)
1958 Icon.left = Box.left + state_width;
1959 if (infoPtr->himlNormal)
1960 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1961 Icon.top = Box.top + ICON_TOP_PADDING;
1962 Icon.right = Icon.left;
1963 Icon.bottom = Icon.top;
1964 if (infoPtr->himlNormal)
1966 Icon.right += infoPtr->iconSize.cx;
1967 Icon.bottom += infoPtr->iconSize.cy;
1970 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1972 Icon.left = Box.left + state_width;
1974 if (uView == LVS_REPORT)
1975 Icon.left += REPORT_MARGINX;
1977 Icon.top = Box.top;
1978 Icon.right = Icon.left;
1979 if (infoPtr->himlSmall &&
1980 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1981 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1982 Icon.right += infoPtr->iconSize.cx;
1983 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1985 if(lprcIcon) *lprcIcon = Icon;
1986 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
1988 /* TODO: is this correct? */
1989 if (lprcStateIcon)
1991 lprcStateIcon->left = Icon.left - state_width;
1992 lprcStateIcon->right = Icon.left;
1993 lprcStateIcon->top = Icon.top;
1994 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
1995 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
1998 else Icon.right = 0;
2000 /************************************************************/
2001 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2002 /************************************************************/
2003 if (doLabel)
2005 /* calculate how far to the right can the label strech */
2006 Label.right = Box.right;
2007 if (uView == LVS_REPORT)
2009 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2012 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2014 labelSize.cx = infoPtr->nItemWidth;
2015 labelSize.cy = infoPtr->nItemHeight;
2016 goto calc_label;
2019 /* we need the text in non owner draw mode */
2020 assert(lpLVItem->mask & LVIF_TEXT);
2021 if (is_textT(lpLVItem->pszText, TRUE))
2023 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2024 HDC hdc = GetDC(infoPtr->hwndSelf);
2025 HFONT hOldFont = SelectObject(hdc, hFont);
2026 UINT uFormat;
2027 RECT rcText;
2029 /* compute rough rectangle where the label will go */
2030 SetRectEmpty(&rcText);
2031 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2032 rcText.bottom = infoPtr->nItemHeight;
2033 if (uView == LVS_ICON)
2034 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2036 /* now figure out the flags */
2037 if (uView == LVS_ICON)
2038 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2039 else
2040 uFormat = LV_SL_DT_FLAGS;
2042 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2044 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2045 labelSize.cy = rcText.bottom - rcText.top;
2047 SelectObject(hdc, hOldFont);
2048 ReleaseDC(infoPtr->hwndSelf, hdc);
2051 calc_label:
2052 if (uView == LVS_ICON)
2054 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2055 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2056 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2057 Label.right = Label.left + labelSize.cx;
2058 Label.bottom = Label.top + infoPtr->nItemHeight;
2059 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2061 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2062 labelSize.cy /= infoPtr->ntmHeight;
2063 labelSize.cy = max(labelSize.cy, 1);
2064 labelSize.cy *= infoPtr->ntmHeight;
2066 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2068 else if (uView == LVS_REPORT)
2070 Label.left = Icon.right;
2071 Label.top = Box.top;
2072 Label.right = lpColumnInfo->rcHeader.right;
2073 Label.bottom = Label.top + infoPtr->nItemHeight;
2075 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2077 Label.left = Icon.right;
2078 Label.top = Box.top;
2079 Label.right = min(Label.left + labelSize.cx, Label.right);
2080 Label.bottom = Label.top + infoPtr->nItemHeight;
2083 if (lprcLabel) *lprcLabel = Label;
2084 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2087 /************************************************************/
2088 /* compute STATEICON bounding box */
2089 /************************************************************/
2090 if (doSelectBox)
2092 if (uView == LVS_REPORT)
2094 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2095 SelectBox.top = Box.top;
2096 SelectBox.bottom = Box.bottom;
2097 if (lpLVItem->iSubItem == 0)
2099 /* we need the indent in report mode */
2100 assert(lpLVItem->mask & LVIF_INDENT);
2101 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2103 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2105 else
2107 UnionRect(&SelectBox, &Icon, &Label);
2109 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2110 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2113 /* Fix the Box if necessary */
2114 if (lprcBox)
2116 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2117 else *lprcBox = Box;
2119 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2122 /***
2123 * DESCRIPTION: [INTERNAL]
2125 * PARAMETER(S):
2126 * [I] infoPtr : valid pointer to the listview structure
2127 * [I] nItem : item number
2128 * [O] lprcBox : ptr to Box rectangle
2130 * RETURN:
2131 * None.
2133 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2135 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2136 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2137 POINT Position, Origin;
2138 LVITEMW lvItem;
2140 LISTVIEW_GetOrigin(infoPtr, &Origin);
2141 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2143 /* Be smart and try to figure out the minimum we have to do */
2144 lvItem.mask = 0;
2145 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2146 lvItem.mask |= LVIF_TEXT;
2147 lvItem.iItem = nItem;
2148 lvItem.iSubItem = 0;
2149 lvItem.pszText = szDispText;
2150 lvItem.cchTextMax = DISP_TEXT_SIZE;
2151 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2152 if (uView == LVS_ICON)
2154 lvItem.mask |= LVIF_STATE;
2155 lvItem.stateMask = LVIS_FOCUSED;
2156 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2158 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2160 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2164 /***
2165 * DESCRIPTION:
2166 * Returns the current icon position, and advances it along the top.
2167 * The returned position is not offset by Origin.
2169 * PARAMETER(S):
2170 * [I] infoPtr : valid pointer to the listview structure
2171 * [O] lpPos : will get the current icon position
2173 * RETURN:
2174 * None
2176 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2178 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2180 *lpPos = infoPtr->currIconPos;
2182 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2183 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2185 infoPtr->currIconPos.x = 0;
2186 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2190 /***
2191 * DESCRIPTION:
2192 * Returns the current icon position, and advances it down the left edge.
2193 * The returned position is not offset by Origin.
2195 * PARAMETER(S):
2196 * [I] infoPtr : valid pointer to the listview structure
2197 * [O] lpPos : will get the current icon position
2199 * RETURN:
2200 * None
2202 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2204 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2206 *lpPos = infoPtr->currIconPos;
2208 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2209 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2211 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2212 infoPtr->currIconPos.y = 0;
2216 /***
2217 * DESCRIPTION:
2218 * Moves an icon to the specified position.
2219 * It takes care of invalidating the item, etc.
2221 * PARAMETER(S):
2222 * [I] infoPtr : valid pointer to the listview structure
2223 * [I] nItem : the item to move
2224 * [I] lpPos : the new icon position
2225 * [I] isNew : flags the item as being new
2227 * RETURN:
2228 * Success: TRUE
2229 * Failure: FALSE
2231 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2233 POINT old;
2235 if (!isNew)
2237 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2238 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2240 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2241 LISTVIEW_InvalidateItem(infoPtr, nItem);
2244 /* Allocating a POINTER for every item is too resource intensive,
2245 * so we'll keep the (x,y) in different arrays */
2246 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2247 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2249 LISTVIEW_InvalidateItem(infoPtr, nItem);
2251 return TRUE;
2254 /***
2255 * DESCRIPTION:
2256 * Arranges listview items in icon display mode.
2258 * PARAMETER(S):
2259 * [I] infoPtr : valid pointer to the listview structure
2260 * [I] nAlignCode : alignment code
2262 * RETURN:
2263 * SUCCESS : TRUE
2264 * FAILURE : FALSE
2266 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2268 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2269 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2270 POINT pos;
2271 INT i;
2273 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2275 TRACE("nAlignCode=%d\n", nAlignCode);
2277 if (nAlignCode == LVA_DEFAULT)
2279 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2280 else nAlignCode = LVA_ALIGNTOP;
2283 switch (nAlignCode)
2285 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2286 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2287 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2288 default: return FALSE;
2291 infoPtr->bAutoarrange = TRUE;
2292 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2293 for (i = 0; i < infoPtr->nItemCount; i++)
2295 next_pos(infoPtr, &pos);
2296 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2299 return TRUE;
2302 /***
2303 * DESCRIPTION:
2304 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2306 * PARAMETER(S):
2307 * [I] infoPtr : valid pointer to the listview structure
2308 * [O] lprcView : bounding rectangle
2310 * RETURN:
2311 * SUCCESS : TRUE
2312 * FAILURE : FALSE
2314 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2316 INT i, x, y;
2318 SetRectEmpty(lprcView);
2320 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2322 case LVS_ICON:
2323 case LVS_SMALLICON:
2324 for (i = 0; i < infoPtr->nItemCount; i++)
2326 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2327 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2328 lprcView->right = max(lprcView->right, x);
2329 lprcView->bottom = max(lprcView->bottom, y);
2331 if (infoPtr->nItemCount > 0)
2333 lprcView->right += infoPtr->nItemWidth;
2334 lprcView->bottom += infoPtr->nItemHeight;
2336 break;
2338 case LVS_LIST:
2339 y = LISTVIEW_GetCountPerColumn(infoPtr);
2340 x = infoPtr->nItemCount / y;
2341 if (infoPtr->nItemCount % y) x++;
2342 lprcView->right = x * infoPtr->nItemWidth;
2343 lprcView->bottom = y * infoPtr->nItemHeight;
2344 break;
2346 case LVS_REPORT:
2347 lprcView->right = infoPtr->nItemWidth;
2348 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2349 break;
2353 /***
2354 * DESCRIPTION:
2355 * Retrieves the bounding rectangle of all the items.
2357 * PARAMETER(S):
2358 * [I] infoPtr : valid pointer to the listview structure
2359 * [O] lprcView : bounding rectangle
2361 * RETURN:
2362 * SUCCESS : TRUE
2363 * FAILURE : FALSE
2365 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2367 POINT ptOrigin;
2369 TRACE("(lprcView=%p)\n", lprcView);
2371 if (!lprcView) return FALSE;
2373 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2374 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2375 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2377 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2379 return TRUE;
2382 /***
2383 * DESCRIPTION:
2384 * Retrieves the subitem pointer associated with the subitem index.
2386 * PARAMETER(S):
2387 * [I] hdpaSubItems : DPA handle for a specific item
2388 * [I] nSubItem : index of subitem
2390 * RETURN:
2391 * SUCCESS : subitem pointer
2392 * FAILURE : NULL
2394 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2396 SUBITEM_INFO *lpSubItem;
2397 INT i;
2399 /* we should binary search here if need be */
2400 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2402 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2403 if (lpSubItem->iSubItem == nSubItem)
2404 return lpSubItem;
2407 return NULL;
2411 /***
2412 * DESCRIPTION:
2413 * Calculates the desired item width.
2415 * PARAMETER(S):
2416 * [I] infoPtr : valid pointer to the listview structure
2418 * RETURN:
2419 * The desired item width.
2421 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2423 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2424 INT nItemWidth = 0;
2426 TRACE("uView=%d\n", uView);
2428 if (uView == LVS_ICON)
2429 nItemWidth = infoPtr->iconSpacing.cx;
2430 else if (uView == LVS_REPORT)
2432 RECT rcHeader;
2434 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2436 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2437 nItemWidth = rcHeader.right;
2440 else /* LVS_SMALLICON, or LVS_LIST */
2442 INT i;
2444 for (i = 0; i < infoPtr->nItemCount; i++)
2445 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2447 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2448 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2450 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2453 return max(nItemWidth, 1);
2456 /***
2457 * DESCRIPTION:
2458 * Calculates the desired item height.
2460 * PARAMETER(S):
2461 * [I] infoPtr : valid pointer to the listview structure
2463 * RETURN:
2464 * The desired item height.
2466 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2468 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2469 INT nItemHeight;
2471 TRACE("uView=%d\n", uView);
2473 if (uView == LVS_ICON)
2474 nItemHeight = infoPtr->iconSpacing.cy;
2475 else
2477 nItemHeight = infoPtr->ntmHeight;
2478 if (infoPtr->himlState)
2479 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2480 if (infoPtr->himlSmall)
2481 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2482 if (infoPtr->himlState || infoPtr->himlSmall)
2483 nItemHeight += HEIGHT_PADDING;
2484 if (infoPtr->nMeasureItemHeight > 0)
2485 nItemHeight = infoPtr->nMeasureItemHeight;
2488 return max(nItemHeight, 1);
2491 /***
2492 * DESCRIPTION:
2493 * Updates the width, and height of an item.
2495 * PARAMETER(S):
2496 * [I] infoPtr : valid pointer to the listview structure
2498 * RETURN:
2499 * None.
2501 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2503 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2504 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2508 /***
2509 * DESCRIPTION:
2510 * Retrieves and saves important text metrics info for the current
2511 * Listview font.
2513 * PARAMETER(S):
2514 * [I] infoPtr : valid pointer to the listview structure
2517 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2519 HDC hdc = GetDC(infoPtr->hwndSelf);
2520 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2521 HFONT hOldFont = SelectObject(hdc, hFont);
2522 TEXTMETRICW tm;
2523 SIZE sz;
2525 if (GetTextMetricsW(hdc, &tm))
2527 infoPtr->ntmHeight = tm.tmHeight;
2528 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2531 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2532 infoPtr->nEllipsisWidth = sz.cx;
2534 SelectObject(hdc, hOldFont);
2535 ReleaseDC(infoPtr->hwndSelf, hdc);
2537 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2540 /***
2541 * DESCRIPTION:
2542 * A compare function for ranges
2544 * PARAMETER(S)
2545 * [I] range1 : pointer to range 1;
2546 * [I] range2 : pointer to range 2;
2547 * [I] flags : flags
2549 * RETURNS:
2550 * > 0 : if range 1 > range 2
2551 * < 0 : if range 2 > range 1
2552 * = 0 : if range intersects range 2
2554 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2556 INT cmp;
2558 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2559 cmp = -1;
2560 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2561 cmp = 1;
2562 else
2563 cmp = 0;
2565 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2567 return cmp;
2570 #if DEBUG_RANGES
2571 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2572 #else
2573 #define ranges_check(ranges, desc) do { } while(0)
2574 #endif
2576 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2578 INT i;
2579 RANGE *prev, *curr;
2581 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2582 assert (ranges);
2583 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2584 ranges_dump(ranges);
2585 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2586 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2587 assert (prev->lower >= 0 && prev->lower < prev->upper);
2588 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2590 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2591 assert (prev->upper <= curr->lower);
2592 assert (curr->lower < curr->upper);
2593 prev = curr;
2595 TRACE("--- Done checking---\n");
2598 static RANGES ranges_create(int count)
2600 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2601 if (!ranges) return NULL;
2602 ranges->hdpa = DPA_Create(count);
2603 if (ranges->hdpa) return ranges;
2604 Free(ranges);
2605 return NULL;
2608 static void ranges_clear(RANGES ranges)
2610 INT i;
2612 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2613 Free(DPA_GetPtr(ranges->hdpa, i));
2614 DPA_DeleteAllPtrs(ranges->hdpa);
2618 static void ranges_destroy(RANGES ranges)
2620 if (!ranges) return;
2621 ranges_clear(ranges);
2622 DPA_Destroy(ranges->hdpa);
2623 Free(ranges);
2626 static RANGES ranges_clone(RANGES ranges)
2628 RANGES clone;
2629 INT i;
2631 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2633 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2635 RANGE *newrng = Alloc(sizeof(RANGE));
2636 if (!newrng) goto fail;
2637 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2638 DPA_SetPtr(clone->hdpa, i, newrng);
2640 return clone;
2642 fail:
2643 TRACE ("clone failed\n");
2644 ranges_destroy(clone);
2645 return NULL;
2648 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2650 INT i;
2652 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2653 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2655 return ranges;
2658 static void ranges_dump(RANGES ranges)
2660 INT i;
2662 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2663 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2666 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2668 RANGE srchrng = { nItem, nItem + 1 };
2670 TRACE("(nItem=%d)\n", nItem);
2671 ranges_check(ranges, "before contain");
2672 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2675 static INT ranges_itemcount(RANGES ranges)
2677 INT i, count = 0;
2679 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2681 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2682 count += sel->upper - sel->lower;
2685 return count;
2688 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2690 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2691 INT index;
2693 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2694 if (index == -1) return TRUE;
2696 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2698 chkrng = DPA_GetPtr(ranges->hdpa, index);
2699 if (chkrng->lower >= nItem)
2700 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2701 if (chkrng->upper > nItem)
2702 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2704 return TRUE;
2707 static BOOL ranges_add(RANGES ranges, RANGE range)
2709 RANGE srchrgn;
2710 INT index;
2712 TRACE("(%s)\n", debugrange(&range));
2713 ranges_check(ranges, "before add");
2715 /* try find overlapping regions first */
2716 srchrgn.lower = range.lower - 1;
2717 srchrgn.upper = range.upper + 1;
2718 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2720 if (index == -1)
2722 RANGE *newrgn;
2724 TRACE("Adding new range\n");
2726 /* create the brand new range to insert */
2727 newrgn = Alloc(sizeof(RANGE));
2728 if(!newrgn) goto fail;
2729 *newrgn = range;
2731 /* figure out where to insert it */
2732 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2733 TRACE("index=%d\n", index);
2734 if (index == -1) index = 0;
2736 /* and get it over with */
2737 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2739 Free(newrgn);
2740 goto fail;
2743 else
2745 RANGE *chkrgn, *mrgrgn;
2746 INT fromindex, mergeindex;
2748 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2749 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2751 chkrgn->lower = min(range.lower, chkrgn->lower);
2752 chkrgn->upper = max(range.upper, chkrgn->upper);
2754 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2756 /* merge now common anges */
2757 fromindex = 0;
2758 srchrgn.lower = chkrgn->lower - 1;
2759 srchrgn.upper = chkrgn->upper + 1;
2763 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2764 if (mergeindex == -1) break;
2765 if (mergeindex == index)
2767 fromindex = index + 1;
2768 continue;
2771 TRACE("Merge with index %i\n", mergeindex);
2773 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2774 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2775 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2776 Free(mrgrgn);
2777 DPA_DeletePtr(ranges->hdpa, mergeindex);
2778 if (mergeindex < index) index --;
2779 } while(1);
2782 ranges_check(ranges, "after add");
2783 return TRUE;
2785 fail:
2786 ranges_check(ranges, "failed add");
2787 return FALSE;
2790 static BOOL ranges_del(RANGES ranges, RANGE range)
2792 RANGE *chkrgn;
2793 INT index;
2795 TRACE("(%s)\n", debugrange(&range));
2796 ranges_check(ranges, "before del");
2798 /* we don't use DPAS_SORTED here, since we need *
2799 * to find the first overlapping range */
2800 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2801 while(index != -1)
2803 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2805 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2807 /* case 1: Same range */
2808 if ( (chkrgn->upper == range.upper) &&
2809 (chkrgn->lower == range.lower) )
2811 DPA_DeletePtr(ranges->hdpa, index);
2812 break;
2814 /* case 2: engulf */
2815 else if ( (chkrgn->upper <= range.upper) &&
2816 (chkrgn->lower >= range.lower) )
2818 DPA_DeletePtr(ranges->hdpa, index);
2820 /* case 3: overlap upper */
2821 else if ( (chkrgn->upper <= range.upper) &&
2822 (chkrgn->lower < range.lower) )
2824 chkrgn->upper = range.lower;
2826 /* case 4: overlap lower */
2827 else if ( (chkrgn->upper > range.upper) &&
2828 (chkrgn->lower >= range.lower) )
2830 chkrgn->lower = range.upper;
2831 break;
2833 /* case 5: fully internal */
2834 else
2836 RANGE tmprgn = *chkrgn, *newrgn;
2838 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2839 newrgn->lower = chkrgn->lower;
2840 newrgn->upper = range.lower;
2841 chkrgn->lower = range.upper;
2842 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2844 Free(newrgn);
2845 goto fail;
2847 chkrgn = &tmprgn;
2848 break;
2851 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2854 ranges_check(ranges, "after del");
2855 return TRUE;
2857 fail:
2858 ranges_check(ranges, "failed del");
2859 return FALSE;
2862 /***
2863 * DESCRIPTION:
2864 * Removes all selection ranges
2866 * Parameters(s):
2867 * [I] infoPtr : valid pointer to the listview structure
2868 * [I] toSkip : item range to skip removing the selection
2870 * RETURNS:
2871 * SUCCESS : TRUE
2872 * FAILURE : TRUE
2874 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2876 LVITEMW lvItem;
2877 ITERATOR i;
2878 RANGES clone;
2880 TRACE("()\n");
2882 lvItem.state = 0;
2883 lvItem.stateMask = LVIS_SELECTED;
2885 /* need to clone the DPA because callbacks can change it */
2886 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2887 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2888 while(iterator_next(&i))
2889 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2890 /* note that the iterator destructor will free the cloned range */
2891 iterator_destroy(&i);
2893 return TRUE;
2896 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2898 RANGES toSkip;
2900 if (!(toSkip = ranges_create(1))) return FALSE;
2901 if (nItem != -1) ranges_additem(toSkip, nItem);
2902 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2903 ranges_destroy(toSkip);
2904 return TRUE;
2907 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2909 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2912 /***
2913 * DESCRIPTION:
2914 * Retrieves the number of items that are marked as selected.
2916 * PARAMETER(S):
2917 * [I] infoPtr : valid pointer to the listview structure
2919 * RETURN:
2920 * Number of items selected.
2922 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2924 INT nSelectedCount = 0;
2926 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2928 INT i;
2929 for (i = 0; i < infoPtr->nItemCount; i++)
2931 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2932 nSelectedCount++;
2935 else
2936 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2938 TRACE("nSelectedCount=%d\n", nSelectedCount);
2939 return nSelectedCount;
2942 /***
2943 * DESCRIPTION:
2944 * Manages the item focus.
2946 * PARAMETER(S):
2947 * [I] infoPtr : valid pointer to the listview structure
2948 * [I] nItem : item index
2950 * RETURN:
2951 * TRUE : focused item changed
2952 * FALSE : focused item has NOT changed
2954 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2956 INT oldFocus = infoPtr->nFocusedItem;
2957 LVITEMW lvItem;
2959 if (nItem == infoPtr->nFocusedItem) return FALSE;
2961 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2962 lvItem.stateMask = LVIS_FOCUSED;
2963 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2965 return oldFocus != infoPtr->nFocusedItem;
2968 /* Helper function for LISTVIEW_ShiftIndices *only* */
2969 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2971 if (nShiftItem < nItem) return nShiftItem;
2973 if (nShiftItem > nItem) return nShiftItem + direction;
2975 if (direction > 0) return nShiftItem + direction;
2977 return min(nShiftItem, infoPtr->nItemCount - 1);
2981 * DESCRIPTION:
2982 * Updates the various indices after an item has been inserted or deleted.
2984 * PARAMETER(S):
2985 * [I] infoPtr : valid pointer to the listview structure
2986 * [I] nItem : item index
2987 * [I] direction : Direction of shift, +1 or -1.
2989 * RETURN:
2990 * None
2992 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2994 INT nNewFocus;
2995 BOOL bOldChange;
2997 /* temporarily disable change notification while shifting items */
2998 bOldChange = infoPtr->bDoChangeNotify;
2999 infoPtr->bDoChangeNotify = FALSE;
3001 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3003 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3005 assert(abs(direction) == 1);
3007 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3009 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3010 if (nNewFocus != infoPtr->nFocusedItem)
3011 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3013 /* But we are not supposed to modify nHotItem! */
3015 infoPtr->bDoChangeNotify = bOldChange;
3020 * DESCRIPTION:
3021 * Adds a block of selections.
3023 * PARAMETER(S):
3024 * [I] infoPtr : valid pointer to the listview structure
3025 * [I] nItem : item index
3027 * RETURN:
3028 * Whether the window is still valid.
3030 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3032 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3033 INT nLast = max(infoPtr->nSelectionMark, nItem);
3034 HWND hwndSelf = infoPtr->hwndSelf;
3035 NMLVODSTATECHANGE nmlv;
3036 LVITEMW item;
3037 BOOL bOldChange;
3038 INT i;
3040 /* Temporarily disable change notification
3041 * If the control is LVS_OWNERDATA, we need to send
3042 * only one LVN_ODSTATECHANGED notification.
3043 * See MSDN documentation for LVN_ITEMCHANGED.
3045 bOldChange = infoPtr->bDoChangeNotify;
3046 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3048 if (nFirst == -1) nFirst = nItem;
3050 item.state = LVIS_SELECTED;
3051 item.stateMask = LVIS_SELECTED;
3053 for (i = nFirst; i <= nLast; i++)
3054 LISTVIEW_SetItemState(infoPtr,i,&item);
3056 ZeroMemory(&nmlv, sizeof(nmlv));
3057 nmlv.iFrom = nFirst;
3058 nmlv.iTo = nLast;
3059 nmlv.uNewState = 0;
3060 nmlv.uOldState = item.state;
3062 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3063 if (!IsWindow(hwndSelf))
3064 return FALSE;
3065 infoPtr->bDoChangeNotify = bOldChange;
3066 return TRUE;
3070 /***
3071 * DESCRIPTION:
3072 * Sets a single group selection.
3074 * PARAMETER(S):
3075 * [I] infoPtr : valid pointer to the listview structure
3076 * [I] nItem : item index
3078 * RETURN:
3079 * None
3081 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3083 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3084 RANGES selection;
3085 LVITEMW item;
3086 ITERATOR i;
3087 BOOL bOldChange;
3089 if (!(selection = ranges_create(100))) return;
3091 item.state = LVIS_SELECTED;
3092 item.stateMask = LVIS_SELECTED;
3094 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3096 if (infoPtr->nSelectionMark == -1)
3098 infoPtr->nSelectionMark = nItem;
3099 ranges_additem(selection, nItem);
3101 else
3103 RANGE sel;
3105 sel.lower = min(infoPtr->nSelectionMark, nItem);
3106 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3107 ranges_add(selection, sel);
3110 else
3112 RECT rcItem, rcSel, rcSelMark;
3113 POINT ptItem;
3115 rcItem.left = LVIR_BOUNDS;
3116 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3117 rcSelMark.left = LVIR_BOUNDS;
3118 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3119 UnionRect(&rcSel, &rcItem, &rcSelMark);
3120 iterator_frameditems(&i, infoPtr, &rcSel);
3121 while(iterator_next(&i))
3123 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3124 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3126 iterator_destroy(&i);
3129 bOldChange = infoPtr->bDoChangeNotify;
3130 infoPtr->bDoChangeNotify = FALSE;
3132 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3135 iterator_rangesitems(&i, selection);
3136 while(iterator_next(&i))
3137 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3138 /* this will also destroy the selection */
3139 iterator_destroy(&i);
3141 infoPtr->bDoChangeNotify = bOldChange;
3143 LISTVIEW_SetItemFocus(infoPtr, nItem);
3146 /***
3147 * DESCRIPTION:
3148 * Sets a single selection.
3150 * PARAMETER(S):
3151 * [I] infoPtr : valid pointer to the listview structure
3152 * [I] nItem : item index
3154 * RETURN:
3155 * None
3157 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3159 LVITEMW lvItem;
3161 TRACE("nItem=%d\n", nItem);
3163 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3165 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3166 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3167 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3169 infoPtr->nSelectionMark = nItem;
3172 /***
3173 * DESCRIPTION:
3174 * Set selection(s) with keyboard.
3176 * PARAMETER(S):
3177 * [I] infoPtr : valid pointer to the listview structure
3178 * [I] nItem : item index
3180 * RETURN:
3181 * SUCCESS : TRUE (needs to be repainted)
3182 * FAILURE : FALSE (nothing has changed)
3184 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3186 /* FIXME: pass in the state */
3187 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3188 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3189 BOOL bResult = FALSE;
3191 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3192 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3194 if (infoPtr->dwStyle & LVS_SINGLESEL)
3196 bResult = TRUE;
3197 LISTVIEW_SetSelection(infoPtr, nItem);
3199 else
3201 if (wShift)
3203 bResult = TRUE;
3204 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3206 else if (wCtrl)
3208 LVITEMW lvItem;
3209 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3210 lvItem.stateMask = LVIS_SELECTED;
3211 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3213 if (lvItem.state & LVIS_SELECTED)
3214 infoPtr->nSelectionMark = nItem;
3216 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3218 else
3220 bResult = TRUE;
3221 LISTVIEW_SetSelection(infoPtr, nItem);
3224 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3227 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3228 return bResult;
3231 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3233 LVHITTESTINFO lvHitTestInfo;
3235 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3236 lvHitTestInfo.pt.x = pt.x;
3237 lvHitTestInfo.pt.y = pt.y;
3239 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3241 lpLVItem->mask = LVIF_PARAM;
3242 lpLVItem->iItem = lvHitTestInfo.iItem;
3243 lpLVItem->iSubItem = 0;
3245 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3248 /***
3249 * DESCRIPTION:
3250 * Called when the mouse is being actively tracked and has hovered for a specified
3251 * amount of time
3253 * PARAMETER(S):
3254 * [I] infoPtr : valid pointer to the listview structure
3255 * [I] fwKeys : key indicator
3256 * [I] x,y : mouse position
3258 * RETURN:
3259 * 0 if the message was processed, non-zero if there was an error
3261 * INFO:
3262 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3263 * over the item for a certain period of time.
3266 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, INT x, INT y)
3268 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3270 LVITEMW item;
3271 POINT pt;
3273 pt.x = x;
3274 pt.y = y;
3276 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3277 LISTVIEW_SetSelection(infoPtr, item.iItem);
3280 return 0;
3283 /***
3284 * DESCRIPTION:
3285 * Called whenever WM_MOUSEMOVE is received.
3287 * PARAMETER(S):
3288 * [I] infoPtr : valid pointer to the listview structure
3289 * [I] fwKeys : key indicator
3290 * [I] x,y : mouse position
3292 * RETURN:
3293 * 0 if the message is processed, non-zero if there was an error
3295 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3297 TRACKMOUSEEVENT trackinfo;
3299 if (!(fwKeys & MK_LBUTTON))
3300 infoPtr->bLButtonDown = FALSE;
3302 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3304 LVHITTESTINFO lvHitTestInfo;
3305 NMLISTVIEW nmlv;
3307 lvHitTestInfo.pt = infoPtr->ptClickPos;
3308 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3310 ZeroMemory(&nmlv, sizeof(nmlv));
3311 nmlv.iItem = lvHitTestInfo.iItem;
3312 nmlv.ptAction = infoPtr->ptClickPos;
3314 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3316 return 0;
3318 else
3319 infoPtr->bLButtonDown = FALSE;
3321 /* see if we are supposed to be tracking mouse hovering */
3322 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3323 /* fill in the trackinfo struct */
3324 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3325 trackinfo.dwFlags = TME_QUERY;
3326 trackinfo.hwndTrack = infoPtr->hwndSelf;
3327 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3329 /* see if we are already tracking this hwnd */
3330 _TrackMouseEvent(&trackinfo);
3332 if(!(trackinfo.dwFlags & TME_HOVER)) {
3333 trackinfo.dwFlags = TME_HOVER;
3335 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3336 _TrackMouseEvent(&trackinfo);
3340 return 0;
3344 /***
3345 * Tests wheather the item is assignable to a list with style lStyle
3347 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3349 if ( (lpLVItem->mask & LVIF_TEXT) &&
3350 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3351 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3353 return TRUE;
3357 /***
3358 * DESCRIPTION:
3359 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3361 * PARAMETER(S):
3362 * [I] infoPtr : valid pointer to the listview structure
3363 * [I] lpLVItem : valid pointer to new item atttributes
3364 * [I] isNew : the item being set is being inserted
3365 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3366 * [O] bChanged : will be set to TRUE if the item really changed
3368 * RETURN:
3369 * SUCCESS : TRUE
3370 * FAILURE : FALSE
3372 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3374 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3375 ITEM_INFO *lpItem;
3376 NMLISTVIEW nmlv;
3377 UINT uChanged = 0;
3378 LVITEMW item;
3380 TRACE("()\n");
3382 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3384 if (lpLVItem->mask == 0) return TRUE;
3386 if (infoPtr->dwStyle & LVS_OWNERDATA)
3388 /* a virtual listview we stores only selection and focus */
3389 if (lpLVItem->mask & ~LVIF_STATE)
3390 return FALSE;
3391 lpItem = NULL;
3393 else
3395 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3396 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3397 assert (lpItem);
3400 /* we need to get the lParam and state of the item */
3401 item.iItem = lpLVItem->iItem;
3402 item.iSubItem = lpLVItem->iSubItem;
3403 item.mask = LVIF_STATE | LVIF_PARAM;
3404 item.stateMask = ~0;
3405 item.state = 0;
3406 item.lParam = 0;
3407 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3409 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3410 /* determine what fields will change */
3411 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3412 uChanged |= LVIF_STATE;
3414 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3415 uChanged |= LVIF_IMAGE;
3417 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3418 uChanged |= LVIF_PARAM;
3420 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3421 uChanged |= LVIF_INDENT;
3423 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3424 uChanged |= LVIF_TEXT;
3426 TRACE("uChanged=0x%x\n", uChanged);
3427 if (!uChanged) return TRUE;
3428 *bChanged = TRUE;
3430 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3431 nmlv.iItem = lpLVItem->iItem;
3432 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3433 nmlv.uOldState = item.state;
3434 nmlv.uChanged = uChanged;
3435 nmlv.lParam = item.lParam;
3437 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3438 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3439 /* are enabled */
3440 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3442 HWND hwndSelf = infoPtr->hwndSelf;
3444 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3445 return FALSE;
3446 if (!IsWindow(hwndSelf))
3447 return FALSE;
3450 /* copy information */
3451 if (lpLVItem->mask & LVIF_TEXT)
3452 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3454 if (lpLVItem->mask & LVIF_IMAGE)
3455 lpItem->hdr.iImage = lpLVItem->iImage;
3457 if (lpLVItem->mask & LVIF_PARAM)
3458 lpItem->lParam = lpLVItem->lParam;
3460 if (lpLVItem->mask & LVIF_INDENT)
3461 lpItem->iIndent = lpLVItem->iIndent;
3463 if (uChanged & LVIF_STATE)
3465 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3467 lpItem->state &= ~lpLVItem->stateMask;
3468 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3470 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3472 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3473 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3475 else if (lpLVItem->stateMask & LVIS_SELECTED)
3476 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3478 /* if we are asked to change focus, and we manage it, do it */
3479 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3481 if (lpLVItem->state & LVIS_FOCUSED)
3483 LISTVIEW_SetItemFocus(infoPtr, -1);
3484 infoPtr->nFocusedItem = lpLVItem->iItem;
3485 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3487 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3488 infoPtr->nFocusedItem = -1;
3492 /* if we're inserting the item, we're done */
3493 if (isNew) return TRUE;
3495 /* send LVN_ITEMCHANGED notification */
3496 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3497 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3499 return TRUE;
3502 /***
3503 * DESCRIPTION:
3504 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3506 * PARAMETER(S):
3507 * [I] infoPtr : valid pointer to the listview structure
3508 * [I] lpLVItem : valid pointer to new subitem atttributes
3509 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3510 * [O] bChanged : will be set to TRUE if the item really changed
3512 * RETURN:
3513 * SUCCESS : TRUE
3514 * FAILURE : FALSE
3516 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3518 HDPA hdpaSubItems;
3519 SUBITEM_INFO *lpSubItem;
3521 /* we do not support subitems for virtual listviews */
3522 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3524 /* set subitem only if column is present */
3525 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3527 /* First do some sanity checks */
3528 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3529 particularly useful. We currently do not actually do anything with
3530 the flag on subitems.
3532 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3533 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3535 /* get the subitem structure, and create it if not there */
3536 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3537 assert (hdpaSubItems);
3539 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3540 if (!lpSubItem)
3542 SUBITEM_INFO *tmpSubItem;
3543 INT i;
3545 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3546 if (!lpSubItem) return FALSE;
3547 /* we could binary search here, if need be...*/
3548 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3550 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3551 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3553 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3555 Free(lpSubItem);
3556 return FALSE;
3558 lpSubItem->iSubItem = lpLVItem->iSubItem;
3559 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3560 *bChanged = TRUE;
3563 if (lpLVItem->mask & LVIF_IMAGE)
3564 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3566 lpSubItem->hdr.iImage = lpLVItem->iImage;
3567 *bChanged = TRUE;
3570 if (lpLVItem->mask & LVIF_TEXT)
3571 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3573 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3574 *bChanged = TRUE;
3577 return TRUE;
3580 /***
3581 * DESCRIPTION:
3582 * Sets item attributes.
3584 * PARAMETER(S):
3585 * [I] infoPtr : valid pointer to the listview structure
3586 * [I] lpLVItem : new item atttributes
3587 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3589 * RETURN:
3590 * SUCCESS : TRUE
3591 * FAILURE : FALSE
3593 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3595 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3596 HWND hwndSelf = infoPtr->hwndSelf;
3597 LPWSTR pszText = NULL;
3598 BOOL bResult, bChanged = FALSE;
3600 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3602 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3603 return FALSE;
3605 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3606 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3608 pszText = lpLVItem->pszText;
3609 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3612 /* actually set the fields */
3613 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3615 if (lpLVItem->iSubItem)
3616 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3617 else
3618 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3619 if (!IsWindow(hwndSelf))
3620 return FALSE;
3622 /* redraw item, if necessary */
3623 if (bChanged && !infoPtr->bIsDrawing)
3625 /* this little optimization eliminates some nasty flicker */
3626 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3627 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3628 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3629 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3630 else
3631 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3633 /* restore text */
3634 if (pszText)
3636 textfreeT(lpLVItem->pszText, isW);
3637 lpLVItem->pszText = pszText;
3640 return bResult;
3643 /***
3644 * DESCRIPTION:
3645 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3647 * PARAMETER(S):
3648 * [I] infoPtr : valid pointer to the listview structure
3650 * RETURN:
3651 * item index
3653 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3655 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3656 INT nItem = 0;
3657 SCROLLINFO scrollInfo;
3659 scrollInfo.cbSize = sizeof(SCROLLINFO);
3660 scrollInfo.fMask = SIF_POS;
3662 if (uView == LVS_LIST)
3664 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3665 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3667 else if (uView == LVS_REPORT)
3669 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3670 nItem = scrollInfo.nPos;
3672 else
3674 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3675 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3678 TRACE("nItem=%d\n", nItem);
3680 return nItem;
3684 /***
3685 * DESCRIPTION:
3686 * Erases the background of the given rectangle
3688 * PARAMETER(S):
3689 * [I] infoPtr : valid pointer to the listview structure
3690 * [I] hdc : device context handle
3691 * [I] lprcBox : clipping rectangle
3693 * RETURN:
3694 * Success: TRUE
3695 * Failure: FALSE
3697 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3699 if (!infoPtr->hBkBrush) return FALSE;
3701 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3703 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3706 /***
3707 * DESCRIPTION:
3708 * Draws an item.
3710 * PARAMETER(S):
3711 * [I] infoPtr : valid pointer to the listview structure
3712 * [I] hdc : device context handle
3713 * [I] nItem : item index
3714 * [I] nSubItem : subitem index
3715 * [I] pos : item position in client coordinates
3716 * [I] cdmode : custom draw mode
3718 * RETURN:
3719 * Success: TRUE
3720 * Failure: FALSE
3722 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3724 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3725 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3726 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3727 DWORD cdsubitemmode = CDRF_DODEFAULT;
3728 LPRECT lprcFocus;
3729 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3730 NMLVCUSTOMDRAW nmlvcd;
3731 HIMAGELIST himl;
3732 LVITEMW lvItem;
3733 HFONT hOldFont;
3735 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3737 /* get information needed for drawing the item */
3738 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3739 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3740 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3741 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3742 lvItem.iItem = nItem;
3743 lvItem.iSubItem = nSubItem;
3744 lvItem.state = 0;
3745 lvItem.lParam = 0;
3746 lvItem.cchTextMax = DISP_TEXT_SIZE;
3747 lvItem.pszText = szDispText;
3748 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3749 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3750 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3751 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3752 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3754 /* now check if we need to update the focus rectangle */
3755 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3757 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3758 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3759 OffsetRect(&rcBox, pos.x, pos.y);
3760 OffsetRect(&rcSelect, pos.x, pos.y);
3761 OffsetRect(&rcIcon, pos.x, pos.y);
3762 OffsetRect(&rcStateIcon, pos.x, pos.y);
3763 OffsetRect(&rcLabel, pos.x, pos.y);
3764 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3765 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3766 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3768 /* fill in the custom draw structure */
3769 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3771 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3772 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3773 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3774 if (cdmode & CDRF_NOTIFYITEMDRAW)
3775 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3776 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3777 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3778 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3779 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3781 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3782 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3784 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3785 prepaint_setup(infoPtr, hdc, &nmlvcd);
3787 /* in full row select, subitems, will just use main item's colors */
3788 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3789 nmlvcd.clrTextBk = CLR_NONE;
3791 /* state icons */
3792 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3794 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3795 if (uStateImage)
3797 TRACE("uStateImage=%d\n", uStateImage);
3798 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3799 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3803 /* small icons */
3804 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3805 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3807 TRACE("iImage=%d\n", lvItem.iImage);
3808 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3809 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3810 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3813 /* Don't bother painting item being edited */
3814 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3816 /* FIXME: temporary hack */
3817 rcSelect.left = rcLabel.left;
3819 /* draw the selection background, if we're drawing the main item */
3820 if (nSubItem == 0)
3822 /* in icon mode, the label rect is really what we want to draw the
3823 * background for */
3824 if (uView == LVS_ICON)
3825 rcSelect = rcLabel;
3827 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3828 rcSelect.right = rcBox.right;
3830 if (nmlvcd.clrTextBk != CLR_NONE)
3831 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3832 if(lprcFocus) *lprcFocus = rcSelect;
3835 /* figure out the text drawing flags */
3836 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3837 if (uView == LVS_ICON)
3838 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3839 else if (nSubItem)
3841 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3843 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3844 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3845 default: uFormat |= DT_LEFT;
3848 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3850 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3851 else rcLabel.left += LABEL_HOR_PADDING;
3853 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3854 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3856 postpaint:
3857 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3858 notify_postpaint(infoPtr, &nmlvcd);
3859 if (cdsubitemmode & CDRF_NEWFONT)
3860 SelectObject(hdc, hOldFont);
3861 return TRUE;
3864 /***
3865 * DESCRIPTION:
3866 * Draws listview items when in owner draw mode.
3868 * PARAMETER(S):
3869 * [I] infoPtr : valid pointer to the listview structure
3870 * [I] hdc : device context handle
3872 * RETURN:
3873 * None
3875 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3877 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3878 DWORD cditemmode = CDRF_DODEFAULT;
3879 NMLVCUSTOMDRAW nmlvcd;
3880 POINT Origin, Position;
3881 DRAWITEMSTRUCT dis;
3882 LVITEMW item;
3884 TRACE("()\n");
3886 ZeroMemory(&dis, sizeof(dis));
3888 /* Get scroll info once before loop */
3889 LISTVIEW_GetOrigin(infoPtr, &Origin);
3891 /* iterate through the invalidated rows */
3892 while(iterator_next(i))
3894 item.iItem = i->nItem;
3895 item.iSubItem = 0;
3896 item.mask = LVIF_PARAM | LVIF_STATE;
3897 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3898 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3900 dis.CtlType = ODT_LISTVIEW;
3901 dis.CtlID = uID;
3902 dis.itemID = item.iItem;
3903 dis.itemAction = ODA_DRAWENTIRE;
3904 dis.itemState = 0;
3905 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3906 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3907 dis.hwndItem = infoPtr->hwndSelf;
3908 dis.hDC = hdc;
3909 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3910 dis.rcItem.left = Position.x + Origin.x;
3911 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3912 dis.rcItem.top = Position.y + Origin.y;
3913 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3914 dis.itemData = item.lParam;
3916 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3919 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3920 * structure for the rest. of the paint cycle
3922 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3923 if (cdmode & CDRF_NOTIFYITEMDRAW)
3924 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3926 if (!(cditemmode & CDRF_SKIPDEFAULT))
3928 prepaint_setup (infoPtr, hdc, &nmlvcd);
3929 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3932 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3933 notify_postpaint(infoPtr, &nmlvcd);
3937 /***
3938 * DESCRIPTION:
3939 * Draws listview items when in report display mode.
3941 * PARAMETER(S):
3942 * [I] infoPtr : valid pointer to the listview structure
3943 * [I] hdc : device context handle
3944 * [I] cdmode : custom draw mode
3946 * RETURN:
3947 * None
3949 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3951 INT rgntype;
3952 RECT rcClip, rcItem;
3953 POINT Origin, Position;
3954 RANGE colRange;
3955 ITERATOR j;
3957 TRACE("()\n");
3959 /* figure out what to draw */
3960 rgntype = GetClipBox(hdc, &rcClip);
3961 if (rgntype == NULLREGION) return;
3963 /* Get scroll info once before loop */
3964 LISTVIEW_GetOrigin(infoPtr, &Origin);
3966 /* narrow down the columns we need to paint */
3967 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3969 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3970 if (rcItem.right + Origin.x >= rcClip.left) break;
3972 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3974 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3975 if (rcItem.left + Origin.x < rcClip.right) break;
3977 iterator_rangeitems(&j, colRange);
3979 /* in full row select, we _have_ to draw the main item */
3980 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3981 j.nSpecial = 0;
3983 /* iterate through the invalidated rows */
3984 while(iterator_next(i))
3986 /* iterate through the invalidated columns */
3987 while(iterator_next(&j))
3989 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3990 Position.x += Origin.x;
3991 Position.y += Origin.y;
3993 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3995 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3996 rcItem.top = 0;
3997 rcItem.bottom = infoPtr->nItemHeight;
3998 OffsetRect(&rcItem, Position.x, Position.y);
3999 if (!RectVisible(hdc, &rcItem)) continue;
4002 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4005 iterator_destroy(&j);
4008 /***
4009 * DESCRIPTION:
4010 * Draws listview items when in list display mode.
4012 * PARAMETER(S):
4013 * [I] infoPtr : valid pointer to the listview structure
4014 * [I] hdc : device context handle
4015 * [I] cdmode : custom draw mode
4017 * RETURN:
4018 * None
4020 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4022 POINT Origin, Position;
4024 /* Get scroll info once before loop */
4025 LISTVIEW_GetOrigin(infoPtr, &Origin);
4027 while(iterator_prev(i))
4029 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4030 Position.x += Origin.x;
4031 Position.y += Origin.y;
4033 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4038 /***
4039 * DESCRIPTION:
4040 * Draws listview items.
4042 * PARAMETER(S):
4043 * [I] infoPtr : valid pointer to the listview structure
4044 * [I] hdc : device context handle
4045 * [I] prcErase : rect to be erased before refresh (may be NULL)
4047 * RETURN:
4048 * NoneX
4050 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4052 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4053 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4054 NMLVCUSTOMDRAW nmlvcd;
4055 HFONT hOldFont = 0;
4056 DWORD cdmode;
4057 INT oldBkMode = 0;
4058 RECT rcClient;
4059 ITERATOR i;
4060 HDC hdcOrig = hdc;
4061 HBITMAP hbmp = NULL;
4063 LISTVIEW_DUMP(infoPtr);
4065 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4066 TRACE("double buffering\n");
4068 hdc = CreateCompatibleDC(hdcOrig);
4069 if (!hdc) {
4070 ERR("Failed to create DC for backbuffer\n");
4071 return;
4073 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4074 infoPtr->rcList.bottom);
4075 if (!hbmp) {
4076 ERR("Failed to create bitmap for backbuffer\n");
4077 DeleteDC(hdc);
4078 return;
4081 SelectObject(hdc, hbmp);
4082 SelectObject(hdc, infoPtr->hFont);
4083 } else {
4084 /* Save dc values we're gonna trash while drawing
4085 * FIXME: Should be done in LISTVIEW_DrawItem() */
4086 hOldFont = SelectObject(hdc, infoPtr->hFont);
4087 oldBkMode = GetBkMode(hdc);
4088 oldBkColor = GetBkColor(hdc);
4089 oldTextColor = GetTextColor(hdc);
4092 infoPtr->bIsDrawing = TRUE;
4094 if (prcErase) {
4095 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4096 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4097 /* If no erasing was done (usually because RedrawWindow was called
4098 * with RDW_INVALIDATE only) we need to copy the old contents into
4099 * the backbuffer before continuing. */
4100 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4101 infoPtr->rcList.right - infoPtr->rcList.left,
4102 infoPtr->rcList.bottom - infoPtr->rcList.top,
4103 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4106 /* FIXME: Shouldn't need to do this */
4107 oldClrTextBk = infoPtr->clrTextBk;
4108 oldClrText = infoPtr->clrText;
4110 infoPtr->cditemmode = CDRF_DODEFAULT;
4112 GetClientRect(infoPtr->hwndSelf, &rcClient);
4113 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4114 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4115 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4116 prepaint_setup(infoPtr, hdc, &nmlvcd);
4118 /* Use these colors to draw the items */
4119 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4120 infoPtr->clrText = nmlvcd.clrText;
4122 /* nothing to draw */
4123 if(infoPtr->nItemCount == 0) goto enddraw;
4125 /* figure out what we need to draw */
4126 iterator_visibleitems(&i, infoPtr, hdc);
4128 /* send cache hint notification */
4129 if (infoPtr->dwStyle & LVS_OWNERDATA)
4131 RANGE range = iterator_range(&i);
4132 NMLVCACHEHINT nmlv;
4134 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4135 nmlv.iFrom = range.lower;
4136 nmlv.iTo = range.upper - 1;
4137 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4140 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4141 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4142 else
4144 if (uView == LVS_REPORT)
4145 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4146 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4147 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4149 /* if we have a focus rect, draw it */
4150 if (infoPtr->bFocus)
4151 DrawFocusRect(hdc, &infoPtr->rcFocus);
4153 iterator_destroy(&i);
4155 enddraw:
4156 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4157 notify_postpaint(infoPtr, &nmlvcd);
4159 infoPtr->clrTextBk = oldClrTextBk;
4160 infoPtr->clrText = oldClrText;
4162 if(hbmp) {
4163 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4164 infoPtr->rcList.right - infoPtr->rcList.left,
4165 infoPtr->rcList.bottom - infoPtr->rcList.top,
4166 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4168 DeleteObject(hbmp);
4169 DeleteDC(hdc);
4170 } else {
4171 SelectObject(hdc, hOldFont);
4172 SetBkMode(hdc, oldBkMode);
4173 SetBkColor(hdc, oldBkColor);
4174 SetTextColor(hdc, oldTextColor);
4177 infoPtr->bIsDrawing = FALSE;
4181 /***
4182 * DESCRIPTION:
4183 * Calculates the approximate width and height of a given number of items.
4185 * PARAMETER(S):
4186 * [I] infoPtr : valid pointer to the listview structure
4187 * [I] nItemCount : number of items
4188 * [I] wWidth : width
4189 * [I] wHeight : height
4191 * RETURN:
4192 * Returns a DWORD. The width in the low word and the height in high word.
4194 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4195 WORD wWidth, WORD wHeight)
4197 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4198 INT nItemCountPerColumn = 1;
4199 INT nColumnCount = 0;
4200 DWORD dwViewRect = 0;
4202 if (nItemCount == -1)
4203 nItemCount = infoPtr->nItemCount;
4205 if (uView == LVS_LIST)
4207 if (wHeight == 0xFFFF)
4209 /* use current height */
4210 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4213 if (wHeight < infoPtr->nItemHeight)
4214 wHeight = infoPtr->nItemHeight;
4216 if (nItemCount > 0)
4218 if (infoPtr->nItemHeight > 0)
4220 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4221 if (nItemCountPerColumn == 0)
4222 nItemCountPerColumn = 1;
4224 if (nItemCount % nItemCountPerColumn != 0)
4225 nColumnCount = nItemCount / nItemCountPerColumn;
4226 else
4227 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4231 /* Microsoft padding magic */
4232 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4233 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4235 dwViewRect = MAKELONG(wWidth, wHeight);
4237 else if (uView == LVS_REPORT)
4239 RECT rcBox;
4241 if (infoPtr->nItemCount > 0)
4243 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4244 wWidth = rcBox.right - rcBox.left;
4245 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4247 else
4249 /* use current height and width */
4250 if (wHeight == 0xffff)
4251 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4252 if (wWidth == 0xffff)
4253 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4256 dwViewRect = MAKELONG(wWidth, wHeight);
4258 else if (uView == LVS_SMALLICON)
4259 FIXME("uView == LVS_SMALLICON: not implemented\n");
4260 else if (uView == LVS_ICON)
4261 FIXME("uView == LVS_ICON: not implemented\n");
4263 return dwViewRect;
4267 /***
4268 * DESCRIPTION:
4269 * Create a drag image list for the specified item.
4271 * PARAMETER(S):
4272 * [I] infoPtr : valid pointer to the listview structure
4273 * [I] iItem : index of item
4274 * [O] lppt : Upperr-left corner of the image
4276 * RETURN:
4277 * Returns a handle to the image list if successful, NULL otherwise.
4279 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4281 RECT rcItem;
4282 SIZE size;
4283 POINT pos;
4284 HDC hdc, hdcOrig;
4285 HBITMAP hbmp, hOldbmp;
4286 HIMAGELIST dragList = 0;
4287 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4289 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4290 return 0;
4292 rcItem.left = LVIR_BOUNDS;
4293 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4294 return 0;
4296 lppt->x = rcItem.left;
4297 lppt->y = rcItem.top;
4299 size.cx = rcItem.right - rcItem.left;
4300 size.cy = rcItem.bottom - rcItem.top;
4302 hdcOrig = GetDC(infoPtr->hwndSelf);
4303 hdc = CreateCompatibleDC(hdcOrig);
4304 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4305 hOldbmp = SelectObject(hdc, hbmp);
4307 rcItem.left = rcItem.top = 0;
4308 rcItem.right = size.cx;
4309 rcItem.bottom = size.cy;
4310 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4312 pos.x = pos.y = 0;
4313 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4315 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4316 SelectObject(hdc, hOldbmp);
4317 ImageList_Add(dragList, hbmp, 0);
4319 else
4320 SelectObject(hdc, hOldbmp);
4322 DeleteObject(hbmp);
4323 DeleteDC(hdc);
4324 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4326 TRACE("ret=%p\n", dragList);
4328 return dragList;
4332 /***
4333 * DESCRIPTION:
4334 * Removes all listview items and subitems.
4336 * PARAMETER(S):
4337 * [I] infoPtr : valid pointer to the listview structure
4339 * RETURN:
4340 * SUCCESS : TRUE
4341 * FAILURE : FALSE
4343 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4345 NMLISTVIEW nmlv;
4346 HDPA hdpaSubItems = NULL;
4347 BOOL bSuppress;
4348 ITEMHDR *hdrItem;
4349 INT i, j;
4351 TRACE("()\n");
4353 /* we do it directly, to avoid notifications */
4354 ranges_clear(infoPtr->selectionRanges);
4355 infoPtr->nSelectionMark = -1;
4356 infoPtr->nFocusedItem = -1;
4357 SetRectEmpty(&infoPtr->rcFocus);
4358 /* But we are supposed to leave nHotItem as is! */
4361 /* send LVN_DELETEALLITEMS notification */
4362 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4363 nmlv.iItem = -1;
4364 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4366 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4368 /* send LVN_DELETEITEM notification, if not suppressed */
4369 if (!bSuppress) notify_deleteitem(infoPtr, i);
4370 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4372 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4373 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4375 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4376 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4377 Free(hdrItem);
4379 DPA_Destroy(hdpaSubItems);
4380 DPA_DeletePtr(infoPtr->hdpaItems, i);
4382 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4383 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4384 infoPtr->nItemCount --;
4387 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4388 LISTVIEW_UpdateScroll(infoPtr);
4389 LISTVIEW_InvalidateList(infoPtr);
4391 return TRUE;
4394 /***
4395 * DESCRIPTION:
4396 * Scrolls, and updates the columns, when a column is changing width.
4398 * PARAMETER(S):
4399 * [I] infoPtr : valid pointer to the listview structure
4400 * [I] nColumn : column to scroll
4401 * [I] dx : amount of scroll, in pixels
4403 * RETURN:
4404 * None.
4406 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4408 COLUMN_INFO *lpColumnInfo;
4409 RECT rcOld, rcCol;
4410 POINT ptOrigin;
4411 INT nCol;
4413 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4414 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4415 rcCol = lpColumnInfo->rcHeader;
4416 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4417 rcCol.left = rcCol.right;
4419 /* ajust the other columns */
4420 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4422 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4423 lpColumnInfo->rcHeader.left += dx;
4424 lpColumnInfo->rcHeader.right += dx;
4427 /* do not update screen if not in report mode */
4428 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4430 /* if we have a focus, must first erase the focus rect */
4431 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4433 /* Need to reset the item width when inserting a new column */
4434 infoPtr->nItemWidth += dx;
4436 LISTVIEW_UpdateScroll(infoPtr);
4437 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4439 /* scroll to cover the deleted column, and invalidate for redraw */
4440 rcOld = infoPtr->rcList;
4441 rcOld.left = ptOrigin.x + rcCol.left + dx;
4442 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4444 /* we can restore focus now */
4445 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4448 /***
4449 * DESCRIPTION:
4450 * Removes a column from the listview control.
4452 * PARAMETER(S):
4453 * [I] infoPtr : valid pointer to the listview structure
4454 * [I] nColumn : column index
4456 * RETURN:
4457 * SUCCESS : TRUE
4458 * FAILURE : FALSE
4460 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4462 RECT rcCol;
4464 TRACE("nColumn=%d\n", nColumn);
4466 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4467 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4469 /* While the MSDN specifically says that column zero should not be deleted,
4470 what actually happens is that the column itself is deleted but no items or subitems
4471 are removed.
4474 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4476 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4477 return FALSE;
4479 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4480 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4482 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4484 SUBITEM_INFO *lpSubItem, *lpDelItem;
4485 HDPA hdpaSubItems;
4486 INT nItem, nSubItem, i;
4488 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4490 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4491 nSubItem = 0;
4492 lpDelItem = 0;
4493 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4495 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4496 if (lpSubItem->iSubItem == nColumn)
4498 nSubItem = i;
4499 lpDelItem = lpSubItem;
4501 else if (lpSubItem->iSubItem > nColumn)
4503 lpSubItem->iSubItem--;
4507 /* if we found our subitem, zapp it */
4508 if (nSubItem > 0)
4510 /* free string */
4511 if (is_textW(lpDelItem->hdr.pszText))
4512 Free(lpDelItem->hdr.pszText);
4514 /* free item */
4515 Free(lpDelItem);
4517 /* free dpa memory */
4518 DPA_DeletePtr(hdpaSubItems, nSubItem);
4523 /* update the other column info */
4524 LISTVIEW_UpdateItemSize(infoPtr);
4525 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4526 LISTVIEW_InvalidateList(infoPtr);
4527 else
4528 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4530 return TRUE;
4533 /***
4534 * DESCRIPTION:
4535 * Invalidates the listview after an item's insertion or deletion.
4537 * PARAMETER(S):
4538 * [I] infoPtr : valid pointer to the listview structure
4539 * [I] nItem : item index
4540 * [I] dir : -1 if deleting, 1 if inserting
4542 * RETURN:
4543 * None
4545 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4547 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4548 INT nPerCol, nItemCol, nItemRow;
4549 RECT rcScroll;
4550 POINT Origin;
4552 /* if we don't refresh, what's the point of scrolling? */
4553 if (!is_redrawing(infoPtr)) return;
4555 assert (abs(dir) == 1);
4557 /* arrange icons if autoarrange is on */
4558 if (is_autoarrange(infoPtr))
4560 BOOL arrange = TRUE;
4561 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4562 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4563 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4566 /* scrollbars need updating */
4567 LISTVIEW_UpdateScroll(infoPtr);
4569 /* figure out the item's position */
4570 if (uView == LVS_REPORT)
4571 nPerCol = infoPtr->nItemCount + 1;
4572 else if (uView == LVS_LIST)
4573 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4574 else /* LVS_ICON, or LVS_SMALLICON */
4575 return;
4577 nItemCol = nItem / nPerCol;
4578 nItemRow = nItem % nPerCol;
4579 LISTVIEW_GetOrigin(infoPtr, &Origin);
4581 /* move the items below up a slot */
4582 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4583 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4584 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4585 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4586 OffsetRect(&rcScroll, Origin.x, Origin.y);
4587 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4588 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4590 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4591 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4592 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4595 /* report has only that column, so we're done */
4596 if (uView == LVS_REPORT) return;
4598 /* now for LISTs, we have to deal with the columns to the right */
4599 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4600 rcScroll.top = 0;
4601 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4602 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4603 OffsetRect(&rcScroll, Origin.x, Origin.y);
4604 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4605 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4606 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4609 /***
4610 * DESCRIPTION:
4611 * Removes an item from the listview control.
4613 * PARAMETER(S):
4614 * [I] infoPtr : valid pointer to the listview structure
4615 * [I] nItem : item index
4617 * RETURN:
4618 * SUCCESS : TRUE
4619 * FAILURE : FALSE
4621 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4623 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4624 LVITEMW item;
4626 TRACE("(nItem=%d)\n", nItem);
4628 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4630 /* remove selection, and focus */
4631 item.state = 0;
4632 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4633 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4635 /* send LVN_DELETEITEM notification. */
4636 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4638 /* we need to do this here, because we'll be deleting stuff */
4639 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4640 LISTVIEW_InvalidateItem(infoPtr, nItem);
4642 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4644 HDPA hdpaSubItems;
4645 ITEMHDR *hdrItem;
4646 INT i;
4648 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4649 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4651 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4652 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4653 Free(hdrItem);
4655 DPA_Destroy(hdpaSubItems);
4658 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4660 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4661 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4664 infoPtr->nItemCount--;
4665 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4667 /* now is the invalidation fun */
4668 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4669 return TRUE;
4673 /***
4674 * DESCRIPTION:
4675 * Callback implementation for editlabel control
4677 * PARAMETER(S):
4678 * [I] infoPtr : valid pointer to the listview structure
4679 * [I] pszText : modified text
4680 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4682 * RETURN:
4683 * SUCCESS : TRUE
4684 * FAILURE : FALSE
4686 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4688 HWND hwndSelf = infoPtr->hwndSelf;
4689 NMLVDISPINFOW dispInfo;
4691 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4693 ZeroMemory(&dispInfo, sizeof(dispInfo));
4694 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4695 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4696 dispInfo.item.iSubItem = 0;
4697 dispInfo.item.stateMask = ~0;
4698 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4699 /* add the text from the edit in */
4700 dispInfo.item.mask |= LVIF_TEXT;
4701 dispInfo.item.pszText = pszText;
4702 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4704 /* Do we need to update the Item Text */
4705 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4706 if (!IsWindow(hwndSelf))
4707 return FALSE;
4708 if (!pszText) return TRUE;
4710 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4712 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4713 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4714 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4716 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4717 return TRUE;
4721 ZeroMemory(&dispInfo, sizeof(dispInfo));
4722 dispInfo.item.mask = LVIF_TEXT;
4723 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4724 dispInfo.item.iSubItem = 0;
4725 dispInfo.item.pszText = pszText;
4726 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4727 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4730 /***
4731 * DESCRIPTION:
4732 * Begin in place editing of specified list view item
4734 * PARAMETER(S):
4735 * [I] infoPtr : valid pointer to the listview structure
4736 * [I] nItem : item index
4737 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4739 * RETURN:
4740 * SUCCESS : TRUE
4741 * FAILURE : FALSE
4743 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4745 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4746 NMLVDISPINFOW dispInfo;
4747 RECT rect;
4748 HWND hwndSelf = infoPtr->hwndSelf;
4750 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4752 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4753 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4755 infoPtr->nEditLabelItem = nItem;
4757 /* Is the EditBox still there, if so remove it */
4758 if(infoPtr->hwndEdit != 0)
4760 SetFocus(infoPtr->hwndSelf);
4761 infoPtr->hwndEdit = 0;
4764 LISTVIEW_SetSelection(infoPtr, nItem);
4765 LISTVIEW_SetItemFocus(infoPtr, nItem);
4766 LISTVIEW_InvalidateItem(infoPtr, nItem);
4768 rect.left = LVIR_LABEL;
4769 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4771 ZeroMemory(&dispInfo, sizeof(dispInfo));
4772 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4773 dispInfo.item.iItem = nItem;
4774 dispInfo.item.iSubItem = 0;
4775 dispInfo.item.stateMask = ~0;
4776 dispInfo.item.pszText = szDispText;
4777 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4778 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4780 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4781 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4782 if (!infoPtr->hwndEdit) return 0;
4784 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4786 if (!IsWindow(hwndSelf))
4787 return 0;
4788 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4789 infoPtr->hwndEdit = 0;
4790 return 0;
4793 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4794 SetFocus(infoPtr->hwndEdit);
4795 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4796 return infoPtr->hwndEdit;
4800 /***
4801 * DESCRIPTION:
4802 * Ensures the specified item is visible, scrolling into view if necessary.
4804 * PARAMETER(S):
4805 * [I] infoPtr : valid pointer to the listview structure
4806 * [I] nItem : item index
4807 * [I] bPartial : partially or entirely visible
4809 * RETURN:
4810 * SUCCESS : TRUE
4811 * FAILURE : FALSE
4813 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4815 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4816 INT nScrollPosHeight = 0;
4817 INT nScrollPosWidth = 0;
4818 INT nHorzAdjust = 0;
4819 INT nVertAdjust = 0;
4820 INT nHorzDiff = 0;
4821 INT nVertDiff = 0;
4822 RECT rcItem, rcTemp;
4824 rcItem.left = LVIR_BOUNDS;
4825 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4827 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4829 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4831 /* scroll left/right, but in LVS_REPORT mode */
4832 if (uView == LVS_LIST)
4833 nScrollPosWidth = infoPtr->nItemWidth;
4834 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4835 nScrollPosWidth = 1;
4837 if (rcItem.left < infoPtr->rcList.left)
4839 nHorzAdjust = -1;
4840 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4842 else
4844 nHorzAdjust = 1;
4845 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4849 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4851 /* scroll up/down, but not in LVS_LIST mode */
4852 if (uView == LVS_REPORT)
4853 nScrollPosHeight = infoPtr->nItemHeight;
4854 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4855 nScrollPosHeight = 1;
4857 if (rcItem.top < infoPtr->rcList.top)
4859 nVertAdjust = -1;
4860 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4862 else
4864 nVertAdjust = 1;
4865 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4869 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4871 if (nScrollPosWidth)
4873 INT diff = nHorzDiff / nScrollPosWidth;
4874 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4875 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4878 if (nScrollPosHeight)
4880 INT diff = nVertDiff / nScrollPosHeight;
4881 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4882 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4885 return TRUE;
4888 /***
4889 * DESCRIPTION:
4890 * Searches for an item with specific characteristics.
4892 * PARAMETER(S):
4893 * [I] hwnd : window handle
4894 * [I] nStart : base item index
4895 * [I] lpFindInfo : item information to look for
4897 * RETURN:
4898 * SUCCESS : index of item
4899 * FAILURE : -1
4901 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
4902 const LVFINDINFOW *lpFindInfo)
4904 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4905 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4906 BOOL bWrap = FALSE, bNearest = FALSE;
4907 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4908 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4909 POINT Position, Destination;
4910 LVITEMW lvItem;
4912 if (!lpFindInfo || nItem < 0) return -1;
4914 lvItem.mask = 0;
4915 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4917 lvItem.mask |= LVIF_TEXT;
4918 lvItem.pszText = szDispText;
4919 lvItem.cchTextMax = DISP_TEXT_SIZE;
4922 if (lpFindInfo->flags & LVFI_WRAP)
4923 bWrap = TRUE;
4925 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4926 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4928 POINT Origin;
4929 RECT rcArea;
4931 LISTVIEW_GetOrigin(infoPtr, &Origin);
4932 Destination.x = lpFindInfo->pt.x - Origin.x;
4933 Destination.y = lpFindInfo->pt.y - Origin.y;
4934 switch(lpFindInfo->vkDirection)
4936 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4937 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4938 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4939 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4940 case VK_HOME: Destination.x = Destination.y = 0; break;
4941 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4942 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4943 case VK_END:
4944 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4945 Destination.x = rcArea.right;
4946 Destination.y = rcArea.bottom;
4947 break;
4948 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4950 bNearest = TRUE;
4952 else Destination.x = Destination.y = 0;
4954 /* if LVFI_PARAM is specified, all other flags are ignored */
4955 if (lpFindInfo->flags & LVFI_PARAM)
4957 lvItem.mask |= LVIF_PARAM;
4958 bNearest = FALSE;
4959 lvItem.mask &= ~LVIF_TEXT;
4962 again:
4963 for (; nItem < nLast; nItem++)
4965 lvItem.iItem = nItem;
4966 lvItem.iSubItem = 0;
4967 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4969 if (lvItem.mask & LVIF_PARAM)
4971 if (lpFindInfo->lParam == lvItem.lParam)
4972 return nItem;
4973 else
4974 continue;
4977 if (lvItem.mask & LVIF_TEXT)
4979 if (lpFindInfo->flags & LVFI_PARTIAL)
4981 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4983 else
4985 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4989 if (!bNearest) return nItem;
4991 /* This is very inefficient. To do a good job here,
4992 * we need a sorted array of (x,y) item positions */
4993 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4995 /* compute the distance^2 to the destination */
4996 xdist = Destination.x - Position.x;
4997 ydist = Destination.y - Position.y;
4998 dist = xdist * xdist + ydist * ydist;
5000 /* remember the distance, and item if it's closer */
5001 if (dist < mindist)
5003 mindist = dist;
5004 nNearestItem = nItem;
5008 if (bWrap)
5010 nItem = 0;
5011 nLast = min(nStart + 1, infoPtr->nItemCount);
5012 bWrap = FALSE;
5013 goto again;
5016 return nNearestItem;
5019 /***
5020 * DESCRIPTION:
5021 * Searches for an item with specific characteristics.
5023 * PARAMETER(S):
5024 * [I] hwnd : window handle
5025 * [I] nStart : base item index
5026 * [I] lpFindInfo : item information to look for
5028 * RETURN:
5029 * SUCCESS : index of item
5030 * FAILURE : -1
5032 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5033 const LVFINDINFOA *lpFindInfo)
5035 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5036 LVFINDINFOW fiw;
5037 INT res;
5039 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5040 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5041 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5042 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
5043 return res;
5046 /***
5047 * DESCRIPTION:
5048 * Retrieves the background image of the listview control.
5050 * PARAMETER(S):
5051 * [I] infoPtr : valid pointer to the listview structure
5052 * [O] lpBkImage : background image attributes
5054 * RETURN:
5055 * SUCCESS : TRUE
5056 * FAILURE : FALSE
5058 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5059 /* { */
5060 /* FIXME (listview, "empty stub!\n"); */
5061 /* return FALSE; */
5062 /* } */
5064 /***
5065 * DESCRIPTION:
5066 * Retrieves column attributes.
5068 * PARAMETER(S):
5069 * [I] infoPtr : valid pointer to the listview structure
5070 * [I] nColumn : column index
5071 * [IO] lpColumn : column information
5072 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5073 * otherwise it is in fact a LPLVCOLUMNA
5075 * RETURN:
5076 * SUCCESS : TRUE
5077 * FAILURE : FALSE
5079 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5081 COLUMN_INFO *lpColumnInfo;
5082 HDITEMW hdi;
5084 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5085 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5087 /* initialize memory */
5088 ZeroMemory(&hdi, sizeof(hdi));
5090 if (lpColumn->mask & LVCF_TEXT)
5092 hdi.mask |= HDI_TEXT;
5093 hdi.pszText = lpColumn->pszText;
5094 hdi.cchTextMax = lpColumn->cchTextMax;
5097 if (lpColumn->mask & LVCF_IMAGE)
5098 hdi.mask |= HDI_IMAGE;
5100 if (lpColumn->mask & LVCF_ORDER)
5101 hdi.mask |= HDI_ORDER;
5103 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5105 if (lpColumn->mask & LVCF_FMT)
5106 lpColumn->fmt = lpColumnInfo->fmt;
5108 if (lpColumn->mask & LVCF_WIDTH)
5109 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5111 if (lpColumn->mask & LVCF_IMAGE)
5112 lpColumn->iImage = hdi.iImage;
5114 if (lpColumn->mask & LVCF_ORDER)
5115 lpColumn->iOrder = hdi.iOrder;
5117 return TRUE;
5121 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5123 INT i;
5125 if (!lpiArray)
5126 return FALSE;
5128 /* FIXME: little hack */
5129 for (i = 0; i < iCount; i++)
5130 lpiArray[i] = i;
5132 return TRUE;
5135 /***
5136 * DESCRIPTION:
5137 * Retrieves the column width.
5139 * PARAMETER(S):
5140 * [I] infoPtr : valid pointer to the listview structure
5141 * [I] int : column index
5143 * RETURN:
5144 * SUCCESS : column width
5145 * FAILURE : zero
5147 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5149 INT nColumnWidth = 0;
5150 RECT rcHeader;
5152 TRACE("nColumn=%d\n", nColumn);
5154 /* we have a 'column' in LIST and REPORT mode only */
5155 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5157 case LVS_LIST:
5158 nColumnWidth = infoPtr->nItemWidth;
5159 break;
5160 case LVS_REPORT:
5161 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
5162 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
5163 nColumnWidth = rcHeader.right - rcHeader.left;
5164 break;
5167 TRACE("nColumnWidth=%d\n", nColumnWidth);
5168 return nColumnWidth;
5171 /***
5172 * DESCRIPTION:
5173 * In list or report display mode, retrieves the number of items that can fit
5174 * vertically in the visible area. In icon or small icon display mode,
5175 * retrieves the total number of visible items.
5177 * PARAMETER(S):
5178 * [I] infoPtr : valid pointer to the listview structure
5180 * RETURN:
5181 * Number of fully visible items.
5183 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5185 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5187 case LVS_ICON:
5188 case LVS_SMALLICON:
5189 return infoPtr->nItemCount;
5190 case LVS_REPORT:
5191 return LISTVIEW_GetCountPerColumn(infoPtr);
5192 case LVS_LIST:
5193 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5195 assert(FALSE);
5196 return 0;
5199 /***
5200 * DESCRIPTION:
5201 * Retrieves an image list handle.
5203 * PARAMETER(S):
5204 * [I] infoPtr : valid pointer to the listview structure
5205 * [I] nImageList : image list identifier
5207 * RETURN:
5208 * SUCCESS : image list handle
5209 * FAILURE : NULL
5211 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5213 switch (nImageList)
5215 case LVSIL_NORMAL: return infoPtr->himlNormal;
5216 case LVSIL_SMALL: return infoPtr->himlSmall;
5217 case LVSIL_STATE: return infoPtr->himlState;
5219 return NULL;
5222 /* LISTVIEW_GetISearchString */
5224 /***
5225 * DESCRIPTION:
5226 * Retrieves item attributes.
5228 * PARAMETER(S):
5229 * [I] hwnd : window handle
5230 * [IO] lpLVItem : item info
5231 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5232 * if FALSE, then lpLVItem is a LPLVITEMA.
5234 * NOTE:
5235 * This is the internal 'GetItem' interface -- it tries to
5236 * be smart and avoid text copies, if possible, by modifying
5237 * lpLVItem->pszText to point to the text string. Please note
5238 * that this is not always possible (e.g. OWNERDATA), so on
5239 * entry you *must* supply valid values for pszText, and cchTextMax.
5240 * The only difference to the documented interface is that upon
5241 * return, you should use *only* the lpLVItem->pszText, rather than
5242 * the buffer pointer you provided on input. Most code already does
5243 * that, so it's not a problem.
5244 * For the two cases when the text must be copied (that is,
5245 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5247 * RETURN:
5248 * SUCCESS : TRUE
5249 * FAILURE : FALSE
5251 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5253 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5254 NMLVDISPINFOW dispInfo;
5255 ITEM_INFO *lpItem;
5256 ITEMHDR* pItemHdr;
5257 HDPA hdpaSubItems;
5258 INT isubitem;
5260 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5262 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5263 return FALSE;
5265 if (lpLVItem->mask == 0) return TRUE;
5267 /* make a local copy */
5268 isubitem = lpLVItem->iSubItem;
5270 /* a quick optimization if all we're asked is the focus state
5271 * these queries are worth optimising since they are common,
5272 * and can be answered in constant time, without the heavy accesses */
5273 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5274 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5276 lpLVItem->state = 0;
5277 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5278 lpLVItem->state |= LVIS_FOCUSED;
5279 return TRUE;
5282 ZeroMemory(&dispInfo, sizeof(dispInfo));
5284 /* if the app stores all the data, handle it separately */
5285 if (infoPtr->dwStyle & LVS_OWNERDATA)
5287 dispInfo.item.state = 0;
5289 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5290 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5292 /* NOTE: copy only fields which we _know_ are initialized, some apps
5293 * depend on the uninitialized fields being 0 */
5294 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5295 dispInfo.item.iItem = lpLVItem->iItem;
5296 dispInfo.item.iSubItem = isubitem;
5297 if (lpLVItem->mask & LVIF_TEXT)
5299 dispInfo.item.pszText = lpLVItem->pszText;
5300 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5302 if (lpLVItem->mask & LVIF_STATE)
5303 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5304 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5305 dispInfo.item.stateMask = lpLVItem->stateMask;
5306 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5308 /* full size structure expected - _WIN32IE >= 0x560 */
5309 *lpLVItem = dispInfo.item;
5311 else if (lpLVItem->mask & LVIF_INDENT)
5313 /* indent member expected - _WIN32IE >= 0x300 */
5314 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5316 else
5318 /* minimal structure expected */
5319 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5321 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5324 /* make sure lParam is zeroed out */
5325 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5327 /* we store only a little state, so if we're not asked, we're done */
5328 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5330 /* if focus is handled by us, report it */
5331 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5333 lpLVItem->state &= ~LVIS_FOCUSED;
5334 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5335 lpLVItem->state |= LVIS_FOCUSED;
5338 /* and do the same for selection, if we handle it */
5339 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5341 lpLVItem->state &= ~LVIS_SELECTED;
5342 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5343 lpLVItem->state |= LVIS_SELECTED;
5346 return TRUE;
5349 /* find the item and subitem structures before we proceed */
5350 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5351 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5352 assert (lpItem);
5354 if (isubitem)
5356 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5357 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5358 if (!lpSubItem)
5360 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5361 isubitem = 0;
5364 else
5365 pItemHdr = &lpItem->hdr;
5367 /* Do we need to query the state from the app? */
5368 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5370 dispInfo.item.mask |= LVIF_STATE;
5371 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5374 /* Do we need to enquire about the image? */
5375 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5376 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5378 dispInfo.item.mask |= LVIF_IMAGE;
5379 dispInfo.item.iImage = I_IMAGECALLBACK;
5382 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5383 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5385 dispInfo.item.mask |= LVIF_TEXT;
5386 dispInfo.item.pszText = lpLVItem->pszText;
5387 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5388 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5389 *dispInfo.item.pszText = '\0';
5392 /* If we don't have all the requested info, query the application */
5393 if (dispInfo.item.mask != 0)
5395 dispInfo.item.iItem = lpLVItem->iItem;
5396 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5397 dispInfo.item.lParam = lpItem->lParam;
5398 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5399 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5402 /* we should not store values for subitems */
5403 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5405 /* Now, handle the iImage field */
5406 if (dispInfo.item.mask & LVIF_IMAGE)
5408 lpLVItem->iImage = dispInfo.item.iImage;
5409 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5410 pItemHdr->iImage = dispInfo.item.iImage;
5412 else if (lpLVItem->mask & LVIF_IMAGE)
5414 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5415 lpLVItem->iImage = pItemHdr->iImage;
5416 else
5417 lpLVItem->iImage = 0;
5420 /* The pszText field */
5421 if (dispInfo.item.mask & LVIF_TEXT)
5423 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5424 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5426 lpLVItem->pszText = dispInfo.item.pszText;
5428 else if (lpLVItem->mask & LVIF_TEXT)
5430 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5431 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5434 /* Next is the lParam field */
5435 if (dispInfo.item.mask & LVIF_PARAM)
5437 lpLVItem->lParam = dispInfo.item.lParam;
5438 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5439 lpItem->lParam = dispInfo.item.lParam;
5441 else if (lpLVItem->mask & LVIF_PARAM)
5442 lpLVItem->lParam = lpItem->lParam;
5444 /* if this is a subitem, we're done */
5445 if (isubitem) return TRUE;
5447 /* ... the state field (this one is different due to uCallbackmask) */
5448 if (lpLVItem->mask & LVIF_STATE)
5450 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5451 if (dispInfo.item.mask & LVIF_STATE)
5453 lpLVItem->state &= ~dispInfo.item.stateMask;
5454 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5456 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5458 lpLVItem->state &= ~LVIS_FOCUSED;
5459 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5460 lpLVItem->state |= LVIS_FOCUSED;
5462 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5464 lpLVItem->state &= ~LVIS_SELECTED;
5465 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5466 lpLVItem->state |= LVIS_SELECTED;
5470 /* and last, but not least, the indent field */
5471 if (lpLVItem->mask & LVIF_INDENT)
5472 lpLVItem->iIndent = lpItem->iIndent;
5474 return TRUE;
5477 /***
5478 * DESCRIPTION:
5479 * Retrieves item attributes.
5481 * PARAMETER(S):
5482 * [I] hwnd : window handle
5483 * [IO] lpLVItem : item info
5484 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5485 * if FALSE, then lpLVItem is a LPLVITEMA.
5487 * NOTE:
5488 * This is the external 'GetItem' interface -- it properly copies
5489 * the text in the provided buffer.
5491 * RETURN:
5492 * SUCCESS : TRUE
5493 * FAILURE : FALSE
5495 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5497 LPWSTR pszText;
5498 BOOL bResult;
5500 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5501 return FALSE;
5503 pszText = lpLVItem->pszText;
5504 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5505 if (bResult && lpLVItem->pszText != pszText)
5506 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5507 lpLVItem->pszText = pszText;
5509 return bResult;
5513 /***
5514 * DESCRIPTION:
5515 * Retrieves the position (upper-left) of the listview control item.
5516 * Note that for LVS_ICON style, the upper-left is that of the icon
5517 * and not the bounding box.
5519 * PARAMETER(S):
5520 * [I] infoPtr : valid pointer to the listview structure
5521 * [I] nItem : item index
5522 * [O] lpptPosition : coordinate information
5524 * RETURN:
5525 * SUCCESS : TRUE
5526 * FAILURE : FALSE
5528 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5530 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5531 POINT Origin;
5533 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5535 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5537 LISTVIEW_GetOrigin(infoPtr, &Origin);
5538 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5540 if (uView == LVS_ICON)
5542 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5543 lpptPosition->y += ICON_TOP_PADDING;
5545 lpptPosition->x += Origin.x;
5546 lpptPosition->y += Origin.y;
5548 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5549 return TRUE;
5553 /***
5554 * DESCRIPTION:
5555 * Retrieves the bounding rectangle for a listview control item.
5557 * PARAMETER(S):
5558 * [I] infoPtr : valid pointer to the listview structure
5559 * [I] nItem : item index
5560 * [IO] lprc : bounding rectangle coordinates
5561 * lprc->left specifies the portion of the item for which the bounding
5562 * rectangle will be retrieved.
5564 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5565 * including the icon and label.
5567 * * For LVS_ICON
5568 * * Experiment shows that native control returns:
5569 * * width = min (48, length of text line)
5570 * * .left = position.x - (width - iconsize.cx)/2
5571 * * .right = .left + width
5572 * * height = #lines of text * ntmHeight + icon height + 8
5573 * * .top = position.y - 2
5574 * * .bottom = .top + height
5575 * * separation between items .y = itemSpacing.cy - height
5576 * * .x = itemSpacing.cx - width
5577 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5579 * * For LVS_ICON
5580 * * Experiment shows that native control returns:
5581 * * width = iconSize.cx + 16
5582 * * .left = position.x - (width - iconsize.cx)/2
5583 * * .right = .left + width
5584 * * height = iconSize.cy + 4
5585 * * .top = position.y - 2
5586 * * .bottom = .top + height
5587 * * separation between items .y = itemSpacing.cy - height
5588 * * .x = itemSpacing.cx - width
5589 * LVIR_LABEL Returns the bounding rectangle of the item text.
5591 * * For LVS_ICON
5592 * * Experiment shows that native control returns:
5593 * * width = text length
5594 * * .left = position.x - width/2
5595 * * .right = .left + width
5596 * * height = ntmH * linecount + 2
5597 * * .top = position.y + iconSize.cy + 6
5598 * * .bottom = .top + height
5599 * * separation between items .y = itemSpacing.cy - height
5600 * * .x = itemSpacing.cx - width
5601 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5602 * rectangles, but excludes columns in report view.
5604 * RETURN:
5605 * SUCCESS : TRUE
5606 * FAILURE : FALSE
5608 * NOTES
5609 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5610 * upon whether the window has the focus currently and on whether the item
5611 * is the one with the focus. Ensure that the control's record of which
5612 * item has the focus agrees with the items' records.
5614 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5616 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5617 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5618 BOOL doLabel = TRUE, oversizedBox = FALSE;
5619 POINT Position, Origin;
5620 LVITEMW lvItem;
5622 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5624 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5626 LISTVIEW_GetOrigin(infoPtr, &Origin);
5627 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5629 /* Be smart and try to figure out the minimum we have to do */
5630 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5631 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5632 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5633 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5634 oversizedBox = TRUE;
5636 /* get what we need from the item before hand, so we make
5637 * only one request. This can speed up things, if data
5638 * is stored on the app side */
5639 lvItem.mask = 0;
5640 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5641 if (doLabel) lvItem.mask |= LVIF_TEXT;
5642 lvItem.iItem = nItem;
5643 lvItem.iSubItem = 0;
5644 lvItem.pszText = szDispText;
5645 lvItem.cchTextMax = DISP_TEXT_SIZE;
5646 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5647 /* we got the state already up, simulate it here, to avoid a reget */
5648 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5650 lvItem.mask |= LVIF_STATE;
5651 lvItem.stateMask = LVIS_FOCUSED;
5652 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5655 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5656 lprc->left = LVIR_BOUNDS;
5657 switch(lprc->left)
5659 case LVIR_ICON:
5660 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5661 break;
5663 case LVIR_LABEL:
5664 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5665 break;
5667 case LVIR_BOUNDS:
5668 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5669 break;
5671 case LVIR_SELECTBOUNDS:
5672 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5673 break;
5675 default:
5676 WARN("Unknown value: %d\n", lprc->left);
5677 return FALSE;
5680 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5682 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5684 return TRUE;
5687 /***
5688 * DESCRIPTION:
5689 * Retrieves the spacing between listview control items.
5691 * PARAMETER(S):
5692 * [I] infoPtr : valid pointer to the listview structure
5693 * [IO] lprc : rectangle to receive the output
5694 * on input, lprc->top = nSubItem
5695 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5697 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5698 * not only those of the first column.
5699 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5701 * RETURN:
5702 * TRUE: success
5703 * FALSE: failure
5705 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5707 POINT Position;
5708 LVITEMW lvItem;
5709 INT nColumn;
5711 if (!lprc) return FALSE;
5713 nColumn = lprc->top;
5715 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5716 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5717 if (lprc->top == 0)
5718 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5720 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5722 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5724 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5726 lvItem.mask = 0;
5727 lvItem.iItem = nItem;
5728 lvItem.iSubItem = nColumn;
5730 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5731 switch(lprc->left)
5733 case LVIR_ICON:
5734 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5735 break;
5737 case LVIR_LABEL:
5738 case LVIR_BOUNDS:
5739 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5740 break;
5742 default:
5743 ERR("Unknown bounds=%d\n", lprc->left);
5744 return FALSE;
5747 OffsetRect(lprc, Position.x, Position.y);
5748 return TRUE;
5752 /***
5753 * DESCRIPTION:
5754 * Retrieves the width of a label.
5756 * PARAMETER(S):
5757 * [I] infoPtr : valid pointer to the listview structure
5759 * RETURN:
5760 * SUCCESS : string width (in pixels)
5761 * FAILURE : zero
5763 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5765 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5766 LVITEMW lvItem;
5768 TRACE("(nItem=%d)\n", nItem);
5770 lvItem.mask = LVIF_TEXT;
5771 lvItem.iItem = nItem;
5772 lvItem.iSubItem = 0;
5773 lvItem.pszText = szDispText;
5774 lvItem.cchTextMax = DISP_TEXT_SIZE;
5775 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5777 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5780 /***
5781 * DESCRIPTION:
5782 * Retrieves the spacing between listview control items.
5784 * PARAMETER(S):
5785 * [I] infoPtr : valid pointer to the listview structure
5786 * [I] bSmall : flag for small or large icon
5788 * RETURN:
5789 * Horizontal + vertical spacing
5791 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5793 LONG lResult;
5795 if (!bSmall)
5797 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5799 else
5801 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5802 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5803 else
5804 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5806 return lResult;
5809 /***
5810 * DESCRIPTION:
5811 * Retrieves the state of a listview control item.
5813 * PARAMETER(S):
5814 * [I] infoPtr : valid pointer to the listview structure
5815 * [I] nItem : item index
5816 * [I] uMask : state mask
5818 * RETURN:
5819 * State specified by the mask.
5821 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5823 LVITEMW lvItem;
5825 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5827 lvItem.iItem = nItem;
5828 lvItem.iSubItem = 0;
5829 lvItem.mask = LVIF_STATE;
5830 lvItem.stateMask = uMask;
5831 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5833 return lvItem.state & uMask;
5836 /***
5837 * DESCRIPTION:
5838 * Retrieves the text of a listview control item or subitem.
5840 * PARAMETER(S):
5841 * [I] hwnd : window handle
5842 * [I] nItem : item index
5843 * [IO] lpLVItem : item information
5844 * [I] isW : TRUE if lpLVItem is Unicode
5846 * RETURN:
5847 * SUCCESS : string length
5848 * FAILURE : 0
5850 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5852 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5854 lpLVItem->mask = LVIF_TEXT;
5855 lpLVItem->iItem = nItem;
5856 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5858 return textlenT(lpLVItem->pszText, isW);
5861 /***
5862 * DESCRIPTION:
5863 * Searches for an item based on properties + relationships.
5865 * PARAMETER(S):
5866 * [I] infoPtr : valid pointer to the listview structure
5867 * [I] nItem : item index
5868 * [I] uFlags : relationship flag
5870 * RETURN:
5871 * SUCCESS : item index
5872 * FAILURE : -1
5874 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5876 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5877 UINT uMask = 0;
5878 LVFINDINFOW lvFindInfo;
5879 INT nCountPerColumn;
5880 INT nCountPerRow;
5881 INT i;
5883 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5884 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5886 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5888 if (uFlags & LVNI_CUT)
5889 uMask |= LVIS_CUT;
5891 if (uFlags & LVNI_DROPHILITED)
5892 uMask |= LVIS_DROPHILITED;
5894 if (uFlags & LVNI_FOCUSED)
5895 uMask |= LVIS_FOCUSED;
5897 if (uFlags & LVNI_SELECTED)
5898 uMask |= LVIS_SELECTED;
5900 /* if we're asked for the focused item, that's only one,
5901 * so it's worth optimizing */
5902 if (uFlags & LVNI_FOCUSED)
5904 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
5905 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5908 if (uFlags & LVNI_ABOVE)
5910 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5912 while (nItem >= 0)
5914 nItem--;
5915 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5916 return nItem;
5919 else
5921 /* Special case for autoarrange - move 'til the top of a list */
5922 if (is_autoarrange(infoPtr))
5924 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5925 while (nItem - nCountPerRow >= 0)
5927 nItem -= nCountPerRow;
5928 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5929 return nItem;
5931 return -1;
5933 lvFindInfo.flags = LVFI_NEARESTXY;
5934 lvFindInfo.vkDirection = VK_UP;
5935 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5936 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5938 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5939 return nItem;
5943 else if (uFlags & LVNI_BELOW)
5945 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5947 while (nItem < infoPtr->nItemCount)
5949 nItem++;
5950 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5951 return nItem;
5954 else
5956 /* Special case for autoarrange - move 'til the bottom of a list */
5957 if (is_autoarrange(infoPtr))
5959 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5960 while (nItem + nCountPerRow < infoPtr->nItemCount )
5962 nItem += nCountPerRow;
5963 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5964 return nItem;
5966 return -1;
5968 lvFindInfo.flags = LVFI_NEARESTXY;
5969 lvFindInfo.vkDirection = VK_DOWN;
5970 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5971 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5973 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5974 return nItem;
5978 else if (uFlags & LVNI_TOLEFT)
5980 if (uView == LVS_LIST)
5982 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5983 while (nItem - nCountPerColumn >= 0)
5985 nItem -= nCountPerColumn;
5986 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5987 return nItem;
5990 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5992 /* Special case for autoarrange - move 'ti the beginning of a row */
5993 if (is_autoarrange(infoPtr))
5995 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5996 while (nItem % nCountPerRow > 0)
5998 nItem --;
5999 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6000 return nItem;
6002 return -1;
6004 lvFindInfo.flags = LVFI_NEARESTXY;
6005 lvFindInfo.vkDirection = VK_LEFT;
6006 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6007 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6009 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6010 return nItem;
6014 else if (uFlags & LVNI_TORIGHT)
6016 if (uView == LVS_LIST)
6018 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6019 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6021 nItem += nCountPerColumn;
6022 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6023 return nItem;
6026 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6028 /* Special case for autoarrange - move 'til the end of a row */
6029 if (is_autoarrange(infoPtr))
6031 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6032 while (nItem % nCountPerRow < nCountPerRow - 1 )
6034 nItem ++;
6035 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6036 return nItem;
6038 return -1;
6040 lvFindInfo.flags = LVFI_NEARESTXY;
6041 lvFindInfo.vkDirection = VK_RIGHT;
6042 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6043 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6045 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6046 return nItem;
6050 else
6052 nItem++;
6054 /* search by index */
6055 for (i = nItem; i < infoPtr->nItemCount; i++)
6057 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6058 return i;
6062 return -1;
6065 /* LISTVIEW_GetNumberOfWorkAreas */
6067 /***
6068 * DESCRIPTION:
6069 * Retrieves the origin coordinates when in icon or small icon display mode.
6071 * PARAMETER(S):
6072 * [I] infoPtr : valid pointer to the listview structure
6073 * [O] lpptOrigin : coordinate information
6075 * RETURN:
6076 * None.
6078 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6080 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6081 INT nHorzPos = 0, nVertPos = 0;
6082 SCROLLINFO scrollInfo;
6084 scrollInfo.cbSize = sizeof(SCROLLINFO);
6085 scrollInfo.fMask = SIF_POS;
6087 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6088 nHorzPos = scrollInfo.nPos;
6089 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6090 nVertPos = scrollInfo.nPos;
6092 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6094 lpptOrigin->x = infoPtr->rcList.left;
6095 lpptOrigin->y = infoPtr->rcList.top;
6096 if (uView == LVS_LIST)
6097 nHorzPos *= infoPtr->nItemWidth;
6098 else if (uView == LVS_REPORT)
6099 nVertPos *= infoPtr->nItemHeight;
6101 lpptOrigin->x -= nHorzPos;
6102 lpptOrigin->y -= nVertPos;
6104 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6107 /***
6108 * DESCRIPTION:
6109 * Retrieves the width of a string.
6111 * PARAMETER(S):
6112 * [I] hwnd : window handle
6113 * [I] lpszText : text string to process
6114 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6116 * RETURN:
6117 * SUCCESS : string width (in pixels)
6118 * FAILURE : zero
6120 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6122 SIZE stringSize;
6124 stringSize.cx = 0;
6125 if (is_textT(lpszText, isW))
6127 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6128 HDC hdc = GetDC(infoPtr->hwndSelf);
6129 HFONT hOldFont = SelectObject(hdc, hFont);
6131 if (isW)
6132 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6133 else
6134 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6135 SelectObject(hdc, hOldFont);
6136 ReleaseDC(infoPtr->hwndSelf, hdc);
6138 return stringSize.cx;
6141 /***
6142 * DESCRIPTION:
6143 * Determines which listview item is located at the specified position.
6145 * PARAMETER(S):
6146 * [I] infoPtr : valid pointer to the listview structure
6147 * [IO] lpht : hit test information
6148 * [I] subitem : fill out iSubItem.
6149 * [I] select : return the index only if the hit selects the item
6151 * NOTE:
6152 * (mm 20001022): We must not allow iSubItem to be touched, for
6153 * an app might pass only a structure with space up to iItem!
6154 * (MS Office 97 does that for instance in the file open dialog)
6156 * RETURN:
6157 * SUCCESS : item index
6158 * FAILURE : -1
6160 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6162 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6163 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6164 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6165 POINT Origin, Position, opt;
6166 LVITEMW lvItem;
6167 ITERATOR i;
6168 INT iItem;
6170 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6172 lpht->flags = 0;
6173 lpht->iItem = -1;
6174 if (subitem) lpht->iSubItem = 0;
6176 if (infoPtr->rcList.left > lpht->pt.x)
6177 lpht->flags |= LVHT_TOLEFT;
6178 else if (infoPtr->rcList.right < lpht->pt.x)
6179 lpht->flags |= LVHT_TORIGHT;
6181 if (infoPtr->rcList.top > lpht->pt.y)
6182 lpht->flags |= LVHT_ABOVE;
6183 else if (infoPtr->rcList.bottom < lpht->pt.y)
6184 lpht->flags |= LVHT_BELOW;
6186 TRACE("lpht->flags=0x%x\n", lpht->flags);
6187 if (lpht->flags) return -1;
6189 lpht->flags |= LVHT_NOWHERE;
6191 LISTVIEW_GetOrigin(infoPtr, &Origin);
6193 /* first deal with the large items */
6194 rcSearch.left = lpht->pt.x;
6195 rcSearch.top = lpht->pt.y;
6196 rcSearch.right = rcSearch.left + 1;
6197 rcSearch.bottom = rcSearch.top + 1;
6199 iterator_frameditems(&i, infoPtr, &rcSearch);
6200 iterator_next(&i); /* go to first item in the sequence */
6201 iItem = i.nItem;
6202 iterator_destroy(&i);
6204 TRACE("lpht->iItem=%d\n", iItem);
6205 if (iItem == -1) return -1;
6207 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6208 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6209 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6210 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6211 lvItem.iItem = iItem;
6212 lvItem.iSubItem = 0;
6213 lvItem.pszText = szDispText;
6214 lvItem.cchTextMax = DISP_TEXT_SIZE;
6215 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6216 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6218 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6219 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6220 opt.x = lpht->pt.x - Position.x - Origin.x;
6221 opt.y = lpht->pt.y - Position.y - Origin.y;
6223 if (uView == LVS_REPORT)
6224 rcBounds = rcBox;
6225 else
6226 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6227 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6228 if (!PtInRect(&rcBounds, opt)) return -1;
6230 if (PtInRect(&rcIcon, opt))
6231 lpht->flags |= LVHT_ONITEMICON;
6232 else if (PtInRect(&rcLabel, opt))
6233 lpht->flags |= LVHT_ONITEMLABEL;
6234 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6235 lpht->flags |= LVHT_ONITEMSTATEICON;
6236 if (lpht->flags & LVHT_ONITEM)
6237 lpht->flags &= ~LVHT_NOWHERE;
6239 TRACE("lpht->flags=0x%x\n", lpht->flags);
6240 if (uView == LVS_REPORT && subitem)
6242 INT j;
6244 rcBounds.right = rcBounds.left;
6245 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6247 rcBounds.left = rcBounds.right;
6248 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6249 if (PtInRect(&rcBounds, opt))
6251 lpht->iSubItem = j;
6252 break;
6257 if (select && !(uView == LVS_REPORT &&
6258 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6259 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6261 if (uView == LVS_REPORT)
6263 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6264 UnionRect(&rcBounds, &rcBounds, &rcState);
6266 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6268 return lpht->iItem = iItem;
6272 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6273 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6274 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6275 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6276 their own sort proc. when sending LVM_SORTITEMS.
6278 /* Platform SDK:
6279 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6281 LVS_SORTXXX must be specified,
6282 LVS_OWNERDRAW is not set,
6283 <item>.pszText is not LPSTR_TEXTCALLBACK.
6285 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6286 are sorted based on item text..."
6288 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6290 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6291 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6292 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6294 /* if we're sorting descending, negate the return value */
6295 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6298 /***
6299 * DESCRIPTION:
6300 * Inserts a new item in the listview control.
6302 * PARAMETER(S):
6303 * [I] infoPtr : valid pointer to the listview structure
6304 * [I] lpLVItem : item information
6305 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6307 * RETURN:
6308 * SUCCESS : new item index
6309 * FAILURE : -1
6311 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6313 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6314 INT nItem;
6315 HDPA hdpaSubItems;
6316 NMLISTVIEW nmlv;
6317 ITEM_INFO *lpItem;
6318 BOOL is_sorted, has_changed;
6319 LVITEMW item;
6320 HWND hwndSelf = infoPtr->hwndSelf;
6322 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6324 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6326 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6327 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6329 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6331 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6333 /* insert item in listview control data structure */
6334 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6335 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6337 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6338 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6340 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6342 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6343 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6344 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6345 if (nItem == -1) goto fail;
6346 infoPtr->nItemCount++;
6348 /* shift indices first so they don't get tangled */
6349 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6351 /* set the item attributes */
6352 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6354 /* full size structure expected - _WIN32IE >= 0x560 */
6355 item = *lpLVItem;
6357 else if (lpLVItem->mask & LVIF_INDENT)
6359 /* indent member expected - _WIN32IE >= 0x300 */
6360 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6362 else
6364 /* minimal structure expected */
6365 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6367 item.iItem = nItem;
6368 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6370 item.mask |= LVIF_STATE;
6371 item.stateMask |= LVIS_STATEIMAGEMASK;
6372 item.state &= ~LVIS_STATEIMAGEMASK;
6373 item.state |= INDEXTOSTATEIMAGEMASK(1);
6375 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6377 /* if we're sorted, sort the list, and update the index */
6378 if (is_sorted)
6380 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6381 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6382 assert(nItem != -1);
6385 /* make room for the position, if we are in the right mode */
6386 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6388 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6389 goto undo;
6390 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6392 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6393 goto undo;
6397 /* send LVN_INSERTITEM notification */
6398 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6399 nmlv.iItem = nItem;
6400 nmlv.lParam = lpItem->lParam;
6401 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6402 if (!IsWindow(hwndSelf))
6403 return -1;
6405 /* align items (set position of each item) */
6406 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6408 POINT pt;
6410 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6411 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6412 else
6413 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6415 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6418 /* now is the invalidation fun */
6419 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6420 return nItem;
6422 undo:
6423 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6424 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6425 infoPtr->nItemCount--;
6426 fail:
6427 DPA_DeletePtr(hdpaSubItems, 0);
6428 DPA_Destroy (hdpaSubItems);
6429 Free (lpItem);
6430 return -1;
6433 /***
6434 * DESCRIPTION:
6435 * Redraws a range of items.
6437 * PARAMETER(S):
6438 * [I] infoPtr : valid pointer to the listview structure
6439 * [I] nFirst : first item
6440 * [I] nLast : last item
6442 * RETURN:
6443 * SUCCESS : TRUE
6444 * FAILURE : FALSE
6446 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6448 INT i;
6450 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6451 max(nFirst, nLast) >= infoPtr->nItemCount)
6452 return FALSE;
6454 for (i = nFirst; i <= nLast; i++)
6455 LISTVIEW_InvalidateItem(infoPtr, i);
6457 return TRUE;
6460 /***
6461 * DESCRIPTION:
6462 * Scroll the content of a listview.
6464 * PARAMETER(S):
6465 * [I] infoPtr : valid pointer to the listview structure
6466 * [I] dx : horizontal scroll amount in pixels
6467 * [I] dy : vertical scroll amount in pixels
6469 * RETURN:
6470 * SUCCESS : TRUE
6471 * FAILURE : FALSE
6473 * COMMENTS:
6474 * If the control is in report mode (LVS_REPORT) the control can
6475 * be scrolled only in line increments. "dy" will be rounded to the
6476 * nearest number of pixels that are a whole line. Ex: if line height
6477 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6478 * is passed the the scroll will be 0. (per MSDN 7/2002)
6480 * For: (per experimentaion with native control and CSpy ListView)
6481 * LVS_ICON dy=1 = 1 pixel (vertical only)
6482 * dx ignored
6483 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6484 * dx ignored
6485 * LVS_LIST dx=1 = 1 column (horizontal only)
6486 * but will only scroll 1 column per message
6487 * no matter what the value.
6488 * dy must be 0 or FALSE returned.
6489 * LVS_REPORT dx=1 = 1 pixel
6490 * dy= see above
6493 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6495 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6496 case LVS_REPORT:
6497 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6498 dy /= infoPtr->nItemHeight;
6499 break;
6500 case LVS_LIST:
6501 if (dy != 0) return FALSE;
6502 break;
6503 default: /* icon */
6504 dx = 0;
6505 break;
6508 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6509 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6511 return TRUE;
6514 /***
6515 * DESCRIPTION:
6516 * Sets the background color.
6518 * PARAMETER(S):
6519 * [I] infoPtr : valid pointer to the listview structure
6520 * [I] clrBk : background color
6522 * RETURN:
6523 * SUCCESS : TRUE
6524 * FAILURE : FALSE
6526 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6528 TRACE("(clrBk=%x)\n", clrBk);
6530 if(infoPtr->clrBk != clrBk) {
6531 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6532 infoPtr->clrBk = clrBk;
6533 if (clrBk == CLR_NONE)
6534 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6535 else
6536 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6537 LISTVIEW_InvalidateList(infoPtr);
6540 return TRUE;
6543 /* LISTVIEW_SetBkImage */
6545 /*** Helper for {Insert,Set}ColumnT *only* */
6546 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6547 const LVCOLUMNW *lpColumn, BOOL isW)
6549 if (lpColumn->mask & LVCF_FMT)
6551 /* format member is valid */
6552 lphdi->mask |= HDI_FORMAT;
6554 /* set text alignment (leftmost column must be left-aligned) */
6555 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6556 lphdi->fmt |= HDF_LEFT;
6557 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6558 lphdi->fmt |= HDF_RIGHT;
6559 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6560 lphdi->fmt |= HDF_CENTER;
6562 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6563 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6565 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6567 lphdi->fmt |= HDF_IMAGE;
6568 lphdi->iImage = I_IMAGECALLBACK;
6572 if (lpColumn->mask & LVCF_WIDTH)
6574 lphdi->mask |= HDI_WIDTH;
6575 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6577 /* make it fill the remainder of the controls width */
6578 RECT rcHeader;
6579 INT item_index;
6581 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6583 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6584 lphdi->cxy += rcHeader.right - rcHeader.left;
6587 /* retrieve the layout of the header */
6588 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6589 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6591 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6593 else
6594 lphdi->cxy = lpColumn->cx;
6597 if (lpColumn->mask & LVCF_TEXT)
6599 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6600 lphdi->fmt |= HDF_STRING;
6601 lphdi->pszText = lpColumn->pszText;
6602 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6605 if (lpColumn->mask & LVCF_IMAGE)
6607 lphdi->mask |= HDI_IMAGE;
6608 lphdi->iImage = lpColumn->iImage;
6611 if (lpColumn->mask & LVCF_ORDER)
6613 lphdi->mask |= HDI_ORDER;
6614 lphdi->iOrder = lpColumn->iOrder;
6619 /***
6620 * DESCRIPTION:
6621 * Inserts a new column.
6623 * PARAMETER(S):
6624 * [I] infoPtr : valid pointer to the listview structure
6625 * [I] nColumn : column index
6626 * [I] lpColumn : column information
6627 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6629 * RETURN:
6630 * SUCCESS : new column index
6631 * FAILURE : -1
6633 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6634 const LVCOLUMNW *lpColumn, BOOL isW)
6636 COLUMN_INFO *lpColumnInfo;
6637 INT nNewColumn;
6638 HDITEMW hdi;
6640 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6642 if (!lpColumn || nColumn < 0) return -1;
6643 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6645 ZeroMemory(&hdi, sizeof(HDITEMW));
6646 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6649 * when the iSubItem is available Windows copies it to the header lParam. It seems
6650 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6652 if (lpColumn->mask & LVCF_SUBITEM)
6654 hdi.mask |= HDI_LPARAM;
6655 hdi.lParam = lpColumn->iSubItem;
6658 /* insert item in header control */
6659 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6660 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6661 (WPARAM)nColumn, (LPARAM)&hdi);
6662 if (nNewColumn == -1) return -1;
6663 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6665 /* create our own column info */
6666 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6667 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6669 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6670 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6672 /* now we have to actually adjust the data */
6673 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6675 SUBITEM_INFO *lpSubItem;
6676 HDPA hdpaSubItems;
6677 INT nItem, i;
6679 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6681 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6682 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6684 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6685 if (lpSubItem->iSubItem >= nNewColumn)
6686 lpSubItem->iSubItem++;
6691 /* make space for the new column */
6692 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6693 LISTVIEW_UpdateItemSize(infoPtr);
6695 return nNewColumn;
6697 fail:
6698 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6699 if (lpColumnInfo)
6701 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6702 Free(lpColumnInfo);
6704 return -1;
6707 /***
6708 * DESCRIPTION:
6709 * Sets the attributes of a header item.
6711 * PARAMETER(S):
6712 * [I] infoPtr : valid pointer to the listview structure
6713 * [I] nColumn : column index
6714 * [I] lpColumn : column attributes
6715 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6717 * RETURN:
6718 * SUCCESS : TRUE
6719 * FAILURE : FALSE
6721 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6722 const LVCOLUMNW *lpColumn, BOOL isW)
6724 HDITEMW hdi, hdiget;
6725 BOOL bResult;
6727 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6729 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6731 ZeroMemory(&hdi, sizeof(HDITEMW));
6732 if (lpColumn->mask & LVCF_FMT)
6734 hdi.mask |= HDI_FORMAT;
6735 hdiget.mask = HDI_FORMAT;
6736 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6737 hdi.fmt = hdiget.fmt & HDF_STRING;
6739 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6741 /* set header item attributes */
6742 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6743 if (!bResult) return FALSE;
6745 if (lpColumn->mask & LVCF_FMT)
6747 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6748 int oldFmt = lpColumnInfo->fmt;
6750 lpColumnInfo->fmt = lpColumn->fmt;
6751 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6753 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6754 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6758 return TRUE;
6761 /***
6762 * DESCRIPTION:
6763 * Sets the column order array
6765 * PARAMETERS:
6766 * [I] infoPtr : valid pointer to the listview structure
6767 * [I] iCount : number of elements in column order array
6768 * [I] lpiArray : pointer to column order array
6770 * RETURN:
6771 * SUCCESS : TRUE
6772 * FAILURE : FALSE
6774 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6776 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6778 if (!lpiArray)
6779 return FALSE;
6781 return TRUE;
6785 /***
6786 * DESCRIPTION:
6787 * Sets the width of a column
6789 * PARAMETERS:
6790 * [I] infoPtr : valid pointer to the listview structure
6791 * [I] nColumn : column index
6792 * [I] cx : column width
6794 * RETURN:
6795 * SUCCESS : TRUE
6796 * FAILURE : FALSE
6798 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6800 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6801 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6802 INT max_cx = 0;
6803 HDITEMW hdi;
6805 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6807 /* set column width only if in report or list mode */
6808 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6810 /* take care of invalid cx values */
6811 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6812 else if (uView == LVS_LIST && cx < 1) return FALSE;
6814 /* resize all columns if in LVS_LIST mode */
6815 if(uView == LVS_LIST)
6817 infoPtr->nItemWidth = cx;
6818 LISTVIEW_InvalidateList(infoPtr);
6819 return TRUE;
6822 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6824 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6826 INT nLabelWidth;
6827 LVITEMW lvItem;
6829 lvItem.mask = LVIF_TEXT;
6830 lvItem.iItem = 0;
6831 lvItem.iSubItem = nColumn;
6832 lvItem.pszText = szDispText;
6833 lvItem.cchTextMax = DISP_TEXT_SIZE;
6834 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6836 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6837 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6838 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6840 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6841 max_cx += infoPtr->iconSize.cx;
6842 max_cx += TRAILING_LABEL_PADDING;
6845 /* autosize based on listview items width */
6846 if(cx == LVSCW_AUTOSIZE)
6847 cx = max_cx;
6848 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6850 /* if iCol is the last column make it fill the remainder of the controls width */
6851 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6853 RECT rcHeader;
6854 POINT Origin;
6856 LISTVIEW_GetOrigin(infoPtr, &Origin);
6857 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6859 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6861 else
6863 /* Despite what the MS docs say, if this is not the last
6864 column, then MS resizes the column to the width of the
6865 largest text string in the column, including headers
6866 and items. This is different from LVSCW_AUTOSIZE in that
6867 LVSCW_AUTOSIZE ignores the header string length. */
6868 cx = 0;
6870 /* retrieve header text */
6871 hdi.mask = HDI_TEXT;
6872 hdi.cchTextMax = DISP_TEXT_SIZE;
6873 hdi.pszText = szDispText;
6874 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
6876 HDC hdc = GetDC(infoPtr->hwndSelf);
6877 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6878 SIZE size;
6880 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6881 cx = size.cx + TRAILING_HEADER_PADDING;
6882 /* FIXME: Take into account the header image, if one is present */
6883 SelectObject(hdc, old_font);
6884 ReleaseDC(infoPtr->hwndSelf, hdc);
6886 cx = max (cx, max_cx);
6890 if (cx < 0) return FALSE;
6892 /* call header to update the column change */
6893 hdi.mask = HDI_WIDTH;
6894 hdi.cxy = cx;
6895 TRACE("hdi.cxy=%d\n", hdi.cxy);
6896 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
6899 /***
6900 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6903 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
6905 HDC hdc_wnd, hdc;
6906 HBITMAP hbm_im, hbm_mask, hbm_orig;
6907 RECT rc;
6908 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6909 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6910 HIMAGELIST himl;
6912 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6913 ILC_COLOR | ILC_MASK, 2, 2);
6914 hdc_wnd = GetDC(infoPtr->hwndSelf);
6915 hdc = CreateCompatibleDC(hdc_wnd);
6916 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6917 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6918 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6920 rc.left = rc.top = 0;
6921 rc.right = GetSystemMetrics(SM_CXSMICON);
6922 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6924 hbm_orig = SelectObject(hdc, hbm_mask);
6925 FillRect(hdc, &rc, hbr_white);
6926 InflateRect(&rc, -3, -3);
6927 FillRect(hdc, &rc, hbr_black);
6929 SelectObject(hdc, hbm_im);
6930 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6931 SelectObject(hdc, hbm_orig);
6932 ImageList_Add(himl, hbm_im, hbm_mask);
6934 SelectObject(hdc, hbm_im);
6935 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6936 SelectObject(hdc, hbm_orig);
6937 ImageList_Add(himl, hbm_im, hbm_mask);
6939 DeleteObject(hbm_mask);
6940 DeleteObject(hbm_im);
6941 DeleteDC(hdc);
6943 return himl;
6946 /***
6947 * DESCRIPTION:
6948 * Sets the extended listview style.
6950 * PARAMETERS:
6951 * [I] infoPtr : valid pointer to the listview structure
6952 * [I] dwMask : mask
6953 * [I] dwStyle : style
6955 * RETURN:
6956 * SUCCESS : previous style
6957 * FAILURE : 0
6959 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6961 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6963 /* set new style */
6964 if (dwMask)
6965 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6966 else
6967 infoPtr->dwLvExStyle = dwStyle;
6969 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6971 HIMAGELIST himl = 0;
6972 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6974 LVITEMW item;
6975 item.mask = LVIF_STATE;
6976 item.stateMask = LVIS_STATEIMAGEMASK;
6977 item.state = INDEXTOSTATEIMAGEMASK(1);
6978 LISTVIEW_SetItemState(infoPtr, -1, &item);
6980 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6982 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6985 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_HEADERDRAGDROP)
6987 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
6988 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
6989 dwStyle |= HDS_DRAGDROP;
6990 else
6991 dwStyle &= ~HDS_DRAGDROP;
6992 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
6995 return dwOldStyle;
6998 /***
6999 * DESCRIPTION:
7000 * Sets the new hot cursor used during hot tracking and hover selection.
7002 * PARAMETER(S):
7003 * [I] infoPtr : valid pointer to the listview structure
7004 * [I] hCursor : the new hot cursor handle
7006 * RETURN:
7007 * Returns the previous hot cursor
7009 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7011 HCURSOR oldCursor = infoPtr->hHotCursor;
7013 infoPtr->hHotCursor = hCursor;
7015 return oldCursor;
7019 /***
7020 * DESCRIPTION:
7021 * Sets the hot item index.
7023 * PARAMETERS:
7024 * [I] infoPtr : valid pointer to the listview structure
7025 * [I] iIndex : index
7027 * RETURN:
7028 * SUCCESS : previous hot item index
7029 * FAILURE : -1 (no hot item)
7031 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7033 INT iOldIndex = infoPtr->nHotItem;
7035 infoPtr->nHotItem = iIndex;
7037 return iOldIndex;
7041 /***
7042 * DESCRIPTION:
7043 * Sets the amount of time the cursor must hover over an item before it is selected.
7045 * PARAMETER(S):
7046 * [I] infoPtr : valid pointer to the listview structure
7047 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7049 * RETURN:
7050 * Returns the previous hover time
7052 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7054 DWORD oldHoverTime = infoPtr->dwHoverTime;
7056 infoPtr->dwHoverTime = dwHoverTime;
7058 return oldHoverTime;
7061 /***
7062 * DESCRIPTION:
7063 * Sets spacing for icons of LVS_ICON style.
7065 * PARAMETER(S):
7066 * [I] infoPtr : valid pointer to the listview structure
7067 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7068 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7070 * RETURN:
7071 * MAKELONG(oldcx, oldcy)
7073 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7075 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7076 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7078 TRACE("requested=(%d,%d)\n", cx, cy);
7080 /* this is supported only for LVS_ICON style */
7081 if (uView != LVS_ICON) return oldspacing;
7083 /* set to defaults, if instructed to */
7084 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7085 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7087 /* if 0 then compute width
7088 * FIXME: Should scan each item and determine max width of
7089 * icon or label, then make that the width */
7090 if (cx == 0)
7091 cx = infoPtr->iconSpacing.cx;
7093 /* if 0 then compute height */
7094 if (cy == 0)
7095 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7096 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7099 infoPtr->iconSpacing.cx = cx;
7100 infoPtr->iconSpacing.cy = cy;
7102 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7103 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7104 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7105 infoPtr->ntmHeight);
7107 /* these depend on the iconSpacing */
7108 LISTVIEW_UpdateItemSize(infoPtr);
7110 return oldspacing;
7113 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7115 INT cx, cy;
7117 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7119 size->cx = cx;
7120 size->cy = cy;
7122 else
7124 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7125 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7129 /***
7130 * DESCRIPTION:
7131 * Sets image lists.
7133 * PARAMETER(S):
7134 * [I] infoPtr : valid pointer to the listview structure
7135 * [I] nType : image list type
7136 * [I] himl : image list handle
7138 * RETURN:
7139 * SUCCESS : old image list
7140 * FAILURE : NULL
7142 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7144 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7145 INT oldHeight = infoPtr->nItemHeight;
7146 HIMAGELIST himlOld = 0;
7148 TRACE("(nType=%d, himl=%p\n", nType, himl);
7150 switch (nType)
7152 case LVSIL_NORMAL:
7153 himlOld = infoPtr->himlNormal;
7154 infoPtr->himlNormal = himl;
7155 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7156 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7157 break;
7159 case LVSIL_SMALL:
7160 himlOld = infoPtr->himlSmall;
7161 infoPtr->himlSmall = himl;
7162 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7163 break;
7165 case LVSIL_STATE:
7166 himlOld = infoPtr->himlState;
7167 infoPtr->himlState = himl;
7168 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7169 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7170 break;
7172 default:
7173 ERR("Unknown icon type=%d\n", nType);
7174 return NULL;
7177 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7178 if (infoPtr->nItemHeight != oldHeight)
7179 LISTVIEW_UpdateScroll(infoPtr);
7181 return himlOld;
7184 /***
7185 * DESCRIPTION:
7186 * Preallocates memory (does *not* set the actual count of items !)
7188 * PARAMETER(S):
7189 * [I] infoPtr : valid pointer to the listview structure
7190 * [I] nItems : item count (projected number of items to allocate)
7191 * [I] dwFlags : update flags
7193 * RETURN:
7194 * SUCCESS : TRUE
7195 * FAILURE : FALSE
7197 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7199 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7201 if (infoPtr->dwStyle & LVS_OWNERDATA)
7203 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7204 INT nOldCount = infoPtr->nItemCount;
7206 if (nItems < nOldCount)
7208 RANGE range = { nItems, nOldCount };
7209 ranges_del(infoPtr->selectionRanges, range);
7210 if (infoPtr->nFocusedItem >= nItems)
7212 infoPtr->nFocusedItem = -1;
7213 SetRectEmpty(&infoPtr->rcFocus);
7217 infoPtr->nItemCount = nItems;
7218 LISTVIEW_UpdateScroll(infoPtr);
7220 /* the flags are valid only in ownerdata report and list modes */
7221 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7223 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7224 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7226 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7227 LISTVIEW_InvalidateList(infoPtr);
7228 else
7230 INT nFrom, nTo;
7231 POINT Origin;
7232 RECT rcErase;
7234 LISTVIEW_GetOrigin(infoPtr, &Origin);
7235 nFrom = min(nOldCount, nItems);
7236 nTo = max(nOldCount, nItems);
7238 if (uView == LVS_REPORT)
7240 rcErase.left = 0;
7241 rcErase.top = nFrom * infoPtr->nItemHeight;
7242 rcErase.right = infoPtr->nItemWidth;
7243 rcErase.bottom = nTo * infoPtr->nItemHeight;
7244 OffsetRect(&rcErase, Origin.x, Origin.y);
7245 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7246 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7248 else /* LVS_LIST */
7250 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7252 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7253 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7254 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7255 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7256 OffsetRect(&rcErase, Origin.x, Origin.y);
7257 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7258 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7260 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7261 rcErase.top = 0;
7262 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7263 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7264 OffsetRect(&rcErase, Origin.x, Origin.y);
7265 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7266 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7270 else
7272 /* According to MSDN for non-LVS_OWNERDATA this is just
7273 * a performance issue. The control allocates its internal
7274 * data structures for the number of items specified. It
7275 * cuts down on the number of memory allocations. Therefore
7276 * we will just issue a WARN here
7278 WARN("for non-ownerdata performance option not implemented.\n");
7281 return TRUE;
7284 /***
7285 * DESCRIPTION:
7286 * Sets the position of an item.
7288 * PARAMETER(S):
7289 * [I] infoPtr : valid pointer to the listview structure
7290 * [I] nItem : item index
7291 * [I] pt : coordinate
7293 * RETURN:
7294 * SUCCESS : TRUE
7295 * FAILURE : FALSE
7297 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7299 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7300 POINT Origin;
7302 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7304 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7305 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7307 LISTVIEW_GetOrigin(infoPtr, &Origin);
7309 /* This point value seems to be an undocumented feature.
7310 * The best guess is that it means either at the origin,
7311 * or at true beginning of the list. I will assume the origin. */
7312 if ((pt.x == -1) && (pt.y == -1))
7313 pt = Origin;
7315 if (uView == LVS_ICON)
7317 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7318 pt.y -= ICON_TOP_PADDING;
7320 pt.x -= Origin.x;
7321 pt.y -= Origin.y;
7323 infoPtr->bAutoarrange = FALSE;
7325 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7328 /***
7329 * DESCRIPTION:
7330 * Sets the state of one or many items.
7332 * PARAMETER(S):
7333 * [I] infoPtr : valid pointer to the listview structure
7334 * [I] nItem : item index
7335 * [I] lpLVItem : item or subitem info
7337 * RETURN:
7338 * SUCCESS : TRUE
7339 * FAILURE : FALSE
7341 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7343 BOOL bResult = TRUE;
7344 LVITEMW lvItem;
7346 lvItem.iItem = nItem;
7347 lvItem.iSubItem = 0;
7348 lvItem.mask = LVIF_STATE;
7349 lvItem.state = lpLVItem->state;
7350 lvItem.stateMask = lpLVItem->stateMask;
7351 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7353 if (nItem == -1)
7355 /* apply to all items */
7356 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7357 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7359 else
7360 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7363 * Update selection mark
7365 * Investigation on windows 2k showed that selection mark was updated
7366 * whenever a new selection was made, but if the selected item was
7367 * unselected it was not updated.
7369 * we are probably still not 100% accurate, but this at least sets the
7370 * proper selection mark when it is needed
7373 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7374 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7376 int i;
7377 infoPtr->nSelectionMark = -1;
7378 for (i = 0; i < infoPtr->nItemCount; i++)
7380 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7382 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7384 infoPtr->nSelectionMark = i;
7385 break;
7388 else if (ranges_contain(infoPtr->selectionRanges, i))
7390 infoPtr->nSelectionMark = i;
7391 break;
7396 return bResult;
7399 /***
7400 * DESCRIPTION:
7401 * Sets the text of an item or subitem.
7403 * PARAMETER(S):
7404 * [I] hwnd : window handle
7405 * [I] nItem : item index
7406 * [I] lpLVItem : item or subitem info
7407 * [I] isW : TRUE if input is Unicode
7409 * RETURN:
7410 * SUCCESS : TRUE
7411 * FAILURE : FALSE
7413 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7415 LVITEMW lvItem;
7417 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7419 lvItem.iItem = nItem;
7420 lvItem.iSubItem = lpLVItem->iSubItem;
7421 lvItem.mask = LVIF_TEXT;
7422 lvItem.pszText = lpLVItem->pszText;
7423 lvItem.cchTextMax = lpLVItem->cchTextMax;
7425 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7427 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7430 /***
7431 * DESCRIPTION:
7432 * Set item index that marks the start of a multiple selection.
7434 * PARAMETER(S):
7435 * [I] infoPtr : valid pointer to the listview structure
7436 * [I] nIndex : index
7438 * RETURN:
7439 * Index number or -1 if there is no selection mark.
7441 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7443 INT nOldIndex = infoPtr->nSelectionMark;
7445 TRACE("(nIndex=%d)\n", nIndex);
7447 infoPtr->nSelectionMark = nIndex;
7449 return nOldIndex;
7452 /***
7453 * DESCRIPTION:
7454 * Sets the text background color.
7456 * PARAMETER(S):
7457 * [I] infoPtr : valid pointer to the listview structure
7458 * [I] clrTextBk : text background color
7460 * RETURN:
7461 * SUCCESS : TRUE
7462 * FAILURE : FALSE
7464 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7466 TRACE("(clrTextBk=%x)\n", clrTextBk);
7468 if (infoPtr->clrTextBk != clrTextBk)
7470 infoPtr->clrTextBk = clrTextBk;
7471 LISTVIEW_InvalidateList(infoPtr);
7474 return TRUE;
7477 /***
7478 * DESCRIPTION:
7479 * Sets the text foreground color.
7481 * PARAMETER(S):
7482 * [I] infoPtr : valid pointer to the listview structure
7483 * [I] clrText : text color
7485 * RETURN:
7486 * SUCCESS : TRUE
7487 * FAILURE : FALSE
7489 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7491 TRACE("(clrText=%x)\n", clrText);
7493 if (infoPtr->clrText != clrText)
7495 infoPtr->clrText = clrText;
7496 LISTVIEW_InvalidateList(infoPtr);
7499 return TRUE;
7502 /***
7503 * DESCRIPTION:
7504 * Determines which listview item is located at the specified position.
7506 * PARAMETER(S):
7507 * [I] infoPtr : valid pointer to the listview structure
7508 * [I] hwndNewToolTip : handle to new ToolTip
7510 * RETURN:
7511 * old tool tip
7513 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7515 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7516 infoPtr->hwndToolTip = hwndNewToolTip;
7517 return hwndOldToolTip;
7521 * DESCRIPTION:
7522 * sets the Unicode character format flag for the control
7523 * PARAMETER(S):
7524 * [I] infoPtr :valid pointer to the listview structure
7525 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7527 * RETURN:
7528 * Old Unicode Format
7530 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7532 BOOL rc = infoPtr->notifyFormat;
7533 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7534 return rc;
7537 /* LISTVIEW_SetWorkAreas */
7539 /***
7540 * DESCRIPTION:
7541 * Callback internally used by LISTVIEW_SortItems()
7543 * PARAMETER(S):
7544 * [I] first : pointer to first ITEM_INFO to compare
7545 * [I] second : pointer to second ITEM_INFO to compare
7546 * [I] lParam : HWND of control
7548 * RETURN:
7549 * if first comes before second : negative
7550 * if first comes after second : positive
7551 * if first and second are equivalent : zero
7553 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7555 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7556 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7557 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7559 /* Forward the call to the client defined callback */
7560 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7563 /***
7564 * DESCRIPTION:
7565 * Sorts the listview items.
7567 * PARAMETER(S):
7568 * [I] infoPtr : valid pointer to the listview structure
7569 * [I] pfnCompare : application-defined value
7570 * [I] lParamSort : pointer to comparision callback
7572 * RETURN:
7573 * SUCCESS : TRUE
7574 * FAILURE : FALSE
7576 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7578 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7579 HDPA hdpaSubItems;
7580 ITEM_INFO *lpItem;
7581 LPVOID selectionMarkItem;
7582 LVITEMW item;
7583 int i;
7585 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7587 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7589 if (!pfnCompare) return FALSE;
7590 if (!infoPtr->hdpaItems) return FALSE;
7592 /* if there are 0 or 1 items, there is no need to sort */
7593 if (infoPtr->nItemCount < 2) return TRUE;
7595 if (infoPtr->nFocusedItem >= 0)
7597 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7598 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7599 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7601 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7602 /* clear the lpItem->state for non-selected ones */
7603 /* remove the selection ranges */
7605 infoPtr->pfnCompare = pfnCompare;
7606 infoPtr->lParamSort = lParamSort;
7607 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7609 /* Adjust selections and indices so that they are the way they should
7610 * be after the sort (otherwise, the list items move around, but
7611 * whatever is at the item's previous original position will be
7612 * selected instead)
7614 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7615 for (i=0; i < infoPtr->nItemCount; i++)
7617 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7618 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7620 if (lpItem->state & LVIS_SELECTED)
7622 item.state = LVIS_SELECTED;
7623 item.stateMask = LVIS_SELECTED;
7624 LISTVIEW_SetItemState(infoPtr, i, &item);
7626 if (lpItem->state & LVIS_FOCUSED)
7628 infoPtr->nFocusedItem = i;
7629 lpItem->state &= ~LVIS_FOCUSED;
7632 if (selectionMarkItem != NULL)
7633 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7634 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7636 /* refresh the display */
7637 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7638 LISTVIEW_InvalidateList(infoPtr);
7640 return TRUE;
7643 /***
7644 * DESCRIPTION:
7645 * Update theme handle after a theme change.
7647 * PARAMETER(S):
7648 * [I] infoPtr : valid pointer to the listview structure
7650 * RETURN:
7651 * SUCCESS : 0
7652 * FAILURE : something else
7654 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7656 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7657 CloseThemeData(theme);
7658 OpenThemeData(infoPtr->hwndSelf, themeClass);
7659 return 0;
7662 /***
7663 * DESCRIPTION:
7664 * Updates an items or rearranges the listview control.
7666 * PARAMETER(S):
7667 * [I] infoPtr : valid pointer to the listview structure
7668 * [I] nItem : item index
7670 * RETURN:
7671 * SUCCESS : TRUE
7672 * FAILURE : FALSE
7674 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7676 TRACE("(nItem=%d)\n", nItem);
7678 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7680 /* rearrange with default alignment style */
7681 if (is_autoarrange(infoPtr))
7682 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7683 else
7684 LISTVIEW_InvalidateItem(infoPtr, nItem);
7686 return TRUE;
7689 /***
7690 * DESCRIPTION:
7691 * Draw the track line at the place defined in the infoPtr structure.
7692 * The line is drawn with a XOR pen so drawing the line for the second time
7693 * in the same place erases the line.
7695 * PARAMETER(S):
7696 * [I] infoPtr : valid pointer to the listview structure
7698 * RETURN:
7699 * SUCCESS : TRUE
7700 * FAILURE : FALSE
7702 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7704 HPEN hOldPen;
7705 HDC hdc;
7706 INT oldROP;
7708 if (infoPtr->xTrackLine == -1)
7709 return FALSE;
7711 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7712 return FALSE;
7713 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7714 oldROP = SetROP2(hdc, R2_XORPEN);
7715 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7716 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7717 SetROP2(hdc, oldROP);
7718 SelectObject(hdc, hOldPen);
7719 ReleaseDC(infoPtr->hwndSelf, hdc);
7720 return TRUE;
7723 /***
7724 * DESCRIPTION:
7725 * Called when an edit control should be displayed. This function is called after
7726 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7728 * PARAMETER(S):
7729 * [I] hwnd : Handle to the listview
7730 * [I] uMsg : WM_TIMER (ignored)
7731 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7732 * [I] dwTimer : The elapsed time (ignored)
7734 * RETURN:
7735 * None.
7737 static CALLBACK VOID LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7739 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7740 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7742 KillTimer(hwnd, idEvent);
7743 editItem->fEnabled = FALSE;
7744 /* check if the item is still selected */
7745 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7746 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7749 /***
7750 * DESCRIPTION:
7751 * Creates the listview control - the WM_NCCREATE phase.
7753 * PARAMETER(S):
7754 * [I] hwnd : window handle
7755 * [I] lpcs : the create parameters
7757 * RETURN:
7758 * Success: TRUE
7759 * Failure: FALSE
7761 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7763 LISTVIEW_INFO *infoPtr;
7764 LOGFONTW logFont;
7766 TRACE("(lpcs=%p)\n", lpcs);
7768 /* initialize info pointer */
7769 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7770 if (!infoPtr) return FALSE;
7772 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7774 infoPtr->hwndSelf = hwnd;
7775 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7776 /* determine the type of structures to use */
7777 infoPtr->hwndNotify = lpcs->hwndParent;
7778 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7780 /* initialize color information */
7781 infoPtr->clrBk = CLR_NONE;
7782 infoPtr->clrText = CLR_DEFAULT;
7783 infoPtr->clrTextBk = CLR_DEFAULT;
7784 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7786 /* set default values */
7787 infoPtr->nFocusedItem = -1;
7788 infoPtr->nSelectionMark = -1;
7789 infoPtr->nHotItem = -1;
7790 infoPtr->bRedraw = TRUE;
7791 infoPtr->bNoItemMetrics = TRUE;
7792 infoPtr->bDoChangeNotify = TRUE;
7793 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7794 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7795 infoPtr->nEditLabelItem = -1;
7796 infoPtr->dwHoverTime = -1; /* default system hover time */
7797 infoPtr->nMeasureItemHeight = 0;
7798 infoPtr->xTrackLine = -1; /* no track line */
7799 infoPtr->itemEdit.fEnabled = FALSE;
7801 /* get default font (icon title) */
7802 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7803 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7804 infoPtr->hFont = infoPtr->hDefaultFont;
7805 LISTVIEW_SaveTextMetrics(infoPtr);
7807 /* allocate memory for the data structure */
7808 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7809 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7810 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7811 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7812 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7813 return TRUE;
7815 fail:
7816 DestroyWindow(infoPtr->hwndHeader);
7817 ranges_destroy(infoPtr->selectionRanges);
7818 DPA_Destroy(infoPtr->hdpaItems);
7819 DPA_Destroy(infoPtr->hdpaPosX);
7820 DPA_Destroy(infoPtr->hdpaPosY);
7821 DPA_Destroy(infoPtr->hdpaColumns);
7822 Free(infoPtr);
7823 return FALSE;
7826 /***
7827 * DESCRIPTION:
7828 * Creates the listview control - the WM_CREATE phase. Most of the data is
7829 * already set up in LISTVIEW_NCCreate
7831 * PARAMETER(S):
7832 * [I] hwnd : window handle
7833 * [I] lpcs : the create parameters
7835 * RETURN:
7836 * Success: 0
7837 * Failure: -1
7839 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7841 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7842 UINT uView = lpcs->style & LVS_TYPEMASK;
7844 TRACE("(lpcs=%p)\n", lpcs);
7846 infoPtr->dwStyle = lpcs->style;
7847 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7848 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7850 /* create header */
7851 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7852 WS_CHILD | HDS_HORZ | HDS_FULLDRAG | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7853 0, 0, 0, 0, hwnd, NULL,
7854 lpcs->hInstance, NULL);
7855 if (!infoPtr->hwndHeader) return -1;
7857 /* set header unicode format */
7858 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7860 /* set header font */
7861 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7863 /* init item size to avoid division by 0 */
7864 LISTVIEW_UpdateItemSize (infoPtr);
7866 if (uView == LVS_REPORT)
7868 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7870 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7872 else
7874 /* set HDS_HIDDEN flag to hide the header bar */
7875 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7876 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7880 OpenThemeData(hwnd, themeClass);
7882 /* initialize the icon sizes */
7883 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7884 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7885 return 0;
7888 /***
7889 * DESCRIPTION:
7890 * Destroys the listview control.
7892 * PARAMETER(S):
7893 * [I] infoPtr : valid pointer to the listview structure
7895 * RETURN:
7896 * Success: 0
7897 * Failure: -1
7899 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
7901 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7902 CloseThemeData(theme);
7903 return 0;
7906 /***
7907 * DESCRIPTION:
7908 * Enables the listview control.
7910 * PARAMETER(S):
7911 * [I] infoPtr : valid pointer to the listview structure
7912 * [I] bEnable : specifies whether to enable or disable the window
7914 * RETURN:
7915 * SUCCESS : TRUE
7916 * FAILURE : FALSE
7918 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
7920 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
7921 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
7922 return TRUE;
7925 /***
7926 * DESCRIPTION:
7927 * Erases the background of the listview control.
7929 * PARAMETER(S):
7930 * [I] infoPtr : valid pointer to the listview structure
7931 * [I] hdc : device context handle
7933 * RETURN:
7934 * SUCCESS : TRUE
7935 * FAILURE : FALSE
7937 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
7939 RECT rc;
7941 TRACE("(hdc=%p)\n", hdc);
7943 if (!GetClipBox(hdc, &rc)) return FALSE;
7945 /* for double buffered controls we need to do this during refresh */
7946 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
7948 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7952 /***
7953 * DESCRIPTION:
7954 * Helper function for LISTVIEW_[HV]Scroll *only*.
7955 * Performs vertical/horizontal scrolling by a give amount.
7957 * PARAMETER(S):
7958 * [I] infoPtr : valid pointer to the listview structure
7959 * [I] dx : amount of horizontal scroll
7960 * [I] dy : amount of vertical scroll
7962 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7964 /* now we can scroll the list */
7965 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7966 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7967 /* if we have focus, adjust rect */
7968 OffsetRect(&infoPtr->rcFocus, dx, dy);
7969 UpdateWindow(infoPtr->hwndSelf);
7972 /***
7973 * DESCRIPTION:
7974 * Performs vertical scrolling.
7976 * PARAMETER(S):
7977 * [I] infoPtr : valid pointer to the listview structure
7978 * [I] nScrollCode : scroll code
7979 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7980 * [I] hScrollWnd : scrollbar control window handle
7982 * RETURN:
7983 * Zero
7985 * NOTES:
7986 * SB_LINEUP/SB_LINEDOWN:
7987 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7988 * for LVS_REPORT is 1 line
7989 * for LVS_LIST cannot occur
7992 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7993 INT nScrollDiff, HWND hScrollWnd)
7995 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7996 INT nOldScrollPos, nNewScrollPos;
7997 SCROLLINFO scrollInfo;
7998 BOOL is_an_icon;
8000 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8001 debugscrollcode(nScrollCode), nScrollDiff);
8003 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8005 scrollInfo.cbSize = sizeof(SCROLLINFO);
8006 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8008 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8010 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8012 nOldScrollPos = scrollInfo.nPos;
8013 switch (nScrollCode)
8015 case SB_INTERNAL:
8016 break;
8018 case SB_LINEUP:
8019 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8020 break;
8022 case SB_LINEDOWN:
8023 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8024 break;
8026 case SB_PAGEUP:
8027 nScrollDiff = -scrollInfo.nPage;
8028 break;
8030 case SB_PAGEDOWN:
8031 nScrollDiff = scrollInfo.nPage;
8032 break;
8034 case SB_THUMBPOSITION:
8035 case SB_THUMBTRACK:
8036 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8037 break;
8039 default:
8040 nScrollDiff = 0;
8043 /* quit right away if pos isn't changing */
8044 if (nScrollDiff == 0) return 0;
8046 /* calculate new position, and handle overflows */
8047 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8048 if (nScrollDiff > 0) {
8049 if (nNewScrollPos < nOldScrollPos ||
8050 nNewScrollPos > scrollInfo.nMax)
8051 nNewScrollPos = scrollInfo.nMax;
8052 } else {
8053 if (nNewScrollPos > nOldScrollPos ||
8054 nNewScrollPos < scrollInfo.nMin)
8055 nNewScrollPos = scrollInfo.nMin;
8058 /* set the new position, and reread in case it changed */
8059 scrollInfo.fMask = SIF_POS;
8060 scrollInfo.nPos = nNewScrollPos;
8061 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8063 /* carry on only if it really changed */
8064 if (nNewScrollPos == nOldScrollPos) return 0;
8066 /* now adjust to client coordinates */
8067 nScrollDiff = nOldScrollPos - nNewScrollPos;
8068 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8070 /* and scroll the window */
8071 scroll_list(infoPtr, 0, nScrollDiff);
8073 return 0;
8076 /***
8077 * DESCRIPTION:
8078 * Performs horizontal scrolling.
8080 * PARAMETER(S):
8081 * [I] infoPtr : valid pointer to the listview structure
8082 * [I] nScrollCode : scroll code
8083 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8084 * [I] hScrollWnd : scrollbar control window handle
8086 * RETURN:
8087 * Zero
8089 * NOTES:
8090 * SB_LINELEFT/SB_LINERIGHT:
8091 * for LVS_ICON, LVS_SMALLICON 1 pixel
8092 * for LVS_REPORT is 1 pixel
8093 * for LVS_LIST is 1 column --> which is a 1 because the
8094 * scroll is based on columns not pixels
8097 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8098 INT nScrollDiff, HWND hScrollWnd)
8100 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8101 INT nOldScrollPos, nNewScrollPos;
8102 SCROLLINFO scrollInfo;
8104 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8105 debugscrollcode(nScrollCode), nScrollDiff);
8107 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8109 scrollInfo.cbSize = sizeof(SCROLLINFO);
8110 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8112 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8114 nOldScrollPos = scrollInfo.nPos;
8116 switch (nScrollCode)
8118 case SB_INTERNAL:
8119 break;
8121 case SB_LINELEFT:
8122 nScrollDiff = -1;
8123 break;
8125 case SB_LINERIGHT:
8126 nScrollDiff = 1;
8127 break;
8129 case SB_PAGELEFT:
8130 nScrollDiff = -scrollInfo.nPage;
8131 break;
8133 case SB_PAGERIGHT:
8134 nScrollDiff = scrollInfo.nPage;
8135 break;
8137 case SB_THUMBPOSITION:
8138 case SB_THUMBTRACK:
8139 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8140 break;
8142 default:
8143 nScrollDiff = 0;
8146 /* quit right away if pos isn't changing */
8147 if (nScrollDiff == 0) return 0;
8149 /* calculate new position, and handle overflows */
8150 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8151 if (nScrollDiff > 0) {
8152 if (nNewScrollPos < nOldScrollPos ||
8153 nNewScrollPos > scrollInfo.nMax)
8154 nNewScrollPos = scrollInfo.nMax;
8155 } else {
8156 if (nNewScrollPos > nOldScrollPos ||
8157 nNewScrollPos < scrollInfo.nMin)
8158 nNewScrollPos = scrollInfo.nMin;
8161 /* set the new position, and reread in case it changed */
8162 scrollInfo.fMask = SIF_POS;
8163 scrollInfo.nPos = nNewScrollPos;
8164 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8166 /* carry on only if it really changed */
8167 if (nNewScrollPos == nOldScrollPos) return 0;
8169 if(uView == LVS_REPORT)
8170 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8172 /* now adjust to client coordinates */
8173 nScrollDiff = nOldScrollPos - nNewScrollPos;
8174 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8176 /* and scroll the window */
8177 scroll_list(infoPtr, nScrollDiff, 0);
8179 return 0;
8182 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8184 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8185 INT gcWheelDelta = 0;
8186 INT pulScrollLines = 3;
8187 SCROLLINFO scrollInfo;
8189 TRACE("(wheelDelta=%d)\n", wheelDelta);
8191 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8192 gcWheelDelta -= wheelDelta;
8194 scrollInfo.cbSize = sizeof(SCROLLINFO);
8195 scrollInfo.fMask = SIF_POS;
8197 switch(uView)
8199 case LVS_ICON:
8200 case LVS_SMALLICON:
8202 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8203 * should be fixed in the future.
8205 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8206 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8207 break;
8209 case LVS_REPORT:
8210 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8212 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8213 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8214 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8216 break;
8218 case LVS_LIST:
8219 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8220 break;
8222 return 0;
8225 /***
8226 * DESCRIPTION:
8227 * ???
8229 * PARAMETER(S):
8230 * [I] infoPtr : valid pointer to the listview structure
8231 * [I] nVirtualKey : virtual key
8232 * [I] lKeyData : key data
8234 * RETURN:
8235 * Zero
8237 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8239 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8240 HWND hwndSelf = infoPtr->hwndSelf;
8241 INT nItem = -1;
8242 NMLVKEYDOWN nmKeyDown;
8244 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8246 /* send LVN_KEYDOWN notification */
8247 nmKeyDown.wVKey = nVirtualKey;
8248 nmKeyDown.flags = 0;
8249 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8250 if (!IsWindow(hwndSelf))
8251 return 0;
8253 switch (nVirtualKey)
8255 case VK_SPACE:
8256 nItem = infoPtr->nFocusedItem;
8257 break;
8259 case VK_RETURN:
8260 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8262 if (!notify(infoPtr, NM_RETURN)) return 0;
8263 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8265 break;
8267 case VK_HOME:
8268 if (infoPtr->nItemCount > 0)
8269 nItem = 0;
8270 break;
8272 case VK_END:
8273 if (infoPtr->nItemCount > 0)
8274 nItem = infoPtr->nItemCount - 1;
8275 break;
8277 case VK_LEFT:
8278 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8279 break;
8281 case VK_UP:
8282 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8283 break;
8285 case VK_RIGHT:
8286 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8287 break;
8289 case VK_DOWN:
8290 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8291 break;
8293 case VK_PRIOR:
8294 if (uView == LVS_REPORT)
8296 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8297 if (infoPtr->nFocusedItem == topidx)
8298 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8299 else
8300 nItem = topidx;
8302 else
8303 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8304 * LISTVIEW_GetCountPerRow(infoPtr);
8305 if(nItem < 0) nItem = 0;
8306 break;
8308 case VK_NEXT:
8309 if (uView == LVS_REPORT)
8311 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8312 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8313 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8314 nItem = infoPtr->nFocusedItem + cnt - 1;
8315 else
8316 nItem = topidx + cnt - 1;
8318 else
8319 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8320 * LISTVIEW_GetCountPerRow(infoPtr);
8321 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8322 break;
8325 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8326 LISTVIEW_KeySelection(infoPtr, nItem);
8328 return 0;
8331 /***
8332 * DESCRIPTION:
8333 * Kills the focus.
8335 * PARAMETER(S):
8336 * [I] infoPtr : valid pointer to the listview structure
8338 * RETURN:
8339 * Zero
8341 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8343 TRACE("()\n");
8345 /* if we did not have the focus, there's nothing to do */
8346 if (!infoPtr->bFocus) return 0;
8348 /* send NM_KILLFOCUS notification */
8349 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8351 /* if we have a focus rectagle, get rid of it */
8352 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8354 /* set window focus flag */
8355 infoPtr->bFocus = FALSE;
8357 /* invalidate the selected items before reseting focus flag */
8358 LISTVIEW_InvalidateSelectedItems(infoPtr);
8360 return 0;
8363 /***
8364 * DESCRIPTION:
8365 * Processes double click messages (left mouse button).
8367 * PARAMETER(S):
8368 * [I] infoPtr : valid pointer to the listview structure
8369 * [I] wKey : key flag
8370 * [I] x,y : mouse coordinate
8372 * RETURN:
8373 * Zero
8375 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8377 LVHITTESTINFO htInfo;
8379 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8381 /* Cancel the item edition if any */
8382 if (infoPtr->itemEdit.fEnabled)
8384 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8385 infoPtr->itemEdit.fEnabled = FALSE;
8388 /* send NM_RELEASEDCAPTURE notification */
8389 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8391 htInfo.pt.x = x;
8392 htInfo.pt.y = y;
8394 /* send NM_DBLCLK notification */
8395 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8396 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8398 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8399 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8401 return 0;
8404 /***
8405 * DESCRIPTION:
8406 * Processes mouse down messages (left mouse button).
8408 * PARAMETERS:
8409 * infoPtr [I ] valid pointer to the listview structure
8410 * wKey [I ] key flag
8411 * x,y [I ] mouse coordinate
8413 * RETURN:
8414 * Zero
8416 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8418 LVHITTESTINFO lvHitTestInfo;
8419 static BOOL bGroupSelect = TRUE;
8420 BOOL bReceivedFocus = FALSE;
8421 POINT pt = { x, y };
8422 INT nItem;
8424 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8426 /* send NM_RELEASEDCAPTURE notification */
8427 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8429 if (!infoPtr->bFocus)
8431 bReceivedFocus = TRUE;
8432 SetFocus(infoPtr->hwndSelf);
8435 /* set left button down flag and record the click position */
8436 infoPtr->bLButtonDown = TRUE;
8437 infoPtr->ptClickPos = pt;
8439 lvHitTestInfo.pt.x = x;
8440 lvHitTestInfo.pt.y = y;
8442 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8443 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8444 infoPtr->nEditLabelItem = -1;
8445 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8447 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8449 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
8450 if(state == 1 || state == 2)
8452 LVITEMW lvitem;
8453 state ^= 3;
8454 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
8455 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8456 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8458 return 0;
8461 if (infoPtr->dwStyle & LVS_SINGLESEL)
8463 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8464 infoPtr->nEditLabelItem = nItem;
8465 else
8466 LISTVIEW_SetSelection(infoPtr, nItem);
8468 else
8470 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8472 if (bGroupSelect)
8474 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8475 LISTVIEW_SetItemFocus(infoPtr, nItem);
8476 infoPtr->nSelectionMark = nItem;
8478 else
8480 LVITEMW item;
8482 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8483 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8485 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8486 infoPtr->nSelectionMark = nItem;
8489 else if (wKey & MK_CONTROL)
8491 LVITEMW item;
8493 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8495 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8496 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8497 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8498 infoPtr->nSelectionMark = nItem;
8500 else if (wKey & MK_SHIFT)
8502 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8504 else
8506 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8507 infoPtr->nEditLabelItem = nItem;
8509 /* set selection (clears other pre-existing selections) */
8510 LISTVIEW_SetSelection(infoPtr, nItem);
8514 else
8516 /* remove all selections */
8517 LISTVIEW_DeselectAll(infoPtr);
8518 ReleaseCapture();
8521 if (bReceivedFocus)
8522 infoPtr->nEditLabelItem = -1;
8524 return 0;
8527 /***
8528 * DESCRIPTION:
8529 * Processes mouse up messages (left mouse button).
8531 * PARAMETERS:
8532 * infoPtr [I ] valid pointer to the listview structure
8533 * wKey [I ] key flag
8534 * x,y [I ] mouse coordinate
8536 * RETURN:
8537 * Zero
8539 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8541 LVHITTESTINFO lvHitTestInfo;
8543 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8545 if (!infoPtr->bLButtonDown) return 0;
8547 lvHitTestInfo.pt.x = x;
8548 lvHitTestInfo.pt.y = y;
8550 /* send NM_CLICK notification */
8551 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8552 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8554 /* set left button flag */
8555 infoPtr->bLButtonDown = FALSE;
8557 /* if we clicked on a selected item, edit the label */
8558 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8560 /* we want to make sure the user doesn't want to do a double click. So we will
8561 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8563 infoPtr->itemEdit.fEnabled = TRUE;
8564 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8565 SetTimer(infoPtr->hwndSelf,
8566 (UINT_PTR)&infoPtr->itemEdit,
8567 GetDoubleClickTime(),
8568 LISTVIEW_DelayedEditItem);
8571 return 0;
8574 /***
8575 * DESCRIPTION:
8576 * Destroys the listview control (called after WM_DESTROY).
8578 * PARAMETER(S):
8579 * [I] infoPtr : valid pointer to the listview structure
8581 * RETURN:
8582 * Zero
8584 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8586 TRACE("()\n");
8588 /* delete all items */
8589 LISTVIEW_DeleteAllItems(infoPtr);
8591 /* destroy data structure */
8592 DPA_Destroy(infoPtr->hdpaItems);
8593 DPA_Destroy(infoPtr->hdpaPosX);
8594 DPA_Destroy(infoPtr->hdpaPosY);
8595 DPA_Destroy(infoPtr->hdpaColumns);
8596 ranges_destroy(infoPtr->selectionRanges);
8598 /* destroy image lists */
8599 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8601 if (infoPtr->himlNormal)
8602 ImageList_Destroy(infoPtr->himlNormal);
8603 if (infoPtr->himlSmall)
8604 ImageList_Destroy(infoPtr->himlSmall);
8605 if (infoPtr->himlState)
8606 ImageList_Destroy(infoPtr->himlState);
8609 /* destroy font, bkgnd brush */
8610 infoPtr->hFont = 0;
8611 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8612 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8614 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8616 /* free listview info pointer*/
8617 Free(infoPtr);
8619 return 0;
8622 /***
8623 * DESCRIPTION:
8624 * Handles notifications from header.
8626 * PARAMETER(S):
8627 * [I] infoPtr : valid pointer to the listview structure
8628 * [I] nCtrlId : control identifier
8629 * [I] lpnmh : notification information
8631 * RETURN:
8632 * Zero
8634 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8636 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8637 HWND hwndSelf = infoPtr->hwndSelf;
8639 TRACE("(lpnmh=%p)\n", lpnmh);
8641 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8643 switch (lpnmh->hdr.code)
8645 case HDN_TRACKW:
8646 case HDN_TRACKA:
8648 COLUMN_INFO *lpColumnInfo;
8649 POINT ptOrigin;
8650 INT x;
8652 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8653 break;
8655 /* remove the old line (if any) */
8656 LISTVIEW_DrawTrackLine(infoPtr);
8658 /* compute & draw the new line */
8659 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8660 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8661 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8662 infoPtr->xTrackLine = x + ptOrigin.x;
8663 LISTVIEW_DrawTrackLine(infoPtr);
8664 break;
8667 case HDN_ENDTRACKA:
8668 case HDN_ENDTRACKW:
8669 /* remove the track line (if any) */
8670 LISTVIEW_DrawTrackLine(infoPtr);
8671 infoPtr->xTrackLine = -1;
8672 break;
8674 case HDN_ENDDRAG:
8675 FIXME("Changing column order not implemented\n");
8676 return TRUE;
8678 case HDN_ITEMCHANGINGW:
8679 case HDN_ITEMCHANGINGA:
8680 return notify_forward_header(infoPtr, lpnmh);
8682 case HDN_ITEMCHANGEDW:
8683 case HDN_ITEMCHANGEDA:
8685 COLUMN_INFO *lpColumnInfo;
8686 INT dx, cxy;
8688 notify_forward_header(infoPtr, lpnmh);
8689 if (!IsWindow(hwndSelf))
8690 break;
8692 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8694 HDITEMW hdi;
8696 hdi.mask = HDI_WIDTH;
8697 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8698 cxy = hdi.cxy;
8700 else
8701 cxy = lpnmh->pitem->cxy;
8703 /* determine how much we change since the last know position */
8704 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8705 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8706 if (dx != 0)
8708 lpColumnInfo->rcHeader.right += dx;
8709 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8710 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8711 else
8713 /* only needs to update the scrolls */
8714 infoPtr->nItemWidth += dx;
8715 LISTVIEW_UpdateScroll(infoPtr);
8717 LISTVIEW_UpdateItemSize(infoPtr);
8718 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8720 POINT ptOrigin;
8721 RECT rcCol = lpColumnInfo->rcHeader;
8723 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8724 OffsetRect(&rcCol, ptOrigin.x, 0);
8726 rcCol.top = infoPtr->rcList.top;
8727 rcCol.bottom = infoPtr->rcList.bottom;
8729 /* resizing left-aligned columns leaves most of the left side untouched */
8730 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8732 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8733 if (dx > 0)
8734 nMaxDirty += dx;
8735 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8738 /* when shrinking the last column clear the now unused field */
8739 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8740 rcCol.right -= dx;
8742 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8746 break;
8748 case HDN_ITEMCLICKW:
8749 case HDN_ITEMCLICKA:
8751 /* Handle sorting by Header Column */
8752 NMLISTVIEW nmlv;
8754 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8755 nmlv.iItem = -1;
8756 nmlv.iSubItem = lpnmh->iItem;
8757 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8759 break;
8761 case HDN_DIVIDERDBLCLICKW:
8762 case HDN_DIVIDERDBLCLICKA:
8763 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8764 break;
8767 return 0;
8770 /***
8771 * DESCRIPTION:
8772 * Paint non-client area of control.
8774 * PARAMETER(S):
8775 * [I] infoPtr : valid pointer to the listview structureof the sender
8776 * [I] region : update region
8778 * RETURN:
8779 * TRUE - frame was painted
8780 * FALSE - call default window proc
8782 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8784 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8785 HDC dc;
8786 RECT r;
8787 HRGN cliprgn;
8788 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8789 cyEdge = GetSystemMetrics (SM_CYEDGE);
8791 if (!theme) return FALSE;
8793 GetWindowRect(infoPtr->hwndSelf, &r);
8795 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8796 r.right - cxEdge, r.bottom - cyEdge);
8797 if (region != (HRGN)1)
8798 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8799 OffsetRect(&r, -r.left, -r.top);
8801 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8802 OffsetRect(&r, -r.left, -r.top);
8804 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8805 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8806 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8807 ReleaseDC(infoPtr->hwndSelf, dc);
8809 /* Call default proc to get the scrollbars etc. painted */
8810 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8812 return TRUE;
8815 /***
8816 * DESCRIPTION:
8817 * Determines the type of structure to use.
8819 * PARAMETER(S):
8820 * [I] infoPtr : valid pointer to the listview structureof the sender
8821 * [I] hwndFrom : listview window handle
8822 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8824 * RETURN:
8825 * Zero
8827 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8829 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8831 if (nCommand != NF_REQUERY) return 0;
8833 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8835 return 0;
8838 /***
8839 * DESCRIPTION:
8840 * Paints/Repaints the listview control.
8842 * PARAMETER(S):
8843 * [I] infoPtr : valid pointer to the listview structure
8844 * [I] hdc : device context handle
8846 * RETURN:
8847 * Zero
8849 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8851 TRACE("(hdc=%p)\n", hdc);
8853 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8855 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8857 infoPtr->bNoItemMetrics = FALSE;
8858 LISTVIEW_UpdateItemSize(infoPtr);
8859 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8860 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8861 LISTVIEW_UpdateScroll(infoPtr);
8864 UpdateWindow(infoPtr->hwndHeader);
8866 if (hdc)
8867 LISTVIEW_Refresh(infoPtr, hdc, NULL);
8868 else
8870 PAINTSTRUCT ps;
8872 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8873 if (!hdc) return 1;
8874 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
8875 EndPaint(infoPtr->hwndSelf, &ps);
8878 return 0;
8882 /***
8883 * DESCRIPTION:
8884 * Paints/Repaints the listview control.
8886 * PARAMETER(S):
8887 * [I] infoPtr : valid pointer to the listview structure
8888 * [I] hdc : device context handle
8889 * [I] options : drawing options
8891 * RETURN:
8892 * Zero
8894 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8896 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
8898 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8899 return 0;
8901 if (options & PRF_ERASEBKGND)
8902 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8904 if (options & PRF_CLIENT)
8905 LISTVIEW_Paint(infoPtr, hdc);
8907 return 0;
8911 /***
8912 * DESCRIPTION:
8913 * Processes double click messages (right mouse button).
8915 * PARAMETER(S):
8916 * [I] infoPtr : valid pointer to the listview structure
8917 * [I] wKey : key flag
8918 * [I] x,y : mouse coordinate
8920 * RETURN:
8921 * Zero
8923 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8925 LVHITTESTINFO lvHitTestInfo;
8927 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8929 /* send NM_RELEASEDCAPTURE notification */
8930 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8932 /* send NM_RDBLCLK notification */
8933 lvHitTestInfo.pt.x = x;
8934 lvHitTestInfo.pt.y = y;
8935 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8936 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8938 return 0;
8941 /***
8942 * DESCRIPTION:
8943 * Processes mouse down messages (right mouse button).
8945 * PARAMETER(S):
8946 * [I] infoPtr : valid pointer to the listview structure
8947 * [I] wKey : key flag
8948 * [I] x,y : mouse coordinate
8950 * RETURN:
8951 * Zero
8953 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8955 LVHITTESTINFO lvHitTestInfo;
8956 INT nItem;
8958 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8960 /* send NM_RELEASEDCAPTURE notification */
8961 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8963 /* make sure the listview control window has the focus */
8964 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8966 /* set right button down flag */
8967 infoPtr->bRButtonDown = TRUE;
8969 /* determine the index of the selected item */
8970 lvHitTestInfo.pt.x = x;
8971 lvHitTestInfo.pt.y = y;
8972 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8974 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8976 LISTVIEW_SetItemFocus(infoPtr, nItem);
8977 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8978 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8979 LISTVIEW_SetSelection(infoPtr, nItem);
8981 else
8983 LISTVIEW_DeselectAll(infoPtr);
8986 return 0;
8989 /***
8990 * DESCRIPTION:
8991 * Processes mouse up messages (right mouse button).
8993 * PARAMETER(S):
8994 * [I] infoPtr : valid pointer to the listview structure
8995 * [I] wKey : key flag
8996 * [I] x,y : mouse coordinate
8998 * RETURN:
8999 * Zero
9001 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9003 LVHITTESTINFO lvHitTestInfo;
9004 POINT pt;
9006 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9008 if (!infoPtr->bRButtonDown) return 0;
9010 /* set button flag */
9011 infoPtr->bRButtonDown = FALSE;
9013 /* Send NM_RClICK notification */
9014 lvHitTestInfo.pt.x = x;
9015 lvHitTestInfo.pt.y = y;
9016 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9017 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9019 /* Change to screen coordinate for WM_CONTEXTMENU */
9020 pt = lvHitTestInfo.pt;
9021 ClientToScreen(infoPtr->hwndSelf, &pt);
9023 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9024 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9025 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9027 return 0;
9031 /***
9032 * DESCRIPTION:
9033 * Sets the cursor.
9035 * PARAMETER(S):
9036 * [I] infoPtr : valid pointer to the listview structure
9037 * [I] hwnd : window handle of window containing the cursor
9038 * [I] nHittest : hit-test code
9039 * [I] wMouseMsg : ideintifier of the mouse message
9041 * RETURN:
9042 * TRUE if cursor is set
9043 * FALSE otherwise
9045 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9047 LVHITTESTINFO lvHitTestInfo;
9049 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9051 if(!infoPtr->hHotCursor) return FALSE;
9053 GetCursorPos(&lvHitTestInfo.pt);
9054 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9056 SetCursor(infoPtr->hHotCursor);
9058 return TRUE;
9061 /***
9062 * DESCRIPTION:
9063 * Sets the focus.
9065 * PARAMETER(S):
9066 * [I] infoPtr : valid pointer to the listview structure
9067 * [I] hwndLoseFocus : handle of previously focused window
9069 * RETURN:
9070 * Zero
9072 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9074 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9076 /* if we have the focus already, there's nothing to do */
9077 if (infoPtr->bFocus) return 0;
9079 /* send NM_SETFOCUS notification */
9080 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9082 /* set window focus flag */
9083 infoPtr->bFocus = TRUE;
9085 /* put the focus rect back on */
9086 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9088 /* redraw all visible selected items */
9089 LISTVIEW_InvalidateSelectedItems(infoPtr);
9091 return 0;
9094 /***
9095 * DESCRIPTION:
9096 * Sets the font.
9098 * PARAMETER(S):
9099 * [I] infoPtr : valid pointer to the listview structure
9100 * [I] fRedraw : font handle
9101 * [I] fRedraw : redraw flag
9103 * RETURN:
9104 * Zero
9106 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9108 HFONT oldFont = infoPtr->hFont;
9110 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9112 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9113 if (infoPtr->hFont == oldFont) return 0;
9115 LISTVIEW_SaveTextMetrics(infoPtr);
9117 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9118 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9120 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9122 return 0;
9125 /***
9126 * DESCRIPTION:
9127 * Message handling for WM_SETREDRAW.
9128 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9130 * PARAMETER(S):
9131 * [I] infoPtr : valid pointer to the listview structure
9132 * [I] bRedraw: state of redraw flag
9134 * RETURN:
9135 * DefWinProc return value
9137 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9139 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9141 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9142 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9144 infoPtr->bRedraw = bRedraw;
9146 if(!bRedraw) return 0;
9148 if (is_autoarrange(infoPtr))
9149 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9150 LISTVIEW_UpdateScroll(infoPtr);
9152 /* despite what the WM_SETREDRAW docs says, apps expect us
9153 * to invalidate the listview here... stupid! */
9154 LISTVIEW_InvalidateList(infoPtr);
9156 return 0;
9159 /***
9160 * DESCRIPTION:
9161 * Resizes the listview control. This function processes WM_SIZE
9162 * messages. At this time, the width and height are not used.
9164 * PARAMETER(S):
9165 * [I] infoPtr : valid pointer to the listview structure
9166 * [I] Width : new width
9167 * [I] Height : new height
9169 * RETURN:
9170 * Zero
9172 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9174 RECT rcOld = infoPtr->rcList;
9176 TRACE("(width=%d, height=%d)\n", Width, Height);
9178 LISTVIEW_UpdateSize(infoPtr);
9179 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9181 /* do not bother with display related stuff if we're not redrawing */
9182 if (!is_redrawing(infoPtr)) return 0;
9184 if (is_autoarrange(infoPtr))
9185 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9187 LISTVIEW_UpdateScroll(infoPtr);
9189 /* refresh all only for lists whose height changed significantly */
9190 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9191 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9192 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9193 LISTVIEW_InvalidateList(infoPtr);
9195 return 0;
9198 /***
9199 * DESCRIPTION:
9200 * Sets the size information.
9202 * PARAMETER(S):
9203 * [I] infoPtr : valid pointer to the listview structure
9205 * RETURN:
9206 * None
9208 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9210 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9212 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9214 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9216 if (uView == LVS_LIST)
9218 /* Apparently the "LIST" style is supposed to have the same
9219 * number of items in a column even if there is no scroll bar.
9220 * Since if a scroll bar already exists then the bottom is already
9221 * reduced, only reduce if the scroll bar does not currently exist.
9222 * The "2" is there to mimic the native control. I think it may be
9223 * related to either padding or edges. (GLA 7/2002)
9225 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9226 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9227 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9229 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
9231 HDLAYOUT hl;
9232 WINDOWPOS wp;
9234 hl.prc = &infoPtr->rcList;
9235 hl.pwpos = &wp;
9236 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9238 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9240 infoPtr->rcList.top = max(wp.cy, 0);
9243 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9246 /***
9247 * DESCRIPTION:
9248 * Processes WM_STYLECHANGED messages.
9250 * PARAMETER(S):
9251 * [I] infoPtr : valid pointer to the listview structure
9252 * [I] wStyleType : window style type (normal or extended)
9253 * [I] lpss : window style information
9255 * RETURN:
9256 * Zero
9258 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9259 const STYLESTRUCT *lpss)
9261 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9262 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9264 TRACE("(styletype=%x, styleOld=0x%08x, styleNew=0x%08x)\n",
9265 wStyleType, lpss->styleOld, lpss->styleNew);
9267 if (wStyleType != GWL_STYLE) return 0;
9269 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9270 /* what if LVS_OWNERDATA changed? */
9271 /* or LVS_SINGLESEL */
9272 /* or LVS_SORT{AS,DES}CENDING */
9274 infoPtr->dwStyle = lpss->styleNew;
9276 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9277 ((lpss->styleNew & WS_HSCROLL) == 0))
9278 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9280 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9281 ((lpss->styleNew & WS_VSCROLL) == 0))
9282 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9284 if (uNewView != uOldView)
9286 SIZE oldIconSize = infoPtr->iconSize;
9287 HIMAGELIST himl;
9289 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9290 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9292 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9293 SetRectEmpty(&infoPtr->rcFocus);
9295 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9296 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9298 if (uNewView == LVS_ICON)
9300 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9302 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9303 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9304 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9307 else if (uNewView == LVS_REPORT)
9309 HDLAYOUT hl;
9310 WINDOWPOS wp;
9312 hl.prc = &infoPtr->rcList;
9313 hl.pwpos = &wp;
9314 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9315 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9318 LISTVIEW_UpdateItemSize(infoPtr);
9321 if (uNewView == LVS_REPORT)
9322 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
9324 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9325 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9326 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9328 /* update the size of the client area */
9329 LISTVIEW_UpdateSize(infoPtr);
9331 /* add scrollbars if needed */
9332 LISTVIEW_UpdateScroll(infoPtr);
9334 /* invalidate client area + erase background */
9335 LISTVIEW_InvalidateList(infoPtr);
9337 return 0;
9340 /***
9341 * DESCRIPTION:
9342 * Window procedure of the listview control.
9345 static LRESULT WINAPI
9346 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9348 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9350 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
9352 if (!infoPtr && (uMsg != WM_NCCREATE))
9353 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9355 switch (uMsg)
9357 case LVM_APPROXIMATEVIEWRECT:
9358 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9359 LOWORD(lParam), HIWORD(lParam));
9360 case LVM_ARRANGE:
9361 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9363 /* case LVM_CANCELEDITLABEL: */
9365 case LVM_CREATEDRAGIMAGE:
9366 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9368 case LVM_DELETEALLITEMS:
9369 return LISTVIEW_DeleteAllItems(infoPtr);
9371 case LVM_DELETECOLUMN:
9372 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9374 case LVM_DELETEITEM:
9375 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9377 case LVM_EDITLABELW:
9378 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9380 case LVM_EDITLABELA:
9381 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9383 /* case LVM_ENABLEGROUPVIEW: */
9385 case LVM_ENSUREVISIBLE:
9386 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9388 case LVM_FINDITEMW:
9389 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9391 case LVM_FINDITEMA:
9392 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9394 case LVM_GETBKCOLOR:
9395 return infoPtr->clrBk;
9397 /* case LVM_GETBKIMAGE: */
9399 case LVM_GETCALLBACKMASK:
9400 return infoPtr->uCallbackMask;
9402 case LVM_GETCOLUMNA:
9403 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9405 case LVM_GETCOLUMNW:
9406 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9408 case LVM_GETCOLUMNORDERARRAY:
9409 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9411 case LVM_GETCOLUMNWIDTH:
9412 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9414 case LVM_GETCOUNTPERPAGE:
9415 return LISTVIEW_GetCountPerPage(infoPtr);
9417 case LVM_GETEDITCONTROL:
9418 return (LRESULT)infoPtr->hwndEdit;
9420 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9421 return infoPtr->dwLvExStyle;
9423 /* case LVM_GETGROUPINFO: */
9425 /* case LVM_GETGROUPMETRICS: */
9427 case LVM_GETHEADER:
9428 return (LRESULT)infoPtr->hwndHeader;
9430 case LVM_GETHOTCURSOR:
9431 return (LRESULT)infoPtr->hHotCursor;
9433 case LVM_GETHOTITEM:
9434 return infoPtr->nHotItem;
9436 case LVM_GETHOVERTIME:
9437 return infoPtr->dwHoverTime;
9439 case LVM_GETIMAGELIST:
9440 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9442 /* case LVM_GETINSERTMARK: */
9444 /* case LVM_GETINSERTMARKCOLOR: */
9446 /* case LVM_GETINSERTMARKRECT: */
9448 case LVM_GETISEARCHSTRINGA:
9449 case LVM_GETISEARCHSTRINGW:
9450 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9451 return FALSE;
9453 case LVM_GETITEMA:
9454 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9456 case LVM_GETITEMW:
9457 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9459 case LVM_GETITEMCOUNT:
9460 return infoPtr->nItemCount;
9462 case LVM_GETITEMPOSITION:
9463 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9465 case LVM_GETITEMRECT:
9466 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9468 case LVM_GETITEMSPACING:
9469 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9471 case LVM_GETITEMSTATE:
9472 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9474 case LVM_GETITEMTEXTA:
9475 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9477 case LVM_GETITEMTEXTW:
9478 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9480 case LVM_GETNEXTITEM:
9481 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9483 case LVM_GETNUMBEROFWORKAREAS:
9484 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9485 return 1;
9487 case LVM_GETORIGIN:
9488 if (!lParam) return FALSE;
9489 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9490 return TRUE;
9492 /* case LVM_GETOUTLINECOLOR: */
9494 /* case LVM_GETSELECTEDCOLUMN: */
9496 case LVM_GETSELECTEDCOUNT:
9497 return LISTVIEW_GetSelectedCount(infoPtr);
9499 case LVM_GETSELECTIONMARK:
9500 return infoPtr->nSelectionMark;
9502 case LVM_GETSTRINGWIDTHA:
9503 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9505 case LVM_GETSTRINGWIDTHW:
9506 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9508 case LVM_GETSUBITEMRECT:
9509 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9511 case LVM_GETTEXTBKCOLOR:
9512 return infoPtr->clrTextBk;
9514 case LVM_GETTEXTCOLOR:
9515 return infoPtr->clrText;
9517 /* case LVM_GETTILEINFO: */
9519 /* case LVM_GETTILEVIEWINFO: */
9521 case LVM_GETTOOLTIPS:
9522 if( !infoPtr->hwndToolTip )
9523 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9524 return (LRESULT)infoPtr->hwndToolTip;
9526 case LVM_GETTOPINDEX:
9527 return LISTVIEW_GetTopIndex(infoPtr);
9529 /*case LVM_GETUNICODEFORMAT:
9530 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9531 return FALSE;*/
9533 /* case LVM_GETVIEW: */
9535 case LVM_GETVIEWRECT:
9536 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9538 case LVM_GETWORKAREAS:
9539 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9540 return FALSE;
9542 /* case LVM_HASGROUP: */
9544 case LVM_HITTEST:
9545 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9547 case LVM_INSERTCOLUMNA:
9548 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9550 case LVM_INSERTCOLUMNW:
9551 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9553 /* case LVM_INSERTGROUP: */
9555 /* case LVM_INSERTGROUPSORTED: */
9557 case LVM_INSERTITEMA:
9558 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9560 case LVM_INSERTITEMW:
9561 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9563 /* case LVM_INSERTMARKHITTEST: */
9565 /* case LVM_ISGROUPVIEWENABLED: */
9567 /* case LVM_MAPIDTOINDEX: */
9569 /* case LVM_MAPINDEXTOID: */
9571 /* case LVM_MOVEGROUP: */
9573 /* case LVM_MOVEITEMTOGROUP: */
9575 case LVM_REDRAWITEMS:
9576 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9578 /* case LVM_REMOVEALLGROUPS: */
9580 /* case LVM_REMOVEGROUP: */
9582 case LVM_SCROLL:
9583 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9585 case LVM_SETBKCOLOR:
9586 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9588 /* case LVM_SETBKIMAGE: */
9590 case LVM_SETCALLBACKMASK:
9591 infoPtr->uCallbackMask = (UINT)wParam;
9592 return TRUE;
9594 case LVM_SETCOLUMNA:
9595 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9597 case LVM_SETCOLUMNW:
9598 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9600 case LVM_SETCOLUMNORDERARRAY:
9601 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9603 case LVM_SETCOLUMNWIDTH:
9604 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9606 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9607 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9609 /* case LVM_SETGROUPINFO: */
9611 /* case LVM_SETGROUPMETRICS: */
9613 case LVM_SETHOTCURSOR:
9614 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9616 case LVM_SETHOTITEM:
9617 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9619 case LVM_SETHOVERTIME:
9620 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9622 case LVM_SETICONSPACING:
9623 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9625 case LVM_SETIMAGELIST:
9626 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9628 /* case LVM_SETINFOTIP: */
9630 /* case LVM_SETINSERTMARK: */
9632 /* case LVM_SETINSERTMARKCOLOR: */
9634 case LVM_SETITEMA:
9635 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9637 case LVM_SETITEMW:
9638 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9640 case LVM_SETITEMCOUNT:
9641 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9643 case LVM_SETITEMPOSITION:
9645 POINT pt;
9646 pt.x = (short)LOWORD(lParam);
9647 pt.y = (short)HIWORD(lParam);
9648 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9651 case LVM_SETITEMPOSITION32:
9652 if (lParam == 0) return FALSE;
9653 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9655 case LVM_SETITEMSTATE:
9656 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9658 case LVM_SETITEMTEXTA:
9659 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9661 case LVM_SETITEMTEXTW:
9662 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9664 /* case LVM_SETOUTLINECOLOR: */
9666 /* case LVM_SETSELECTEDCOLUMN: */
9668 case LVM_SETSELECTIONMARK:
9669 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9671 case LVM_SETTEXTBKCOLOR:
9672 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9674 case LVM_SETTEXTCOLOR:
9675 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9677 /* case LVM_SETTILEINFO: */
9679 /* case LVM_SETTILEVIEWINFO: */
9681 /* case LVM_SETTILEWIDTH: */
9683 case LVM_SETTOOLTIPS:
9684 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9686 case LVM_SETUNICODEFORMAT:
9687 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9689 /* case LVM_SETVIEW: */
9691 /* case LVM_SETWORKAREAS: */
9693 /* case LVM_SORTGROUPS: */
9695 case LVM_SORTITEMS:
9696 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9698 /* LVM_SORTITEMSEX: */
9700 case LVM_SUBITEMHITTEST:
9701 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9703 case LVM_UPDATE:
9704 return LISTVIEW_Update(infoPtr, (INT)wParam);
9706 case WM_CHAR:
9707 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9709 case WM_COMMAND:
9710 return LISTVIEW_Command(infoPtr, wParam, lParam);
9712 case WM_NCCREATE:
9713 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9715 case WM_CREATE:
9716 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9718 case WM_DESTROY:
9719 return LISTVIEW_Destroy(infoPtr);
9721 case WM_ENABLE:
9722 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9724 case WM_ERASEBKGND:
9725 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9727 case WM_GETDLGCODE:
9728 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9730 case WM_GETFONT:
9731 return (LRESULT)infoPtr->hFont;
9733 case WM_HSCROLL:
9734 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9736 case WM_KEYDOWN:
9737 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9739 case WM_KILLFOCUS:
9740 return LISTVIEW_KillFocus(infoPtr);
9742 case WM_LBUTTONDBLCLK:
9743 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9745 case WM_LBUTTONDOWN:
9746 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9748 case WM_LBUTTONUP:
9749 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9751 case WM_MOUSEMOVE:
9752 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9754 case WM_MOUSEHOVER:
9755 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9757 case WM_NCDESTROY:
9758 return LISTVIEW_NCDestroy(infoPtr);
9760 case WM_NCPAINT:
9761 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9762 return 0;
9763 goto fwd_msg;
9765 case WM_NOTIFY:
9766 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9767 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9768 else return 0;
9770 case WM_NOTIFYFORMAT:
9771 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9773 case WM_PRINTCLIENT:
9774 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9776 case WM_PAINT:
9777 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9779 case WM_RBUTTONDBLCLK:
9780 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9782 case WM_RBUTTONDOWN:
9783 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9785 case WM_RBUTTONUP:
9786 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9788 case WM_SETCURSOR:
9789 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9790 return TRUE;
9791 goto fwd_msg;
9793 case WM_SETFOCUS:
9794 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9796 case WM_SETFONT:
9797 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9799 case WM_SETREDRAW:
9800 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9802 case WM_SIZE:
9803 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9805 case WM_STYLECHANGED:
9806 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9808 case WM_SYSCOLORCHANGE:
9809 COMCTL32_RefreshSysColors();
9810 return 0;
9812 /* case WM_TIMER: */
9813 case WM_THEMECHANGED:
9814 return LISTVIEW_ThemeChanged(infoPtr);
9816 case WM_VSCROLL:
9817 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9819 case WM_MOUSEWHEEL:
9820 if (wParam & (MK_SHIFT | MK_CONTROL))
9821 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9822 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9824 case WM_WINDOWPOSCHANGED:
9825 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9827 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9828 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9829 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9831 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9833 MEASUREITEMSTRUCT mis;
9834 mis.CtlType = ODT_LISTVIEW;
9835 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9836 mis.itemID = -1;
9837 mis.itemWidth = 0;
9838 mis.itemData = 0;
9839 mis.itemHeight= infoPtr->nItemHeight;
9840 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9841 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9842 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9845 LISTVIEW_UpdateSize(infoPtr);
9846 LISTVIEW_UpdateScroll(infoPtr);
9848 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9850 /* case WM_WININICHANGE: */
9852 default:
9853 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9854 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9856 fwd_msg:
9857 /* call default window procedure */
9858 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9863 /***
9864 * DESCRIPTION:
9865 * Registers the window class.
9867 * PARAMETER(S):
9868 * None
9870 * RETURN:
9871 * None
9873 void LISTVIEW_Register(void)
9875 WNDCLASSW wndClass;
9877 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9878 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9879 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9880 wndClass.cbClsExtra = 0;
9881 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9882 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9883 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9884 wndClass.lpszClassName = WC_LISTVIEWW;
9885 RegisterClassW(&wndClass);
9888 /***
9889 * DESCRIPTION:
9890 * Unregisters the window class.
9892 * PARAMETER(S):
9893 * None
9895 * RETURN:
9896 * None
9898 void LISTVIEW_Unregister(void)
9900 UnregisterClassW(WC_LISTVIEWW, NULL);
9903 /***
9904 * DESCRIPTION:
9905 * Handle any WM_COMMAND messages
9907 * PARAMETER(S):
9908 * [I] infoPtr : valid pointer to the listview structure
9909 * [I] wParam : the first message parameter
9910 * [I] lParam : the second message parameter
9912 * RETURN:
9913 * Zero.
9915 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9917 switch (HIWORD(wParam))
9919 case EN_UPDATE:
9922 * Adjust the edit window size
9924 WCHAR buffer[1024];
9925 HDC hdc = GetDC(infoPtr->hwndEdit);
9926 HFONT hFont, hOldFont = 0;
9927 RECT rect;
9928 SIZE sz;
9929 int len;
9931 if (!infoPtr->hwndEdit || !hdc) return 0;
9932 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9933 GetWindowRect(infoPtr->hwndEdit, &rect);
9935 /* Select font to get the right dimension of the string */
9936 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9937 if(hFont != 0)
9939 hOldFont = SelectObject(hdc, hFont);
9942 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9944 TEXTMETRICW textMetric;
9946 /* Add Extra spacing for the next character */
9947 GetTextMetricsW(hdc, &textMetric);
9948 sz.cx += (textMetric.tmMaxCharWidth * 2);
9950 SetWindowPos (
9951 infoPtr->hwndEdit,
9952 HWND_TOP,
9955 sz.cx,
9956 rect.bottom - rect.top,
9957 SWP_DRAWFRAME|SWP_NOMOVE);
9959 if(hFont != 0)
9960 SelectObject(hdc, hOldFont);
9962 ReleaseDC(infoPtr->hwndEdit, hdc);
9964 break;
9967 default:
9968 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9971 return 0;
9975 /***
9976 * DESCRIPTION:
9977 * Subclassed edit control windproc function
9979 * PARAMETER(S):
9980 * [I] hwnd : the edit window handle
9981 * [I] uMsg : the message that is to be processed
9982 * [I] wParam : first message parameter
9983 * [I] lParam : second message parameter
9984 * [I] isW : TRUE if input is Unicode
9986 * RETURN:
9987 * Zero.
9989 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9991 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
9992 BOOL cancel = FALSE;
9994 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9995 hwnd, uMsg, wParam, lParam, isW);
9997 switch (uMsg)
9999 case WM_GETDLGCODE:
10000 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10002 case WM_KILLFOCUS:
10003 break;
10005 case WM_DESTROY:
10007 WNDPROC editProc = infoPtr->EditWndProc;
10008 infoPtr->EditWndProc = 0;
10009 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10010 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10013 case WM_KEYDOWN:
10014 if (VK_ESCAPE == (INT)wParam)
10016 cancel = TRUE;
10017 break;
10019 else if (VK_RETURN == (INT)wParam)
10020 break;
10022 default:
10023 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10026 /* kill the edit */
10027 if (infoPtr->hwndEdit)
10029 LPWSTR buffer = NULL;
10031 infoPtr->hwndEdit = 0;
10032 if (!cancel)
10034 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10036 if (len)
10038 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10040 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10041 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10045 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10047 Free(buffer);
10050 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10051 return 0;
10054 /***
10055 * DESCRIPTION:
10056 * Subclassed edit control Unicode windproc function
10058 * PARAMETER(S):
10059 * [I] hwnd : the edit window handle
10060 * [I] uMsg : the message that is to be processed
10061 * [I] wParam : first message parameter
10062 * [I] lParam : second message parameter
10064 * RETURN:
10066 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10068 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10071 /***
10072 * DESCRIPTION:
10073 * Subclassed edit control ANSI windproc function
10075 * PARAMETER(S):
10076 * [I] hwnd : the edit window handle
10077 * [I] uMsg : the message that is to be processed
10078 * [I] wParam : first message parameter
10079 * [I] lParam : second message parameter
10081 * RETURN:
10083 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10085 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10088 /***
10089 * DESCRIPTION:
10090 * Creates a subclassed edit cotrol
10092 * PARAMETER(S):
10093 * [I] infoPtr : valid pointer to the listview structure
10094 * [I] text : initial text for the edit
10095 * [I] style : the window style
10096 * [I] isW : TRUE if input is Unicode
10098 * RETURN:
10100 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10101 INT x, INT y, INT width, INT height, BOOL isW)
10103 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10104 HWND hedit;
10105 SIZE sz;
10106 HDC hdc;
10107 HDC hOldFont=0;
10108 TEXTMETRICW textMetric;
10109 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10111 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10113 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10114 hdc = GetDC(infoPtr->hwndSelf);
10116 /* Select the font to get appropriate metric dimensions */
10117 if(infoPtr->hFont != 0)
10118 hOldFont = SelectObject(hdc, infoPtr->hFont);
10120 /*Get String Length in pixels */
10121 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10123 /*Add Extra spacing for the next character */
10124 GetTextMetricsW(hdc, &textMetric);
10125 sz.cx += (textMetric.tmMaxCharWidth * 2);
10127 if(infoPtr->hFont != 0)
10128 SelectObject(hdc, hOldFont);
10130 ReleaseDC(infoPtr->hwndSelf, hdc);
10131 if (isW)
10132 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10133 else
10134 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10136 if (!hedit) return 0;
10138 infoPtr->EditWndProc = (WNDPROC)
10139 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10140 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10142 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10144 return hedit;