push caf8822204667872e3963bc36483c261826f5ad2
[wine/hacks.git] / dlls / comctl32 / listview.c
blobe551ebe679b303e6e172f5f04c5dede6cbade0e2
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 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1775 HFONT hOldFont = SelectObject(hdc, hFont);
1777 item.iItem = infoPtr->nFocusedItem;
1778 item.iSubItem = 0;
1779 item.mask = LVIF_PARAM;
1780 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1782 ZeroMemory(&dis, sizeof(dis));
1783 dis.CtlType = ODT_LISTVIEW;
1784 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1785 dis.itemID = item.iItem;
1786 dis.itemAction = ODA_FOCUS;
1787 if (fShow) dis.itemState |= ODS_FOCUS;
1788 dis.hwndItem = infoPtr->hwndSelf;
1789 dis.hDC = hdc;
1790 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1791 dis.itemData = item.lParam;
1793 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1795 SelectObject(hdc, hOldFont);
1797 else
1799 DrawFocusRect(hdc, &infoPtr->rcFocus);
1801 done:
1802 ReleaseDC(infoPtr->hwndSelf, hdc);
1805 /***
1806 * Invalidates all visible selected items.
1808 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1810 ITERATOR i;
1812 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1813 while(iterator_next(&i))
1815 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1816 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1818 iterator_destroy(&i);
1822 /***
1823 * DESCRIPTION: [INTERNAL]
1824 * Computes an item's (left,top) corner, relative to rcView.
1825 * That is, the position has NOT been made relative to the Origin.
1826 * This is deliberate, to avoid computing the Origin over, and
1827 * over again, when this function is call in a loop. Instead,
1828 * one ca factor the computation of the Origin before the loop,
1829 * and offset the value retured by this function, on every iteration.
1831 * PARAMETER(S):
1832 * [I] infoPtr : valid pointer to the listview structure
1833 * [I] nItem : item number
1834 * [O] lpptOrig : item top, left corner
1836 * RETURN:
1837 * None.
1839 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1841 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1843 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1845 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1847 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1848 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1850 else if (uView == LVS_LIST)
1852 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1853 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1854 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1856 else /* LVS_REPORT */
1858 lpptPosition->x = 0;
1859 lpptPosition->y = nItem * infoPtr->nItemHeight;
1863 /***
1864 * DESCRIPTION: [INTERNAL]
1865 * Compute the rectangles of an item. This is to localize all
1866 * the computations in one place. If you are not interested in some
1867 * of these values, simply pass in a NULL -- the fucntion is smart
1868 * enough to compute only what's necessary. The function computes
1869 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1870 * one, the BOX rectangle. This rectangle is very cheap to compute,
1871 * and is guaranteed to contain all the other rectangles. Computing
1872 * the ICON rect is also cheap, but all the others are potentaily
1873 * expensive. This gives an easy and effective optimization when
1874 * searching (like point inclusion, or rectangle intersection):
1875 * first test against the BOX, and if TRUE, test agains the desired
1876 * rectangle.
1877 * If the function does not have all the necessary information
1878 * to computed the requested rectangles, will crash with a
1879 * failed assertion. This is done so we catch all programming
1880 * errors, given that the function is called only from our code.
1882 * We have the following 'special' meanings for a few fields:
1883 * * If LVIS_FOCUSED is set, we assume the item has the focus
1884 * This is important in ICON mode, where it might get a larger
1885 * then usual rectange
1887 * Please note that subitem support works only in REPORT mode.
1889 * PARAMETER(S):
1890 * [I] infoPtr : valid pointer to the listview structure
1891 * [I] lpLVItem : item to compute the measures for
1892 * [O] lprcBox : ptr to Box rectangle
1893 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1894 * [0] lprcSelectBox : ptr to select box rectangle
1895 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1896 * [O] lprcIcon : ptr to Icon rectangle
1897 * Same as LVM_GETITEMRECT with LVIR_ICON
1898 * [O] lprcStateIcon: ptr to State Icon rectangle
1899 * [O] lprcLabel : ptr to Label rectangle
1900 * Same as LVM_GETITEMRECT with LVIR_LABEL
1902 * RETURN:
1903 * None.
1905 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1906 LPRECT lprcBox, LPRECT lprcSelectBox,
1907 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1909 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1910 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1911 RECT Box, SelectBox, Icon, Label;
1912 COLUMN_INFO *lpColumnInfo = NULL;
1913 SIZE labelSize = { 0, 0 };
1915 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1917 /* Be smart and try to figure out the minimum we have to do */
1918 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1919 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1921 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1922 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1924 if (lprcSelectBox) doSelectBox = TRUE;
1925 if (lprcLabel) doLabel = TRUE;
1926 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1927 if (doSelectBox)
1929 doIcon = TRUE;
1930 doLabel = TRUE;
1933 /************************************************************/
1934 /* compute the box rectangle (it should be cheap to do) */
1935 /************************************************************/
1936 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1937 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1939 if (lpLVItem->iSubItem)
1941 Box = lpColumnInfo->rcHeader;
1943 else
1945 Box.left = 0;
1946 Box.right = infoPtr->nItemWidth;
1948 Box.top = 0;
1949 Box.bottom = infoPtr->nItemHeight;
1951 /******************************************************************/
1952 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1953 /******************************************************************/
1954 if (doIcon)
1956 LONG state_width = 0;
1958 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1959 state_width = infoPtr->iconStateSize.cx;
1961 if (uView == LVS_ICON)
1963 Icon.left = Box.left + state_width;
1964 if (infoPtr->himlNormal)
1965 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1966 Icon.top = Box.top + ICON_TOP_PADDING;
1967 Icon.right = Icon.left;
1968 Icon.bottom = Icon.top;
1969 if (infoPtr->himlNormal)
1971 Icon.right += infoPtr->iconSize.cx;
1972 Icon.bottom += infoPtr->iconSize.cy;
1975 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1977 Icon.left = Box.left + state_width;
1979 if (uView == LVS_REPORT)
1980 Icon.left += REPORT_MARGINX;
1982 Icon.top = Box.top;
1983 Icon.right = Icon.left;
1984 if (infoPtr->himlSmall &&
1985 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1986 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1987 Icon.right += infoPtr->iconSize.cx;
1988 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
1990 if(lprcIcon) *lprcIcon = Icon;
1991 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
1993 /* TODO: is this correct? */
1994 if (lprcStateIcon)
1996 lprcStateIcon->left = Icon.left - state_width;
1997 lprcStateIcon->right = Icon.left;
1998 lprcStateIcon->top = Icon.top;
1999 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2000 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2003 else Icon.right = 0;
2005 /************************************************************/
2006 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2007 /************************************************************/
2008 if (doLabel)
2010 /* calculate how far to the right can the label strech */
2011 Label.right = Box.right;
2012 if (uView == LVS_REPORT)
2014 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2017 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2019 labelSize.cx = infoPtr->nItemWidth;
2020 labelSize.cy = infoPtr->nItemHeight;
2021 goto calc_label;
2024 /* we need the text in non owner draw mode */
2025 assert(lpLVItem->mask & LVIF_TEXT);
2026 if (is_textT(lpLVItem->pszText, TRUE))
2028 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2029 HDC hdc = GetDC(infoPtr->hwndSelf);
2030 HFONT hOldFont = SelectObject(hdc, hFont);
2031 UINT uFormat;
2032 RECT rcText;
2034 /* compute rough rectangle where the label will go */
2035 SetRectEmpty(&rcText);
2036 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2037 rcText.bottom = infoPtr->nItemHeight;
2038 if (uView == LVS_ICON)
2039 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2041 /* now figure out the flags */
2042 if (uView == LVS_ICON)
2043 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2044 else
2045 uFormat = LV_SL_DT_FLAGS;
2047 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2049 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2050 labelSize.cy = rcText.bottom - rcText.top;
2052 SelectObject(hdc, hOldFont);
2053 ReleaseDC(infoPtr->hwndSelf, hdc);
2056 calc_label:
2057 if (uView == LVS_ICON)
2059 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2060 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2061 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2062 Label.right = Label.left + labelSize.cx;
2063 Label.bottom = Label.top + infoPtr->nItemHeight;
2064 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2066 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2067 labelSize.cy /= infoPtr->ntmHeight;
2068 labelSize.cy = max(labelSize.cy, 1);
2069 labelSize.cy *= infoPtr->ntmHeight;
2071 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2073 else if (uView == LVS_REPORT)
2075 Label.left = Icon.right;
2076 Label.top = Box.top;
2077 Label.right = lpColumnInfo->rcHeader.right;
2078 Label.bottom = Label.top + infoPtr->nItemHeight;
2080 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2082 Label.left = Icon.right;
2083 Label.top = Box.top;
2084 Label.right = min(Label.left + labelSize.cx, Label.right);
2085 Label.bottom = Label.top + infoPtr->nItemHeight;
2088 if (lprcLabel) *lprcLabel = Label;
2089 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2092 /************************************************************/
2093 /* compute STATEICON bounding box */
2094 /************************************************************/
2095 if (doSelectBox)
2097 if (uView == LVS_REPORT)
2099 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2100 SelectBox.top = Box.top;
2101 SelectBox.bottom = Box.bottom;
2102 if (lpLVItem->iSubItem == 0)
2104 /* we need the indent in report mode */
2105 assert(lpLVItem->mask & LVIF_INDENT);
2106 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2108 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2110 else
2112 UnionRect(&SelectBox, &Icon, &Label);
2114 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2115 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2118 /* Fix the Box if necessary */
2119 if (lprcBox)
2121 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2122 else *lprcBox = Box;
2124 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2127 /***
2128 * DESCRIPTION: [INTERNAL]
2130 * PARAMETER(S):
2131 * [I] infoPtr : valid pointer to the listview structure
2132 * [I] nItem : item number
2133 * [O] lprcBox : ptr to Box rectangle
2135 * RETURN:
2136 * None.
2138 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2140 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2141 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2142 POINT Position, Origin;
2143 LVITEMW lvItem;
2145 LISTVIEW_GetOrigin(infoPtr, &Origin);
2146 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2148 /* Be smart and try to figure out the minimum we have to do */
2149 lvItem.mask = 0;
2150 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2151 lvItem.mask |= LVIF_TEXT;
2152 lvItem.iItem = nItem;
2153 lvItem.iSubItem = 0;
2154 lvItem.pszText = szDispText;
2155 lvItem.cchTextMax = DISP_TEXT_SIZE;
2156 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2157 if (uView == LVS_ICON)
2159 lvItem.mask |= LVIF_STATE;
2160 lvItem.stateMask = LVIS_FOCUSED;
2161 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2163 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2165 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2169 /***
2170 * DESCRIPTION:
2171 * Returns the current icon position, and advances it along the top.
2172 * The returned position is not offset by Origin.
2174 * PARAMETER(S):
2175 * [I] infoPtr : valid pointer to the listview structure
2176 * [O] lpPos : will get the current icon position
2178 * RETURN:
2179 * None
2181 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2183 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2185 *lpPos = infoPtr->currIconPos;
2187 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2188 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2190 infoPtr->currIconPos.x = 0;
2191 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2195 /***
2196 * DESCRIPTION:
2197 * Returns the current icon position, and advances it down the left edge.
2198 * The returned position is not offset by Origin.
2200 * PARAMETER(S):
2201 * [I] infoPtr : valid pointer to the listview structure
2202 * [O] lpPos : will get the current icon position
2204 * RETURN:
2205 * None
2207 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2209 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2211 *lpPos = infoPtr->currIconPos;
2213 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2214 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2216 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2217 infoPtr->currIconPos.y = 0;
2221 /***
2222 * DESCRIPTION:
2223 * Moves an icon to the specified position.
2224 * It takes care of invalidating the item, etc.
2226 * PARAMETER(S):
2227 * [I] infoPtr : valid pointer to the listview structure
2228 * [I] nItem : the item to move
2229 * [I] lpPos : the new icon position
2230 * [I] isNew : flags the item as being new
2232 * RETURN:
2233 * Success: TRUE
2234 * Failure: FALSE
2236 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2238 POINT old;
2240 if (!isNew)
2242 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2243 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2245 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2246 LISTVIEW_InvalidateItem(infoPtr, nItem);
2249 /* Allocating a POINTER for every item is too resource intensive,
2250 * so we'll keep the (x,y) in different arrays */
2251 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2252 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2254 LISTVIEW_InvalidateItem(infoPtr, nItem);
2256 return TRUE;
2259 /***
2260 * DESCRIPTION:
2261 * Arranges listview items in icon display mode.
2263 * PARAMETER(S):
2264 * [I] infoPtr : valid pointer to the listview structure
2265 * [I] nAlignCode : alignment code
2267 * RETURN:
2268 * SUCCESS : TRUE
2269 * FAILURE : FALSE
2271 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2273 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2274 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2275 POINT pos;
2276 INT i;
2278 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2280 TRACE("nAlignCode=%d\n", nAlignCode);
2282 if (nAlignCode == LVA_DEFAULT)
2284 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2285 else nAlignCode = LVA_ALIGNTOP;
2288 switch (nAlignCode)
2290 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2291 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2292 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2293 default: return FALSE;
2296 infoPtr->bAutoarrange = TRUE;
2297 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2298 for (i = 0; i < infoPtr->nItemCount; i++)
2300 next_pos(infoPtr, &pos);
2301 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2304 return TRUE;
2307 /***
2308 * DESCRIPTION:
2309 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2311 * PARAMETER(S):
2312 * [I] infoPtr : valid pointer to the listview structure
2313 * [O] lprcView : bounding rectangle
2315 * RETURN:
2316 * SUCCESS : TRUE
2317 * FAILURE : FALSE
2319 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2321 INT i, x, y;
2323 SetRectEmpty(lprcView);
2325 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2327 case LVS_ICON:
2328 case LVS_SMALLICON:
2329 for (i = 0; i < infoPtr->nItemCount; i++)
2331 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2332 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2333 lprcView->right = max(lprcView->right, x);
2334 lprcView->bottom = max(lprcView->bottom, y);
2336 if (infoPtr->nItemCount > 0)
2338 lprcView->right += infoPtr->nItemWidth;
2339 lprcView->bottom += infoPtr->nItemHeight;
2341 break;
2343 case LVS_LIST:
2344 y = LISTVIEW_GetCountPerColumn(infoPtr);
2345 x = infoPtr->nItemCount / y;
2346 if (infoPtr->nItemCount % y) x++;
2347 lprcView->right = x * infoPtr->nItemWidth;
2348 lprcView->bottom = y * infoPtr->nItemHeight;
2349 break;
2351 case LVS_REPORT:
2352 lprcView->right = infoPtr->nItemWidth;
2353 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2354 break;
2358 /***
2359 * DESCRIPTION:
2360 * Retrieves the bounding rectangle of all the items.
2362 * PARAMETER(S):
2363 * [I] infoPtr : valid pointer to the listview structure
2364 * [O] lprcView : bounding rectangle
2366 * RETURN:
2367 * SUCCESS : TRUE
2368 * FAILURE : FALSE
2370 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2372 POINT ptOrigin;
2374 TRACE("(lprcView=%p)\n", lprcView);
2376 if (!lprcView) return FALSE;
2378 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2379 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2380 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2382 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2384 return TRUE;
2387 /***
2388 * DESCRIPTION:
2389 * Retrieves the subitem pointer associated with the subitem index.
2391 * PARAMETER(S):
2392 * [I] hdpaSubItems : DPA handle for a specific item
2393 * [I] nSubItem : index of subitem
2395 * RETURN:
2396 * SUCCESS : subitem pointer
2397 * FAILURE : NULL
2399 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2401 SUBITEM_INFO *lpSubItem;
2402 INT i;
2404 /* we should binary search here if need be */
2405 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2407 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2408 if (lpSubItem->iSubItem == nSubItem)
2409 return lpSubItem;
2412 return NULL;
2416 /***
2417 * DESCRIPTION:
2418 * Calculates the desired item width.
2420 * PARAMETER(S):
2421 * [I] infoPtr : valid pointer to the listview structure
2423 * RETURN:
2424 * The desired item width.
2426 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2428 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2429 INT nItemWidth = 0;
2431 TRACE("uView=%d\n", uView);
2433 if (uView == LVS_ICON)
2434 nItemWidth = infoPtr->iconSpacing.cx;
2435 else if (uView == LVS_REPORT)
2437 RECT rcHeader;
2439 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2441 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2442 nItemWidth = rcHeader.right;
2445 else /* LVS_SMALLICON, or LVS_LIST */
2447 INT i;
2449 for (i = 0; i < infoPtr->nItemCount; i++)
2450 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2452 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2453 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2455 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2458 return max(nItemWidth, 1);
2461 /***
2462 * DESCRIPTION:
2463 * Calculates the desired item height.
2465 * PARAMETER(S):
2466 * [I] infoPtr : valid pointer to the listview structure
2468 * RETURN:
2469 * The desired item height.
2471 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2473 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2474 INT nItemHeight;
2476 TRACE("uView=%d\n", uView);
2478 if (uView == LVS_ICON)
2479 nItemHeight = infoPtr->iconSpacing.cy;
2480 else
2482 nItemHeight = infoPtr->ntmHeight;
2483 if (infoPtr->himlState)
2484 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2485 if (infoPtr->himlSmall)
2486 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2487 if (infoPtr->himlState || infoPtr->himlSmall)
2488 nItemHeight += HEIGHT_PADDING;
2489 if (infoPtr->nMeasureItemHeight > 0)
2490 nItemHeight = infoPtr->nMeasureItemHeight;
2493 return max(nItemHeight, 1);
2496 /***
2497 * DESCRIPTION:
2498 * Updates the width, and height of an item.
2500 * PARAMETER(S):
2501 * [I] infoPtr : valid pointer to the listview structure
2503 * RETURN:
2504 * None.
2506 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2508 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2509 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2513 /***
2514 * DESCRIPTION:
2515 * Retrieves and saves important text metrics info for the current
2516 * Listview font.
2518 * PARAMETER(S):
2519 * [I] infoPtr : valid pointer to the listview structure
2522 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2524 HDC hdc = GetDC(infoPtr->hwndSelf);
2525 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2526 HFONT hOldFont = SelectObject(hdc, hFont);
2527 TEXTMETRICW tm;
2528 SIZE sz;
2530 if (GetTextMetricsW(hdc, &tm))
2532 infoPtr->ntmHeight = tm.tmHeight;
2533 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2536 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2537 infoPtr->nEllipsisWidth = sz.cx;
2539 SelectObject(hdc, hOldFont);
2540 ReleaseDC(infoPtr->hwndSelf, hdc);
2542 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2545 /***
2546 * DESCRIPTION:
2547 * A compare function for ranges
2549 * PARAMETER(S)
2550 * [I] range1 : pointer to range 1;
2551 * [I] range2 : pointer to range 2;
2552 * [I] flags : flags
2554 * RETURNS:
2555 * > 0 : if range 1 > range 2
2556 * < 0 : if range 2 > range 1
2557 * = 0 : if range intersects range 2
2559 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2561 INT cmp;
2563 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2564 cmp = -1;
2565 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2566 cmp = 1;
2567 else
2568 cmp = 0;
2570 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2572 return cmp;
2575 #if DEBUG_RANGES
2576 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2577 #else
2578 #define ranges_check(ranges, desc) do { } while(0)
2579 #endif
2581 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2583 INT i;
2584 RANGE *prev, *curr;
2586 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2587 assert (ranges);
2588 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2589 ranges_dump(ranges);
2590 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2591 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2592 assert (prev->lower >= 0 && prev->lower < prev->upper);
2593 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2595 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2596 assert (prev->upper <= curr->lower);
2597 assert (curr->lower < curr->upper);
2598 prev = curr;
2600 TRACE("--- Done checking---\n");
2603 static RANGES ranges_create(int count)
2605 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2606 if (!ranges) return NULL;
2607 ranges->hdpa = DPA_Create(count);
2608 if (ranges->hdpa) return ranges;
2609 Free(ranges);
2610 return NULL;
2613 static void ranges_clear(RANGES ranges)
2615 INT i;
2617 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2618 Free(DPA_GetPtr(ranges->hdpa, i));
2619 DPA_DeleteAllPtrs(ranges->hdpa);
2623 static void ranges_destroy(RANGES ranges)
2625 if (!ranges) return;
2626 ranges_clear(ranges);
2627 DPA_Destroy(ranges->hdpa);
2628 Free(ranges);
2631 static RANGES ranges_clone(RANGES ranges)
2633 RANGES clone;
2634 INT i;
2636 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2638 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2640 RANGE *newrng = Alloc(sizeof(RANGE));
2641 if (!newrng) goto fail;
2642 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2643 DPA_SetPtr(clone->hdpa, i, newrng);
2645 return clone;
2647 fail:
2648 TRACE ("clone failed\n");
2649 ranges_destroy(clone);
2650 return NULL;
2653 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2655 INT i;
2657 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2658 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2660 return ranges;
2663 static void ranges_dump(RANGES ranges)
2665 INT i;
2667 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2668 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2671 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2673 RANGE srchrng = { nItem, nItem + 1 };
2675 TRACE("(nItem=%d)\n", nItem);
2676 ranges_check(ranges, "before contain");
2677 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2680 static INT ranges_itemcount(RANGES ranges)
2682 INT i, count = 0;
2684 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2686 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2687 count += sel->upper - sel->lower;
2690 return count;
2693 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2695 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2696 INT index;
2698 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2699 if (index == -1) return TRUE;
2701 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2703 chkrng = DPA_GetPtr(ranges->hdpa, index);
2704 if (chkrng->lower >= nItem)
2705 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2706 if (chkrng->upper > nItem)
2707 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2709 return TRUE;
2712 static BOOL ranges_add(RANGES ranges, RANGE range)
2714 RANGE srchrgn;
2715 INT index;
2717 TRACE("(%s)\n", debugrange(&range));
2718 ranges_check(ranges, "before add");
2720 /* try find overlapping regions first */
2721 srchrgn.lower = range.lower - 1;
2722 srchrgn.upper = range.upper + 1;
2723 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2725 if (index == -1)
2727 RANGE *newrgn;
2729 TRACE("Adding new range\n");
2731 /* create the brand new range to insert */
2732 newrgn = Alloc(sizeof(RANGE));
2733 if(!newrgn) goto fail;
2734 *newrgn = range;
2736 /* figure out where to insert it */
2737 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2738 TRACE("index=%d\n", index);
2739 if (index == -1) index = 0;
2741 /* and get it over with */
2742 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2744 Free(newrgn);
2745 goto fail;
2748 else
2750 RANGE *chkrgn, *mrgrgn;
2751 INT fromindex, mergeindex;
2753 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2754 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2756 chkrgn->lower = min(range.lower, chkrgn->lower);
2757 chkrgn->upper = max(range.upper, chkrgn->upper);
2759 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2761 /* merge now common anges */
2762 fromindex = 0;
2763 srchrgn.lower = chkrgn->lower - 1;
2764 srchrgn.upper = chkrgn->upper + 1;
2768 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2769 if (mergeindex == -1) break;
2770 if (mergeindex == index)
2772 fromindex = index + 1;
2773 continue;
2776 TRACE("Merge with index %i\n", mergeindex);
2778 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2779 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2780 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2781 Free(mrgrgn);
2782 DPA_DeletePtr(ranges->hdpa, mergeindex);
2783 if (mergeindex < index) index --;
2784 } while(1);
2787 ranges_check(ranges, "after add");
2788 return TRUE;
2790 fail:
2791 ranges_check(ranges, "failed add");
2792 return FALSE;
2795 static BOOL ranges_del(RANGES ranges, RANGE range)
2797 RANGE *chkrgn;
2798 INT index;
2800 TRACE("(%s)\n", debugrange(&range));
2801 ranges_check(ranges, "before del");
2803 /* we don't use DPAS_SORTED here, since we need *
2804 * to find the first overlapping range */
2805 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2806 while(index != -1)
2808 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2810 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2812 /* case 1: Same range */
2813 if ( (chkrgn->upper == range.upper) &&
2814 (chkrgn->lower == range.lower) )
2816 DPA_DeletePtr(ranges->hdpa, index);
2817 break;
2819 /* case 2: engulf */
2820 else if ( (chkrgn->upper <= range.upper) &&
2821 (chkrgn->lower >= range.lower) )
2823 DPA_DeletePtr(ranges->hdpa, index);
2825 /* case 3: overlap upper */
2826 else if ( (chkrgn->upper <= range.upper) &&
2827 (chkrgn->lower < range.lower) )
2829 chkrgn->upper = range.lower;
2831 /* case 4: overlap lower */
2832 else if ( (chkrgn->upper > range.upper) &&
2833 (chkrgn->lower >= range.lower) )
2835 chkrgn->lower = range.upper;
2836 break;
2838 /* case 5: fully internal */
2839 else
2841 RANGE tmprgn = *chkrgn, *newrgn;
2843 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2844 newrgn->lower = chkrgn->lower;
2845 newrgn->upper = range.lower;
2846 chkrgn->lower = range.upper;
2847 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2849 Free(newrgn);
2850 goto fail;
2852 chkrgn = &tmprgn;
2853 break;
2856 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2859 ranges_check(ranges, "after del");
2860 return TRUE;
2862 fail:
2863 ranges_check(ranges, "failed del");
2864 return FALSE;
2867 /***
2868 * DESCRIPTION:
2869 * Removes all selection ranges
2871 * Parameters(s):
2872 * [I] infoPtr : valid pointer to the listview structure
2873 * [I] toSkip : item range to skip removing the selection
2875 * RETURNS:
2876 * SUCCESS : TRUE
2877 * FAILURE : TRUE
2879 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2881 LVITEMW lvItem;
2882 ITERATOR i;
2883 RANGES clone;
2885 TRACE("()\n");
2887 lvItem.state = 0;
2888 lvItem.stateMask = LVIS_SELECTED;
2890 /* need to clone the DPA because callbacks can change it */
2891 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2892 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2893 while(iterator_next(&i))
2894 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2895 /* note that the iterator destructor will free the cloned range */
2896 iterator_destroy(&i);
2898 return TRUE;
2901 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2903 RANGES toSkip;
2905 if (!(toSkip = ranges_create(1))) return FALSE;
2906 if (nItem != -1) ranges_additem(toSkip, nItem);
2907 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2908 ranges_destroy(toSkip);
2909 return TRUE;
2912 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2914 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2917 /***
2918 * DESCRIPTION:
2919 * Retrieves the number of items that are marked as selected.
2921 * PARAMETER(S):
2922 * [I] infoPtr : valid pointer to the listview structure
2924 * RETURN:
2925 * Number of items selected.
2927 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2929 INT nSelectedCount = 0;
2931 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2933 INT i;
2934 for (i = 0; i < infoPtr->nItemCount; i++)
2936 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2937 nSelectedCount++;
2940 else
2941 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2943 TRACE("nSelectedCount=%d\n", nSelectedCount);
2944 return nSelectedCount;
2947 /***
2948 * DESCRIPTION:
2949 * Manages the item focus.
2951 * PARAMETER(S):
2952 * [I] infoPtr : valid pointer to the listview structure
2953 * [I] nItem : item index
2955 * RETURN:
2956 * TRUE : focused item changed
2957 * FALSE : focused item has NOT changed
2959 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2961 INT oldFocus = infoPtr->nFocusedItem;
2962 LVITEMW lvItem;
2964 if (nItem == infoPtr->nFocusedItem) return FALSE;
2966 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2967 lvItem.stateMask = LVIS_FOCUSED;
2968 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2970 return oldFocus != infoPtr->nFocusedItem;
2973 /* Helper function for LISTVIEW_ShiftIndices *only* */
2974 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2976 if (nShiftItem < nItem) return nShiftItem;
2978 if (nShiftItem > nItem) return nShiftItem + direction;
2980 if (direction > 0) return nShiftItem + direction;
2982 return min(nShiftItem, infoPtr->nItemCount - 1);
2986 * DESCRIPTION:
2987 * Updates the various indices after an item has been inserted or deleted.
2989 * PARAMETER(S):
2990 * [I] infoPtr : valid pointer to the listview structure
2991 * [I] nItem : item index
2992 * [I] direction : Direction of shift, +1 or -1.
2994 * RETURN:
2995 * None
2997 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2999 INT nNewFocus;
3000 BOOL bOldChange;
3002 /* temporarily disable change notification while shifting items */
3003 bOldChange = infoPtr->bDoChangeNotify;
3004 infoPtr->bDoChangeNotify = FALSE;
3006 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3008 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3010 assert(abs(direction) == 1);
3012 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3014 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3015 if (nNewFocus != infoPtr->nFocusedItem)
3016 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3018 /* But we are not supposed to modify nHotItem! */
3020 infoPtr->bDoChangeNotify = bOldChange;
3025 * DESCRIPTION:
3026 * Adds a block of selections.
3028 * PARAMETER(S):
3029 * [I] infoPtr : valid pointer to the listview structure
3030 * [I] nItem : item index
3032 * RETURN:
3033 * Whether the window is still valid.
3035 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3037 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3038 INT nLast = max(infoPtr->nSelectionMark, nItem);
3039 HWND hwndSelf = infoPtr->hwndSelf;
3040 NMLVODSTATECHANGE nmlv;
3041 LVITEMW item;
3042 BOOL bOldChange;
3043 INT i;
3045 /* Temporarily disable change notification
3046 * If the control is LVS_OWNERDATA, we need to send
3047 * only one LVN_ODSTATECHANGED notification.
3048 * See MSDN documentation for LVN_ITEMCHANGED.
3050 bOldChange = infoPtr->bDoChangeNotify;
3051 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3053 if (nFirst == -1) nFirst = nItem;
3055 item.state = LVIS_SELECTED;
3056 item.stateMask = LVIS_SELECTED;
3058 for (i = nFirst; i <= nLast; i++)
3059 LISTVIEW_SetItemState(infoPtr,i,&item);
3061 ZeroMemory(&nmlv, sizeof(nmlv));
3062 nmlv.iFrom = nFirst;
3063 nmlv.iTo = nLast;
3064 nmlv.uNewState = 0;
3065 nmlv.uOldState = item.state;
3067 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3068 if (!IsWindow(hwndSelf))
3069 return FALSE;
3070 infoPtr->bDoChangeNotify = bOldChange;
3071 return TRUE;
3075 /***
3076 * DESCRIPTION:
3077 * Sets a single group selection.
3079 * PARAMETER(S):
3080 * [I] infoPtr : valid pointer to the listview structure
3081 * [I] nItem : item index
3083 * RETURN:
3084 * None
3086 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3088 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3089 RANGES selection;
3090 LVITEMW item;
3091 ITERATOR i;
3092 BOOL bOldChange;
3094 if (!(selection = ranges_create(100))) return;
3096 item.state = LVIS_SELECTED;
3097 item.stateMask = LVIS_SELECTED;
3099 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3101 if (infoPtr->nSelectionMark == -1)
3103 infoPtr->nSelectionMark = nItem;
3104 ranges_additem(selection, nItem);
3106 else
3108 RANGE sel;
3110 sel.lower = min(infoPtr->nSelectionMark, nItem);
3111 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3112 ranges_add(selection, sel);
3115 else
3117 RECT rcItem, rcSel, rcSelMark;
3118 POINT ptItem;
3120 rcItem.left = LVIR_BOUNDS;
3121 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3122 rcSelMark.left = LVIR_BOUNDS;
3123 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3124 UnionRect(&rcSel, &rcItem, &rcSelMark);
3125 iterator_frameditems(&i, infoPtr, &rcSel);
3126 while(iterator_next(&i))
3128 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3129 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3131 iterator_destroy(&i);
3134 bOldChange = infoPtr->bDoChangeNotify;
3135 infoPtr->bDoChangeNotify = FALSE;
3137 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3140 iterator_rangesitems(&i, selection);
3141 while(iterator_next(&i))
3142 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3143 /* this will also destroy the selection */
3144 iterator_destroy(&i);
3146 infoPtr->bDoChangeNotify = bOldChange;
3148 LISTVIEW_SetItemFocus(infoPtr, nItem);
3151 /***
3152 * DESCRIPTION:
3153 * Sets a single selection.
3155 * PARAMETER(S):
3156 * [I] infoPtr : valid pointer to the listview structure
3157 * [I] nItem : item index
3159 * RETURN:
3160 * None
3162 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3164 LVITEMW lvItem;
3166 TRACE("nItem=%d\n", nItem);
3168 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3170 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3171 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3172 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3174 infoPtr->nSelectionMark = nItem;
3177 /***
3178 * DESCRIPTION:
3179 * Set selection(s) with keyboard.
3181 * PARAMETER(S):
3182 * [I] infoPtr : valid pointer to the listview structure
3183 * [I] nItem : item index
3185 * RETURN:
3186 * SUCCESS : TRUE (needs to be repainted)
3187 * FAILURE : FALSE (nothing has changed)
3189 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3191 /* FIXME: pass in the state */
3192 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3193 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3194 BOOL bResult = FALSE;
3196 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3197 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3199 if (infoPtr->dwStyle & LVS_SINGLESEL)
3201 bResult = TRUE;
3202 LISTVIEW_SetSelection(infoPtr, nItem);
3204 else
3206 if (wShift)
3208 bResult = TRUE;
3209 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3211 else if (wCtrl)
3213 LVITEMW lvItem;
3214 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3215 lvItem.stateMask = LVIS_SELECTED;
3216 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3218 if (lvItem.state & LVIS_SELECTED)
3219 infoPtr->nSelectionMark = nItem;
3221 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3223 else
3225 bResult = TRUE;
3226 LISTVIEW_SetSelection(infoPtr, nItem);
3229 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3232 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3233 return bResult;
3236 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3238 LVHITTESTINFO lvHitTestInfo;
3240 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3241 lvHitTestInfo.pt.x = pt.x;
3242 lvHitTestInfo.pt.y = pt.y;
3244 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3246 lpLVItem->mask = LVIF_PARAM;
3247 lpLVItem->iItem = lvHitTestInfo.iItem;
3248 lpLVItem->iSubItem = 0;
3250 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3253 /***
3254 * DESCRIPTION:
3255 * Called when the mouse is being actively tracked and has hovered for a specified
3256 * amount of time
3258 * PARAMETER(S):
3259 * [I] infoPtr : valid pointer to the listview structure
3260 * [I] fwKeys : key indicator
3261 * [I] x,y : mouse position
3263 * RETURN:
3264 * 0 if the message was processed, non-zero if there was an error
3266 * INFO:
3267 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3268 * over the item for a certain period of time.
3271 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3273 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3275 LVITEMW item;
3276 POINT pt;
3278 pt.x = x;
3279 pt.y = y;
3281 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3282 LISTVIEW_SetSelection(infoPtr, item.iItem);
3285 return 0;
3288 /***
3289 * DESCRIPTION:
3290 * Called whenever WM_MOUSEMOVE is received.
3292 * PARAMETER(S):
3293 * [I] infoPtr : valid pointer to the listview structure
3294 * [I] fwKeys : key indicator
3295 * [I] x,y : mouse position
3297 * RETURN:
3298 * 0 if the message is processed, non-zero if there was an error
3300 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3302 TRACKMOUSEEVENT trackinfo;
3304 if (!(fwKeys & MK_LBUTTON))
3305 infoPtr->bLButtonDown = FALSE;
3307 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3309 LVHITTESTINFO lvHitTestInfo;
3310 NMLISTVIEW nmlv;
3312 lvHitTestInfo.pt = infoPtr->ptClickPos;
3313 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3315 ZeroMemory(&nmlv, sizeof(nmlv));
3316 nmlv.iItem = lvHitTestInfo.iItem;
3317 nmlv.ptAction = infoPtr->ptClickPos;
3319 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3321 return 0;
3323 else
3324 infoPtr->bLButtonDown = FALSE;
3326 /* see if we are supposed to be tracking mouse hovering */
3327 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3328 /* fill in the trackinfo struct */
3329 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3330 trackinfo.dwFlags = TME_QUERY;
3331 trackinfo.hwndTrack = infoPtr->hwndSelf;
3332 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3334 /* see if we are already tracking this hwnd */
3335 _TrackMouseEvent(&trackinfo);
3337 if(!(trackinfo.dwFlags & TME_HOVER)) {
3338 trackinfo.dwFlags = TME_HOVER;
3340 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3341 _TrackMouseEvent(&trackinfo);
3345 return 0;
3349 /***
3350 * Tests wheather the item is assignable to a list with style lStyle
3352 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3354 if ( (lpLVItem->mask & LVIF_TEXT) &&
3355 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3356 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3358 return TRUE;
3362 /***
3363 * DESCRIPTION:
3364 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3366 * PARAMETER(S):
3367 * [I] infoPtr : valid pointer to the listview structure
3368 * [I] lpLVItem : valid pointer to new item atttributes
3369 * [I] isNew : the item being set is being inserted
3370 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3371 * [O] bChanged : will be set to TRUE if the item really changed
3373 * RETURN:
3374 * SUCCESS : TRUE
3375 * FAILURE : FALSE
3377 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3379 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3380 ITEM_INFO *lpItem;
3381 NMLISTVIEW nmlv;
3382 UINT uChanged = 0;
3383 LVITEMW item;
3385 TRACE("()\n");
3387 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3389 if (lpLVItem->mask == 0) return TRUE;
3391 if (infoPtr->dwStyle & LVS_OWNERDATA)
3393 /* a virtual listview we stores only selection and focus */
3394 if (lpLVItem->mask & ~LVIF_STATE)
3395 return FALSE;
3396 lpItem = NULL;
3398 else
3400 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3401 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3402 assert (lpItem);
3405 /* we need to get the lParam and state of the item */
3406 item.iItem = lpLVItem->iItem;
3407 item.iSubItem = lpLVItem->iSubItem;
3408 item.mask = LVIF_STATE | LVIF_PARAM;
3409 item.stateMask = ~0;
3410 item.state = 0;
3411 item.lParam = 0;
3412 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3414 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3415 /* determine what fields will change */
3416 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3417 uChanged |= LVIF_STATE;
3419 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3420 uChanged |= LVIF_IMAGE;
3422 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3423 uChanged |= LVIF_PARAM;
3425 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3426 uChanged |= LVIF_INDENT;
3428 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3429 uChanged |= LVIF_TEXT;
3431 TRACE("uChanged=0x%x\n", uChanged);
3432 if (!uChanged) return TRUE;
3433 *bChanged = TRUE;
3435 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3436 nmlv.iItem = lpLVItem->iItem;
3437 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3438 nmlv.uOldState = item.state;
3439 nmlv.uChanged = uChanged;
3440 nmlv.lParam = item.lParam;
3442 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3443 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3444 /* are enabled */
3445 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3447 HWND hwndSelf = infoPtr->hwndSelf;
3449 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3450 return FALSE;
3451 if (!IsWindow(hwndSelf))
3452 return FALSE;
3455 /* copy information */
3456 if (lpLVItem->mask & LVIF_TEXT)
3457 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3459 if (lpLVItem->mask & LVIF_IMAGE)
3460 lpItem->hdr.iImage = lpLVItem->iImage;
3462 if (lpLVItem->mask & LVIF_PARAM)
3463 lpItem->lParam = lpLVItem->lParam;
3465 if (lpLVItem->mask & LVIF_INDENT)
3466 lpItem->iIndent = lpLVItem->iIndent;
3468 if (uChanged & LVIF_STATE)
3470 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3472 lpItem->state &= ~lpLVItem->stateMask;
3473 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3475 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3477 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3478 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3480 else if (lpLVItem->stateMask & LVIS_SELECTED)
3481 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3483 /* if we are asked to change focus, and we manage it, do it */
3484 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3486 if (lpLVItem->state & LVIS_FOCUSED)
3488 LISTVIEW_SetItemFocus(infoPtr, -1);
3489 infoPtr->nFocusedItem = lpLVItem->iItem;
3490 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3492 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3493 infoPtr->nFocusedItem = -1;
3497 /* if we're inserting the item, we're done */
3498 if (isNew) return TRUE;
3500 /* send LVN_ITEMCHANGED notification */
3501 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3502 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3504 return TRUE;
3507 /***
3508 * DESCRIPTION:
3509 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3511 * PARAMETER(S):
3512 * [I] infoPtr : valid pointer to the listview structure
3513 * [I] lpLVItem : valid pointer to new subitem atttributes
3514 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3515 * [O] bChanged : will be set to TRUE if the item really changed
3517 * RETURN:
3518 * SUCCESS : TRUE
3519 * FAILURE : FALSE
3521 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3523 HDPA hdpaSubItems;
3524 SUBITEM_INFO *lpSubItem;
3526 /* we do not support subitems for virtual listviews */
3527 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3529 /* set subitem only if column is present */
3530 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3532 /* First do some sanity checks */
3533 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3534 particularly useful. We currently do not actually do anything with
3535 the flag on subitems.
3537 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3538 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3540 /* get the subitem structure, and create it if not there */
3541 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3542 assert (hdpaSubItems);
3544 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3545 if (!lpSubItem)
3547 SUBITEM_INFO *tmpSubItem;
3548 INT i;
3550 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3551 if (!lpSubItem) return FALSE;
3552 /* we could binary search here, if need be...*/
3553 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3555 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3556 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3558 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3560 Free(lpSubItem);
3561 return FALSE;
3563 lpSubItem->iSubItem = lpLVItem->iSubItem;
3564 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3565 *bChanged = TRUE;
3568 if (lpLVItem->mask & LVIF_IMAGE)
3569 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3571 lpSubItem->hdr.iImage = lpLVItem->iImage;
3572 *bChanged = TRUE;
3575 if (lpLVItem->mask & LVIF_TEXT)
3576 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3578 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3579 *bChanged = TRUE;
3582 return TRUE;
3585 /***
3586 * DESCRIPTION:
3587 * Sets item attributes.
3589 * PARAMETER(S):
3590 * [I] infoPtr : valid pointer to the listview structure
3591 * [I] lpLVItem : new item atttributes
3592 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3594 * RETURN:
3595 * SUCCESS : TRUE
3596 * FAILURE : FALSE
3598 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3600 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3601 HWND hwndSelf = infoPtr->hwndSelf;
3602 LPWSTR pszText = NULL;
3603 BOOL bResult, bChanged = FALSE;
3605 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3607 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3608 return FALSE;
3610 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3611 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3613 pszText = lpLVItem->pszText;
3614 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3617 /* actually set the fields */
3618 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3620 if (lpLVItem->iSubItem)
3621 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3622 else
3623 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3624 if (!IsWindow(hwndSelf))
3625 return FALSE;
3627 /* redraw item, if necessary */
3628 if (bChanged && !infoPtr->bIsDrawing)
3630 /* this little optimization eliminates some nasty flicker */
3631 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3632 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3633 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3634 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3635 else
3636 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3638 /* restore text */
3639 if (pszText)
3641 textfreeT(lpLVItem->pszText, isW);
3642 lpLVItem->pszText = pszText;
3645 return bResult;
3648 /***
3649 * DESCRIPTION:
3650 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3652 * PARAMETER(S):
3653 * [I] infoPtr : valid pointer to the listview structure
3655 * RETURN:
3656 * item index
3658 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3660 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3661 INT nItem = 0;
3662 SCROLLINFO scrollInfo;
3664 scrollInfo.cbSize = sizeof(SCROLLINFO);
3665 scrollInfo.fMask = SIF_POS;
3667 if (uView == LVS_LIST)
3669 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3670 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3672 else if (uView == LVS_REPORT)
3674 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3675 nItem = scrollInfo.nPos;
3677 else
3679 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3680 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3683 TRACE("nItem=%d\n", nItem);
3685 return nItem;
3689 /***
3690 * DESCRIPTION:
3691 * Erases the background of the given rectangle
3693 * PARAMETER(S):
3694 * [I] infoPtr : valid pointer to the listview structure
3695 * [I] hdc : device context handle
3696 * [I] lprcBox : clipping rectangle
3698 * RETURN:
3699 * Success: TRUE
3700 * Failure: FALSE
3702 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3704 if (!infoPtr->hBkBrush) return FALSE;
3706 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3708 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3711 /***
3712 * DESCRIPTION:
3713 * Draws an item.
3715 * PARAMETER(S):
3716 * [I] infoPtr : valid pointer to the listview structure
3717 * [I] hdc : device context handle
3718 * [I] nItem : item index
3719 * [I] nSubItem : subitem index
3720 * [I] pos : item position in client coordinates
3721 * [I] cdmode : custom draw mode
3723 * RETURN:
3724 * Success: TRUE
3725 * Failure: FALSE
3727 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3729 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3730 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3731 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3732 DWORD cdsubitemmode = CDRF_DODEFAULT;
3733 LPRECT lprcFocus;
3734 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3735 NMLVCUSTOMDRAW nmlvcd;
3736 HIMAGELIST himl;
3737 LVITEMW lvItem;
3738 HFONT hOldFont;
3740 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3742 /* get information needed for drawing the item */
3743 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3744 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3745 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3746 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3747 lvItem.iItem = nItem;
3748 lvItem.iSubItem = nSubItem;
3749 lvItem.state = 0;
3750 lvItem.lParam = 0;
3751 lvItem.cchTextMax = DISP_TEXT_SIZE;
3752 lvItem.pszText = szDispText;
3753 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3754 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3755 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3756 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3757 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3759 /* now check if we need to update the focus rectangle */
3760 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3762 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3763 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3764 OffsetRect(&rcBox, pos.x, pos.y);
3765 OffsetRect(&rcSelect, pos.x, pos.y);
3766 OffsetRect(&rcIcon, pos.x, pos.y);
3767 OffsetRect(&rcStateIcon, pos.x, pos.y);
3768 OffsetRect(&rcLabel, pos.x, pos.y);
3769 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3770 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3771 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3773 /* fill in the custom draw structure */
3774 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3776 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3777 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3778 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3779 if (cdmode & CDRF_NOTIFYITEMDRAW)
3780 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3781 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3782 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3783 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3784 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3786 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3787 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3789 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3790 prepaint_setup(infoPtr, hdc, &nmlvcd);
3792 /* in full row select, subitems, will just use main item's colors */
3793 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3794 nmlvcd.clrTextBk = CLR_NONE;
3796 /* state icons */
3797 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3799 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3800 if (uStateImage)
3802 TRACE("uStateImage=%d\n", uStateImage);
3803 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3804 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3808 /* small icons */
3809 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3810 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3812 TRACE("iImage=%d\n", lvItem.iImage);
3813 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3814 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3815 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3818 /* Don't bother painting item being edited */
3819 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3821 /* FIXME: temporary hack */
3822 rcSelect.left = rcLabel.left;
3824 /* draw the selection background, if we're drawing the main item */
3825 if (nSubItem == 0)
3827 /* in icon mode, the label rect is really what we want to draw the
3828 * background for */
3829 if (uView == LVS_ICON)
3830 rcSelect = rcLabel;
3832 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3833 rcSelect.right = rcBox.right;
3835 if (nmlvcd.clrTextBk != CLR_NONE)
3836 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3837 if(lprcFocus) *lprcFocus = rcSelect;
3840 /* figure out the text drawing flags */
3841 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3842 if (uView == LVS_ICON)
3843 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3844 else if (nSubItem)
3846 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3848 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3849 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3850 default: uFormat |= DT_LEFT;
3853 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3855 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3856 else rcLabel.left += LABEL_HOR_PADDING;
3858 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3859 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3861 postpaint:
3862 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3863 notify_postpaint(infoPtr, &nmlvcd);
3864 if (cdsubitemmode & CDRF_NEWFONT)
3865 SelectObject(hdc, hOldFont);
3866 return TRUE;
3869 /***
3870 * DESCRIPTION:
3871 * Draws listview items when in owner draw mode.
3873 * PARAMETER(S):
3874 * [I] infoPtr : valid pointer to the listview structure
3875 * [I] hdc : device context handle
3877 * RETURN:
3878 * None
3880 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3882 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3883 DWORD cditemmode = CDRF_DODEFAULT;
3884 NMLVCUSTOMDRAW nmlvcd;
3885 POINT Origin, Position;
3886 DRAWITEMSTRUCT dis;
3887 LVITEMW item;
3889 TRACE("()\n");
3891 ZeroMemory(&dis, sizeof(dis));
3893 /* Get scroll info once before loop */
3894 LISTVIEW_GetOrigin(infoPtr, &Origin);
3896 /* iterate through the invalidated rows */
3897 while(iterator_next(i))
3899 item.iItem = i->nItem;
3900 item.iSubItem = 0;
3901 item.mask = LVIF_PARAM | LVIF_STATE;
3902 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3903 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3905 dis.CtlType = ODT_LISTVIEW;
3906 dis.CtlID = uID;
3907 dis.itemID = item.iItem;
3908 dis.itemAction = ODA_DRAWENTIRE;
3909 dis.itemState = 0;
3910 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3911 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3912 dis.hwndItem = infoPtr->hwndSelf;
3913 dis.hDC = hdc;
3914 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3915 dis.rcItem.left = Position.x + Origin.x;
3916 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3917 dis.rcItem.top = Position.y + Origin.y;
3918 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3919 dis.itemData = item.lParam;
3921 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3924 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3925 * structure for the rest. of the paint cycle
3927 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3928 if (cdmode & CDRF_NOTIFYITEMDRAW)
3929 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3931 if (!(cditemmode & CDRF_SKIPDEFAULT))
3933 prepaint_setup (infoPtr, hdc, &nmlvcd);
3934 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3937 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3938 notify_postpaint(infoPtr, &nmlvcd);
3942 /***
3943 * DESCRIPTION:
3944 * Draws listview items when in report display mode.
3946 * PARAMETER(S):
3947 * [I] infoPtr : valid pointer to the listview structure
3948 * [I] hdc : device context handle
3949 * [I] cdmode : custom draw mode
3951 * RETURN:
3952 * None
3954 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3956 INT rgntype;
3957 RECT rcClip, rcItem;
3958 POINT Origin, Position;
3959 RANGE colRange;
3960 ITERATOR j;
3962 TRACE("()\n");
3964 /* figure out what to draw */
3965 rgntype = GetClipBox(hdc, &rcClip);
3966 if (rgntype == NULLREGION) return;
3968 /* Get scroll info once before loop */
3969 LISTVIEW_GetOrigin(infoPtr, &Origin);
3971 /* narrow down the columns we need to paint */
3972 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3974 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3975 if (rcItem.right + Origin.x >= rcClip.left) break;
3977 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3979 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3980 if (rcItem.left + Origin.x < rcClip.right) break;
3982 iterator_rangeitems(&j, colRange);
3984 /* in full row select, we _have_ to draw the main item */
3985 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3986 j.nSpecial = 0;
3988 /* iterate through the invalidated rows */
3989 while(iterator_next(i))
3991 /* iterate through the invalidated columns */
3992 while(iterator_next(&j))
3994 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3995 Position.x += Origin.x;
3996 Position.y += Origin.y;
3998 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4000 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4001 rcItem.top = 0;
4002 rcItem.bottom = infoPtr->nItemHeight;
4003 OffsetRect(&rcItem, Position.x, Position.y);
4004 if (!RectVisible(hdc, &rcItem)) continue;
4007 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4010 iterator_destroy(&j);
4013 /***
4014 * DESCRIPTION:
4015 * Draws listview items when in list display mode.
4017 * PARAMETER(S):
4018 * [I] infoPtr : valid pointer to the listview structure
4019 * [I] hdc : device context handle
4020 * [I] cdmode : custom draw mode
4022 * RETURN:
4023 * None
4025 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4027 POINT Origin, Position;
4029 /* Get scroll info once before loop */
4030 LISTVIEW_GetOrigin(infoPtr, &Origin);
4032 while(iterator_prev(i))
4034 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4035 Position.x += Origin.x;
4036 Position.y += Origin.y;
4038 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4043 /***
4044 * DESCRIPTION:
4045 * Draws listview items.
4047 * PARAMETER(S):
4048 * [I] infoPtr : valid pointer to the listview structure
4049 * [I] hdc : device context handle
4050 * [I] prcErase : rect to be erased before refresh (may be NULL)
4052 * RETURN:
4053 * NoneX
4055 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4057 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4058 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4059 NMLVCUSTOMDRAW nmlvcd;
4060 HFONT hOldFont = 0;
4061 DWORD cdmode;
4062 INT oldBkMode = 0;
4063 RECT rcClient;
4064 ITERATOR i;
4065 HDC hdcOrig = hdc;
4066 HBITMAP hbmp = NULL;
4068 LISTVIEW_DUMP(infoPtr);
4070 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4071 TRACE("double buffering\n");
4073 hdc = CreateCompatibleDC(hdcOrig);
4074 if (!hdc) {
4075 ERR("Failed to create DC for backbuffer\n");
4076 return;
4078 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4079 infoPtr->rcList.bottom);
4080 if (!hbmp) {
4081 ERR("Failed to create bitmap for backbuffer\n");
4082 DeleteDC(hdc);
4083 return;
4086 SelectObject(hdc, hbmp);
4087 SelectObject(hdc, infoPtr->hFont);
4088 } else {
4089 /* Save dc values we're gonna trash while drawing
4090 * FIXME: Should be done in LISTVIEW_DrawItem() */
4091 hOldFont = SelectObject(hdc, infoPtr->hFont);
4092 oldBkMode = GetBkMode(hdc);
4093 oldBkColor = GetBkColor(hdc);
4094 oldTextColor = GetTextColor(hdc);
4097 infoPtr->bIsDrawing = TRUE;
4099 if (prcErase) {
4100 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4101 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4102 /* If no erasing was done (usually because RedrawWindow was called
4103 * with RDW_INVALIDATE only) we need to copy the old contents into
4104 * the backbuffer before continuing. */
4105 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4106 infoPtr->rcList.right - infoPtr->rcList.left,
4107 infoPtr->rcList.bottom - infoPtr->rcList.top,
4108 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4111 /* FIXME: Shouldn't need to do this */
4112 oldClrTextBk = infoPtr->clrTextBk;
4113 oldClrText = infoPtr->clrText;
4115 infoPtr->cditemmode = CDRF_DODEFAULT;
4117 GetClientRect(infoPtr->hwndSelf, &rcClient);
4118 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4119 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4120 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4121 prepaint_setup(infoPtr, hdc, &nmlvcd);
4123 /* Use these colors to draw the items */
4124 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4125 infoPtr->clrText = nmlvcd.clrText;
4127 /* nothing to draw */
4128 if(infoPtr->nItemCount == 0) goto enddraw;
4130 /* figure out what we need to draw */
4131 iterator_visibleitems(&i, infoPtr, hdc);
4133 /* send cache hint notification */
4134 if (infoPtr->dwStyle & LVS_OWNERDATA)
4136 RANGE range = iterator_range(&i);
4137 NMLVCACHEHINT nmlv;
4139 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4140 nmlv.iFrom = range.lower;
4141 nmlv.iTo = range.upper - 1;
4142 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4145 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4146 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4147 else
4149 if (uView == LVS_REPORT)
4150 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4151 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4152 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4154 /* if we have a focus rect, draw it */
4155 if (infoPtr->bFocus)
4156 DrawFocusRect(hdc, &infoPtr->rcFocus);
4158 iterator_destroy(&i);
4160 enddraw:
4161 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4162 notify_postpaint(infoPtr, &nmlvcd);
4164 infoPtr->clrTextBk = oldClrTextBk;
4165 infoPtr->clrText = oldClrText;
4167 if(hbmp) {
4168 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4169 infoPtr->rcList.right - infoPtr->rcList.left,
4170 infoPtr->rcList.bottom - infoPtr->rcList.top,
4171 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4173 DeleteObject(hbmp);
4174 DeleteDC(hdc);
4175 } else {
4176 SelectObject(hdc, hOldFont);
4177 SetBkMode(hdc, oldBkMode);
4178 SetBkColor(hdc, oldBkColor);
4179 SetTextColor(hdc, oldTextColor);
4182 infoPtr->bIsDrawing = FALSE;
4186 /***
4187 * DESCRIPTION:
4188 * Calculates the approximate width and height of a given number of items.
4190 * PARAMETER(S):
4191 * [I] infoPtr : valid pointer to the listview structure
4192 * [I] nItemCount : number of items
4193 * [I] wWidth : width
4194 * [I] wHeight : height
4196 * RETURN:
4197 * Returns a DWORD. The width in the low word and the height in high word.
4199 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4200 WORD wWidth, WORD wHeight)
4202 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4203 INT nItemCountPerColumn = 1;
4204 INT nColumnCount = 0;
4205 DWORD dwViewRect = 0;
4207 if (nItemCount == -1)
4208 nItemCount = infoPtr->nItemCount;
4210 if (uView == LVS_LIST)
4212 if (wHeight == 0xFFFF)
4214 /* use current height */
4215 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4218 if (wHeight < infoPtr->nItemHeight)
4219 wHeight = infoPtr->nItemHeight;
4221 if (nItemCount > 0)
4223 if (infoPtr->nItemHeight > 0)
4225 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4226 if (nItemCountPerColumn == 0)
4227 nItemCountPerColumn = 1;
4229 if (nItemCount % nItemCountPerColumn != 0)
4230 nColumnCount = nItemCount / nItemCountPerColumn;
4231 else
4232 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4236 /* Microsoft padding magic */
4237 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4238 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4240 dwViewRect = MAKELONG(wWidth, wHeight);
4242 else if (uView == LVS_REPORT)
4244 RECT rcBox;
4246 if (infoPtr->nItemCount > 0)
4248 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4249 wWidth = rcBox.right - rcBox.left;
4250 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4252 else
4254 /* use current height and width */
4255 if (wHeight == 0xffff)
4256 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4257 if (wWidth == 0xffff)
4258 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4261 dwViewRect = MAKELONG(wWidth, wHeight);
4263 else if (uView == LVS_SMALLICON)
4264 FIXME("uView == LVS_SMALLICON: not implemented\n");
4265 else if (uView == LVS_ICON)
4266 FIXME("uView == LVS_ICON: not implemented\n");
4268 return dwViewRect;
4272 /***
4273 * DESCRIPTION:
4274 * Create a drag image list for the specified item.
4276 * PARAMETER(S):
4277 * [I] infoPtr : valid pointer to the listview structure
4278 * [I] iItem : index of item
4279 * [O] lppt : Upperr-left corner of the image
4281 * RETURN:
4282 * Returns a handle to the image list if successful, NULL otherwise.
4284 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4286 RECT rcItem;
4287 SIZE size;
4288 POINT pos;
4289 HDC hdc, hdcOrig;
4290 HBITMAP hbmp, hOldbmp;
4291 HIMAGELIST dragList = 0;
4292 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4294 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4295 return 0;
4297 rcItem.left = LVIR_BOUNDS;
4298 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4299 return 0;
4301 lppt->x = rcItem.left;
4302 lppt->y = rcItem.top;
4304 size.cx = rcItem.right - rcItem.left;
4305 size.cy = rcItem.bottom - rcItem.top;
4307 hdcOrig = GetDC(infoPtr->hwndSelf);
4308 hdc = CreateCompatibleDC(hdcOrig);
4309 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4310 hOldbmp = SelectObject(hdc, hbmp);
4312 rcItem.left = rcItem.top = 0;
4313 rcItem.right = size.cx;
4314 rcItem.bottom = size.cy;
4315 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4317 pos.x = pos.y = 0;
4318 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4320 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4321 SelectObject(hdc, hOldbmp);
4322 ImageList_Add(dragList, hbmp, 0);
4324 else
4325 SelectObject(hdc, hOldbmp);
4327 DeleteObject(hbmp);
4328 DeleteDC(hdc);
4329 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4331 TRACE("ret=%p\n", dragList);
4333 return dragList;
4337 /***
4338 * DESCRIPTION:
4339 * Removes all listview items and subitems.
4341 * PARAMETER(S):
4342 * [I] infoPtr : valid pointer to the listview structure
4344 * RETURN:
4345 * SUCCESS : TRUE
4346 * FAILURE : FALSE
4348 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4350 NMLISTVIEW nmlv;
4351 HDPA hdpaSubItems = NULL;
4352 BOOL bSuppress;
4353 ITEMHDR *hdrItem;
4354 INT i, j;
4356 TRACE("()\n");
4358 /* we do it directly, to avoid notifications */
4359 ranges_clear(infoPtr->selectionRanges);
4360 infoPtr->nSelectionMark = -1;
4361 infoPtr->nFocusedItem = -1;
4362 SetRectEmpty(&infoPtr->rcFocus);
4363 /* But we are supposed to leave nHotItem as is! */
4366 /* send LVN_DELETEALLITEMS notification */
4367 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4368 nmlv.iItem = -1;
4369 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4371 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4373 /* send LVN_DELETEITEM notification, if not suppressed */
4374 if (!bSuppress) notify_deleteitem(infoPtr, i);
4375 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4377 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4378 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4380 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4381 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4382 Free(hdrItem);
4384 DPA_Destroy(hdpaSubItems);
4385 DPA_DeletePtr(infoPtr->hdpaItems, i);
4387 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4388 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4389 infoPtr->nItemCount --;
4392 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4393 LISTVIEW_UpdateScroll(infoPtr);
4394 LISTVIEW_InvalidateList(infoPtr);
4396 return TRUE;
4399 /***
4400 * DESCRIPTION:
4401 * Scrolls, and updates the columns, when a column is changing width.
4403 * PARAMETER(S):
4404 * [I] infoPtr : valid pointer to the listview structure
4405 * [I] nColumn : column to scroll
4406 * [I] dx : amount of scroll, in pixels
4408 * RETURN:
4409 * None.
4411 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4413 COLUMN_INFO *lpColumnInfo;
4414 RECT rcOld, rcCol;
4415 POINT ptOrigin;
4416 INT nCol;
4418 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4419 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4420 rcCol = lpColumnInfo->rcHeader;
4421 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4422 rcCol.left = rcCol.right;
4424 /* ajust the other columns */
4425 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4427 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4428 lpColumnInfo->rcHeader.left += dx;
4429 lpColumnInfo->rcHeader.right += dx;
4432 /* do not update screen if not in report mode */
4433 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4435 /* if we have a focus, must first erase the focus rect */
4436 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4438 /* Need to reset the item width when inserting a new column */
4439 infoPtr->nItemWidth += dx;
4441 LISTVIEW_UpdateScroll(infoPtr);
4442 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4444 /* scroll to cover the deleted column, and invalidate for redraw */
4445 rcOld = infoPtr->rcList;
4446 rcOld.left = ptOrigin.x + rcCol.left + dx;
4447 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4449 /* we can restore focus now */
4450 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4453 /***
4454 * DESCRIPTION:
4455 * Removes a column from the listview control.
4457 * PARAMETER(S):
4458 * [I] infoPtr : valid pointer to the listview structure
4459 * [I] nColumn : column index
4461 * RETURN:
4462 * SUCCESS : TRUE
4463 * FAILURE : FALSE
4465 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4467 RECT rcCol;
4469 TRACE("nColumn=%d\n", nColumn);
4471 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4472 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4474 /* While the MSDN specifically says that column zero should not be deleted,
4475 what actually happens is that the column itself is deleted but no items or subitems
4476 are removed.
4479 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4481 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4482 return FALSE;
4484 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4485 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4487 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4489 SUBITEM_INFO *lpSubItem, *lpDelItem;
4490 HDPA hdpaSubItems;
4491 INT nItem, nSubItem, i;
4493 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4495 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4496 nSubItem = 0;
4497 lpDelItem = 0;
4498 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4500 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4501 if (lpSubItem->iSubItem == nColumn)
4503 nSubItem = i;
4504 lpDelItem = lpSubItem;
4506 else if (lpSubItem->iSubItem > nColumn)
4508 lpSubItem->iSubItem--;
4512 /* if we found our subitem, zapp it */
4513 if (nSubItem > 0)
4515 /* free string */
4516 if (is_textW(lpDelItem->hdr.pszText))
4517 Free(lpDelItem->hdr.pszText);
4519 /* free item */
4520 Free(lpDelItem);
4522 /* free dpa memory */
4523 DPA_DeletePtr(hdpaSubItems, nSubItem);
4528 /* update the other column info */
4529 LISTVIEW_UpdateItemSize(infoPtr);
4530 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4531 LISTVIEW_InvalidateList(infoPtr);
4532 else
4533 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4535 return TRUE;
4538 /***
4539 * DESCRIPTION:
4540 * Invalidates the listview after an item's insertion or deletion.
4542 * PARAMETER(S):
4543 * [I] infoPtr : valid pointer to the listview structure
4544 * [I] nItem : item index
4545 * [I] dir : -1 if deleting, 1 if inserting
4547 * RETURN:
4548 * None
4550 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4552 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4553 INT nPerCol, nItemCol, nItemRow;
4554 RECT rcScroll;
4555 POINT Origin;
4557 /* if we don't refresh, what's the point of scrolling? */
4558 if (!is_redrawing(infoPtr)) return;
4560 assert (abs(dir) == 1);
4562 /* arrange icons if autoarrange is on */
4563 if (is_autoarrange(infoPtr))
4565 BOOL arrange = TRUE;
4566 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4567 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4568 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4571 /* scrollbars need updating */
4572 LISTVIEW_UpdateScroll(infoPtr);
4574 /* figure out the item's position */
4575 if (uView == LVS_REPORT)
4576 nPerCol = infoPtr->nItemCount + 1;
4577 else if (uView == LVS_LIST)
4578 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4579 else /* LVS_ICON, or LVS_SMALLICON */
4580 return;
4582 nItemCol = nItem / nPerCol;
4583 nItemRow = nItem % nPerCol;
4584 LISTVIEW_GetOrigin(infoPtr, &Origin);
4586 /* move the items below up a slot */
4587 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4588 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4589 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4590 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4591 OffsetRect(&rcScroll, Origin.x, Origin.y);
4592 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4593 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4595 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4596 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4597 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4600 /* report has only that column, so we're done */
4601 if (uView == LVS_REPORT) return;
4603 /* now for LISTs, we have to deal with the columns to the right */
4604 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4605 rcScroll.top = 0;
4606 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4607 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4608 OffsetRect(&rcScroll, Origin.x, Origin.y);
4609 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4610 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4611 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4614 /***
4615 * DESCRIPTION:
4616 * Removes an item from the listview control.
4618 * PARAMETER(S):
4619 * [I] infoPtr : valid pointer to the listview structure
4620 * [I] nItem : item index
4622 * RETURN:
4623 * SUCCESS : TRUE
4624 * FAILURE : FALSE
4626 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4628 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4629 LVITEMW item;
4631 TRACE("(nItem=%d)\n", nItem);
4633 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4635 /* remove selection, and focus */
4636 item.state = 0;
4637 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4638 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4640 /* send LVN_DELETEITEM notification. */
4641 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4643 /* we need to do this here, because we'll be deleting stuff */
4644 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4645 LISTVIEW_InvalidateItem(infoPtr, nItem);
4647 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4649 HDPA hdpaSubItems;
4650 ITEMHDR *hdrItem;
4651 INT i;
4653 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4654 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4656 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4657 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4658 Free(hdrItem);
4660 DPA_Destroy(hdpaSubItems);
4663 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4665 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4666 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4669 infoPtr->nItemCount--;
4670 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4672 /* now is the invalidation fun */
4673 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4674 return TRUE;
4678 /***
4679 * DESCRIPTION:
4680 * Callback implementation for editlabel control
4682 * PARAMETER(S):
4683 * [I] infoPtr : valid pointer to the listview structure
4684 * [I] pszText : modified text
4685 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4687 * RETURN:
4688 * SUCCESS : TRUE
4689 * FAILURE : FALSE
4691 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4693 HWND hwndSelf = infoPtr->hwndSelf;
4694 NMLVDISPINFOW dispInfo;
4696 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4698 ZeroMemory(&dispInfo, sizeof(dispInfo));
4699 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4700 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4701 dispInfo.item.iSubItem = 0;
4702 dispInfo.item.stateMask = ~0;
4703 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4704 /* add the text from the edit in */
4705 dispInfo.item.mask |= LVIF_TEXT;
4706 dispInfo.item.pszText = pszText;
4707 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4709 /* Do we need to update the Item Text */
4710 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4711 if (!IsWindow(hwndSelf))
4712 return FALSE;
4713 if (!pszText) return TRUE;
4715 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4717 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4718 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4719 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4721 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4722 return TRUE;
4726 ZeroMemory(&dispInfo, sizeof(dispInfo));
4727 dispInfo.item.mask = LVIF_TEXT;
4728 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4729 dispInfo.item.iSubItem = 0;
4730 dispInfo.item.pszText = pszText;
4731 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4732 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4735 /***
4736 * DESCRIPTION:
4737 * Begin in place editing of specified list view item
4739 * PARAMETER(S):
4740 * [I] infoPtr : valid pointer to the listview structure
4741 * [I] nItem : item index
4742 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4744 * RETURN:
4745 * SUCCESS : TRUE
4746 * FAILURE : FALSE
4748 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4750 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4751 NMLVDISPINFOW dispInfo;
4752 RECT rect;
4753 HWND hwndSelf = infoPtr->hwndSelf;
4755 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4757 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4758 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4760 infoPtr->nEditLabelItem = nItem;
4762 /* Is the EditBox still there, if so remove it */
4763 if(infoPtr->hwndEdit != 0)
4765 SetFocus(infoPtr->hwndSelf);
4766 infoPtr->hwndEdit = 0;
4769 LISTVIEW_SetSelection(infoPtr, nItem);
4770 LISTVIEW_SetItemFocus(infoPtr, nItem);
4771 LISTVIEW_InvalidateItem(infoPtr, nItem);
4773 rect.left = LVIR_LABEL;
4774 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4776 ZeroMemory(&dispInfo, sizeof(dispInfo));
4777 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4778 dispInfo.item.iItem = nItem;
4779 dispInfo.item.iSubItem = 0;
4780 dispInfo.item.stateMask = ~0;
4781 dispInfo.item.pszText = szDispText;
4782 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4783 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4785 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4786 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4787 if (!infoPtr->hwndEdit) return 0;
4789 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4791 if (!IsWindow(hwndSelf))
4792 return 0;
4793 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4794 infoPtr->hwndEdit = 0;
4795 return 0;
4798 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4799 SetFocus(infoPtr->hwndEdit);
4800 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4801 return infoPtr->hwndEdit;
4805 /***
4806 * DESCRIPTION:
4807 * Ensures the specified item is visible, scrolling into view if necessary.
4809 * PARAMETER(S):
4810 * [I] infoPtr : valid pointer to the listview structure
4811 * [I] nItem : item index
4812 * [I] bPartial : partially or entirely visible
4814 * RETURN:
4815 * SUCCESS : TRUE
4816 * FAILURE : FALSE
4818 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4820 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4821 INT nScrollPosHeight = 0;
4822 INT nScrollPosWidth = 0;
4823 INT nHorzAdjust = 0;
4824 INT nVertAdjust = 0;
4825 INT nHorzDiff = 0;
4826 INT nVertDiff = 0;
4827 RECT rcItem, rcTemp;
4829 rcItem.left = LVIR_BOUNDS;
4830 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4832 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4834 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4836 /* scroll left/right, but in LVS_REPORT mode */
4837 if (uView == LVS_LIST)
4838 nScrollPosWidth = infoPtr->nItemWidth;
4839 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4840 nScrollPosWidth = 1;
4842 if (rcItem.left < infoPtr->rcList.left)
4844 nHorzAdjust = -1;
4845 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4847 else
4849 nHorzAdjust = 1;
4850 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4854 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4856 /* scroll up/down, but not in LVS_LIST mode */
4857 if (uView == LVS_REPORT)
4858 nScrollPosHeight = infoPtr->nItemHeight;
4859 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4860 nScrollPosHeight = 1;
4862 if (rcItem.top < infoPtr->rcList.top)
4864 nVertAdjust = -1;
4865 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4867 else
4869 nVertAdjust = 1;
4870 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4874 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4876 if (nScrollPosWidth)
4878 INT diff = nHorzDiff / nScrollPosWidth;
4879 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4880 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4883 if (nScrollPosHeight)
4885 INT diff = nVertDiff / nScrollPosHeight;
4886 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4887 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4890 return TRUE;
4893 /***
4894 * DESCRIPTION:
4895 * Searches for an item with specific characteristics.
4897 * PARAMETER(S):
4898 * [I] hwnd : window handle
4899 * [I] nStart : base item index
4900 * [I] lpFindInfo : item information to look for
4902 * RETURN:
4903 * SUCCESS : index of item
4904 * FAILURE : -1
4906 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
4907 const LVFINDINFOW *lpFindInfo)
4909 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4910 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4911 BOOL bWrap = FALSE, bNearest = FALSE;
4912 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4913 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4914 POINT Position, Destination;
4915 LVITEMW lvItem;
4917 if (!lpFindInfo || nItem < 0) return -1;
4919 lvItem.mask = 0;
4920 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4922 lvItem.mask |= LVIF_TEXT;
4923 lvItem.pszText = szDispText;
4924 lvItem.cchTextMax = DISP_TEXT_SIZE;
4927 if (lpFindInfo->flags & LVFI_WRAP)
4928 bWrap = TRUE;
4930 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4931 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4933 POINT Origin;
4934 RECT rcArea;
4936 LISTVIEW_GetOrigin(infoPtr, &Origin);
4937 Destination.x = lpFindInfo->pt.x - Origin.x;
4938 Destination.y = lpFindInfo->pt.y - Origin.y;
4939 switch(lpFindInfo->vkDirection)
4941 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4942 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4943 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4944 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4945 case VK_HOME: Destination.x = Destination.y = 0; break;
4946 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4947 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4948 case VK_END:
4949 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4950 Destination.x = rcArea.right;
4951 Destination.y = rcArea.bottom;
4952 break;
4953 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4955 bNearest = TRUE;
4957 else Destination.x = Destination.y = 0;
4959 /* if LVFI_PARAM is specified, all other flags are ignored */
4960 if (lpFindInfo->flags & LVFI_PARAM)
4962 lvItem.mask |= LVIF_PARAM;
4963 bNearest = FALSE;
4964 lvItem.mask &= ~LVIF_TEXT;
4967 again:
4968 for (; nItem < nLast; nItem++)
4970 lvItem.iItem = nItem;
4971 lvItem.iSubItem = 0;
4972 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4974 if (lvItem.mask & LVIF_PARAM)
4976 if (lpFindInfo->lParam == lvItem.lParam)
4977 return nItem;
4978 else
4979 continue;
4982 if (lvItem.mask & LVIF_TEXT)
4984 if (lpFindInfo->flags & LVFI_PARTIAL)
4986 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4988 else
4990 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4994 if (!bNearest) return nItem;
4996 /* This is very inefficient. To do a good job here,
4997 * we need a sorted array of (x,y) item positions */
4998 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5000 /* compute the distance^2 to the destination */
5001 xdist = Destination.x - Position.x;
5002 ydist = Destination.y - Position.y;
5003 dist = xdist * xdist + ydist * ydist;
5005 /* remember the distance, and item if it's closer */
5006 if (dist < mindist)
5008 mindist = dist;
5009 nNearestItem = nItem;
5013 if (bWrap)
5015 nItem = 0;
5016 nLast = min(nStart + 1, infoPtr->nItemCount);
5017 bWrap = FALSE;
5018 goto again;
5021 return nNearestItem;
5024 /***
5025 * DESCRIPTION:
5026 * Searches for an item with specific characteristics.
5028 * PARAMETER(S):
5029 * [I] hwnd : window handle
5030 * [I] nStart : base item index
5031 * [I] lpFindInfo : item information to look for
5033 * RETURN:
5034 * SUCCESS : index of item
5035 * FAILURE : -1
5037 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5038 const LVFINDINFOA *lpFindInfo)
5040 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5041 LVFINDINFOW fiw;
5042 INT res;
5043 LPWSTR strW;
5045 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5046 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5047 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5048 if (hasText) textfreeT(strW, FALSE);
5049 return res;
5052 /***
5053 * DESCRIPTION:
5054 * Retrieves the background image of the listview control.
5056 * PARAMETER(S):
5057 * [I] infoPtr : valid pointer to the listview structure
5058 * [O] lpBkImage : background image attributes
5060 * RETURN:
5061 * SUCCESS : TRUE
5062 * FAILURE : FALSE
5064 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5065 /* { */
5066 /* FIXME (listview, "empty stub!\n"); */
5067 /* return FALSE; */
5068 /* } */
5070 /***
5071 * DESCRIPTION:
5072 * Retrieves column attributes.
5074 * PARAMETER(S):
5075 * [I] infoPtr : valid pointer to the listview structure
5076 * [I] nColumn : column index
5077 * [IO] lpColumn : column information
5078 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5079 * otherwise it is in fact a LPLVCOLUMNA
5081 * RETURN:
5082 * SUCCESS : TRUE
5083 * FAILURE : FALSE
5085 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5087 COLUMN_INFO *lpColumnInfo;
5088 HDITEMW hdi;
5090 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5091 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5093 /* initialize memory */
5094 ZeroMemory(&hdi, sizeof(hdi));
5096 if (lpColumn->mask & LVCF_TEXT)
5098 hdi.mask |= HDI_TEXT;
5099 hdi.pszText = lpColumn->pszText;
5100 hdi.cchTextMax = lpColumn->cchTextMax;
5103 if (lpColumn->mask & LVCF_IMAGE)
5104 hdi.mask |= HDI_IMAGE;
5106 if (lpColumn->mask & LVCF_ORDER)
5107 hdi.mask |= HDI_ORDER;
5109 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5111 if (lpColumn->mask & LVCF_FMT)
5112 lpColumn->fmt = lpColumnInfo->fmt;
5114 if (lpColumn->mask & LVCF_WIDTH)
5115 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5117 if (lpColumn->mask & LVCF_IMAGE)
5118 lpColumn->iImage = hdi.iImage;
5120 if (lpColumn->mask & LVCF_ORDER)
5121 lpColumn->iOrder = hdi.iOrder;
5123 return TRUE;
5127 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5129 INT i;
5131 if (!lpiArray)
5132 return FALSE;
5134 /* FIXME: little hack */
5135 for (i = 0; i < iCount; i++)
5136 lpiArray[i] = i;
5138 return TRUE;
5141 /***
5142 * DESCRIPTION:
5143 * Retrieves the column width.
5145 * PARAMETER(S):
5146 * [I] infoPtr : valid pointer to the listview structure
5147 * [I] int : column index
5149 * RETURN:
5150 * SUCCESS : column width
5151 * FAILURE : zero
5153 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5155 INT nColumnWidth = 0;
5156 HDITEMW hdItem;
5158 TRACE("nColumn=%d\n", nColumn);
5160 /* we have a 'column' in LIST and REPORT mode only */
5161 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5163 case LVS_LIST:
5164 nColumnWidth = infoPtr->nItemWidth;
5165 break;
5166 case LVS_REPORT:
5167 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5168 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5169 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5171 * TODO: should we do the same in LVM_GETCOLUMN?
5173 hdItem.mask = HDI_WIDTH;
5174 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5176 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5177 return 0;
5179 nColumnWidth = hdItem.cxy;
5180 break;
5183 TRACE("nColumnWidth=%d\n", nColumnWidth);
5184 return nColumnWidth;
5187 /***
5188 * DESCRIPTION:
5189 * In list or report display mode, retrieves the number of items that can fit
5190 * vertically in the visible area. In icon or small icon display mode,
5191 * retrieves the total number of visible items.
5193 * PARAMETER(S):
5194 * [I] infoPtr : valid pointer to the listview structure
5196 * RETURN:
5197 * Number of fully visible items.
5199 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5201 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5203 case LVS_ICON:
5204 case LVS_SMALLICON:
5205 return infoPtr->nItemCount;
5206 case LVS_REPORT:
5207 return LISTVIEW_GetCountPerColumn(infoPtr);
5208 case LVS_LIST:
5209 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5211 assert(FALSE);
5212 return 0;
5215 /***
5216 * DESCRIPTION:
5217 * Retrieves an image list handle.
5219 * PARAMETER(S):
5220 * [I] infoPtr : valid pointer to the listview structure
5221 * [I] nImageList : image list identifier
5223 * RETURN:
5224 * SUCCESS : image list handle
5225 * FAILURE : NULL
5227 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5229 switch (nImageList)
5231 case LVSIL_NORMAL: return infoPtr->himlNormal;
5232 case LVSIL_SMALL: return infoPtr->himlSmall;
5233 case LVSIL_STATE: return infoPtr->himlState;
5235 return NULL;
5238 /* LISTVIEW_GetISearchString */
5240 /***
5241 * DESCRIPTION:
5242 * Retrieves item attributes.
5244 * PARAMETER(S):
5245 * [I] hwnd : window handle
5246 * [IO] lpLVItem : item info
5247 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5248 * if FALSE, then lpLVItem is a LPLVITEMA.
5250 * NOTE:
5251 * This is the internal 'GetItem' interface -- it tries to
5252 * be smart and avoid text copies, if possible, by modifying
5253 * lpLVItem->pszText to point to the text string. Please note
5254 * that this is not always possible (e.g. OWNERDATA), so on
5255 * entry you *must* supply valid values for pszText, and cchTextMax.
5256 * The only difference to the documented interface is that upon
5257 * return, you should use *only* the lpLVItem->pszText, rather than
5258 * the buffer pointer you provided on input. Most code already does
5259 * that, so it's not a problem.
5260 * For the two cases when the text must be copied (that is,
5261 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5263 * RETURN:
5264 * SUCCESS : TRUE
5265 * FAILURE : FALSE
5267 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5269 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5270 NMLVDISPINFOW dispInfo;
5271 ITEM_INFO *lpItem;
5272 ITEMHDR* pItemHdr;
5273 HDPA hdpaSubItems;
5274 INT isubitem;
5276 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5278 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5279 return FALSE;
5281 if (lpLVItem->mask == 0) return TRUE;
5283 /* make a local copy */
5284 isubitem = lpLVItem->iSubItem;
5286 /* a quick optimization if all we're asked is the focus state
5287 * these queries are worth optimising since they are common,
5288 * and can be answered in constant time, without the heavy accesses */
5289 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5290 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5292 lpLVItem->state = 0;
5293 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5294 lpLVItem->state |= LVIS_FOCUSED;
5295 return TRUE;
5298 ZeroMemory(&dispInfo, sizeof(dispInfo));
5300 /* if the app stores all the data, handle it separately */
5301 if (infoPtr->dwStyle & LVS_OWNERDATA)
5303 dispInfo.item.state = 0;
5305 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5306 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5308 /* NOTE: copy only fields which we _know_ are initialized, some apps
5309 * depend on the uninitialized fields being 0 */
5310 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5311 dispInfo.item.iItem = lpLVItem->iItem;
5312 dispInfo.item.iSubItem = isubitem;
5313 if (lpLVItem->mask & LVIF_TEXT)
5315 dispInfo.item.pszText = lpLVItem->pszText;
5316 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5318 if (lpLVItem->mask & LVIF_STATE)
5319 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5320 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5321 dispInfo.item.stateMask = lpLVItem->stateMask;
5322 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5324 /* full size structure expected - _WIN32IE >= 0x560 */
5325 *lpLVItem = dispInfo.item;
5327 else if (lpLVItem->mask & LVIF_INDENT)
5329 /* indent member expected - _WIN32IE >= 0x300 */
5330 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5332 else
5334 /* minimal structure expected */
5335 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5337 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5340 /* make sure lParam is zeroed out */
5341 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5343 /* we store only a little state, so if we're not asked, we're done */
5344 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5346 /* if focus is handled by us, report it */
5347 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5349 lpLVItem->state &= ~LVIS_FOCUSED;
5350 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5351 lpLVItem->state |= LVIS_FOCUSED;
5354 /* and do the same for selection, if we handle it */
5355 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5357 lpLVItem->state &= ~LVIS_SELECTED;
5358 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5359 lpLVItem->state |= LVIS_SELECTED;
5362 return TRUE;
5365 /* find the item and subitem structures before we proceed */
5366 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5367 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5368 assert (lpItem);
5370 if (isubitem)
5372 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5373 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5374 if (!lpSubItem)
5376 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5377 isubitem = 0;
5380 else
5381 pItemHdr = &lpItem->hdr;
5383 /* Do we need to query the state from the app? */
5384 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5386 dispInfo.item.mask |= LVIF_STATE;
5387 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5390 /* Do we need to enquire about the image? */
5391 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5392 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5394 dispInfo.item.mask |= LVIF_IMAGE;
5395 dispInfo.item.iImage = I_IMAGECALLBACK;
5398 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5399 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5401 dispInfo.item.mask |= LVIF_TEXT;
5402 dispInfo.item.pszText = lpLVItem->pszText;
5403 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5404 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5405 *dispInfo.item.pszText = '\0';
5408 /* If we don't have all the requested info, query the application */
5409 if (dispInfo.item.mask != 0)
5411 dispInfo.item.iItem = lpLVItem->iItem;
5412 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5413 dispInfo.item.lParam = lpItem->lParam;
5414 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5415 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5418 /* we should not store values for subitems */
5419 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5421 /* Now, handle the iImage field */
5422 if (dispInfo.item.mask & LVIF_IMAGE)
5424 lpLVItem->iImage = dispInfo.item.iImage;
5425 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5426 pItemHdr->iImage = dispInfo.item.iImage;
5428 else if (lpLVItem->mask & LVIF_IMAGE)
5430 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5431 lpLVItem->iImage = pItemHdr->iImage;
5432 else
5433 lpLVItem->iImage = 0;
5436 /* The pszText field */
5437 if (dispInfo.item.mask & LVIF_TEXT)
5439 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5440 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5442 lpLVItem->pszText = dispInfo.item.pszText;
5444 else if (lpLVItem->mask & LVIF_TEXT)
5446 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5447 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5450 /* Next is the lParam field */
5451 if (dispInfo.item.mask & LVIF_PARAM)
5453 lpLVItem->lParam = dispInfo.item.lParam;
5454 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5455 lpItem->lParam = dispInfo.item.lParam;
5457 else if (lpLVItem->mask & LVIF_PARAM)
5458 lpLVItem->lParam = lpItem->lParam;
5460 /* if this is a subitem, we're done */
5461 if (isubitem) return TRUE;
5463 /* ... the state field (this one is different due to uCallbackmask) */
5464 if (lpLVItem->mask & LVIF_STATE)
5466 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5467 if (dispInfo.item.mask & LVIF_STATE)
5469 lpLVItem->state &= ~dispInfo.item.stateMask;
5470 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5472 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5474 lpLVItem->state &= ~LVIS_FOCUSED;
5475 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5476 lpLVItem->state |= LVIS_FOCUSED;
5478 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5480 lpLVItem->state &= ~LVIS_SELECTED;
5481 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5482 lpLVItem->state |= LVIS_SELECTED;
5486 /* and last, but not least, the indent field */
5487 if (lpLVItem->mask & LVIF_INDENT)
5488 lpLVItem->iIndent = lpItem->iIndent;
5490 return TRUE;
5493 /***
5494 * DESCRIPTION:
5495 * Retrieves item attributes.
5497 * PARAMETER(S):
5498 * [I] hwnd : window handle
5499 * [IO] lpLVItem : item info
5500 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5501 * if FALSE, then lpLVItem is a LPLVITEMA.
5503 * NOTE:
5504 * This is the external 'GetItem' interface -- it properly copies
5505 * the text in the provided buffer.
5507 * RETURN:
5508 * SUCCESS : TRUE
5509 * FAILURE : FALSE
5511 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5513 LPWSTR pszText;
5514 BOOL bResult;
5516 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5517 return FALSE;
5519 pszText = lpLVItem->pszText;
5520 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5521 if (bResult && lpLVItem->pszText != pszText)
5522 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5523 lpLVItem->pszText = pszText;
5525 return bResult;
5529 /***
5530 * DESCRIPTION:
5531 * Retrieves the position (upper-left) of the listview control item.
5532 * Note that for LVS_ICON style, the upper-left is that of the icon
5533 * and not the bounding box.
5535 * PARAMETER(S):
5536 * [I] infoPtr : valid pointer to the listview structure
5537 * [I] nItem : item index
5538 * [O] lpptPosition : coordinate information
5540 * RETURN:
5541 * SUCCESS : TRUE
5542 * FAILURE : FALSE
5544 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5546 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5547 POINT Origin;
5549 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5551 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5553 LISTVIEW_GetOrigin(infoPtr, &Origin);
5554 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5556 if (uView == LVS_ICON)
5558 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5559 lpptPosition->y += ICON_TOP_PADDING;
5561 lpptPosition->x += Origin.x;
5562 lpptPosition->y += Origin.y;
5564 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5565 return TRUE;
5569 /***
5570 * DESCRIPTION:
5571 * Retrieves the bounding rectangle for a listview control item.
5573 * PARAMETER(S):
5574 * [I] infoPtr : valid pointer to the listview structure
5575 * [I] nItem : item index
5576 * [IO] lprc : bounding rectangle coordinates
5577 * lprc->left specifies the portion of the item for which the bounding
5578 * rectangle will be retrieved.
5580 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5581 * including the icon and label.
5583 * * For LVS_ICON
5584 * * Experiment shows that native control returns:
5585 * * width = min (48, length of text line)
5586 * * .left = position.x - (width - iconsize.cx)/2
5587 * * .right = .left + width
5588 * * height = #lines of text * ntmHeight + icon height + 8
5589 * * .top = position.y - 2
5590 * * .bottom = .top + height
5591 * * separation between items .y = itemSpacing.cy - height
5592 * * .x = itemSpacing.cx - width
5593 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5595 * * For LVS_ICON
5596 * * Experiment shows that native control returns:
5597 * * width = iconSize.cx + 16
5598 * * .left = position.x - (width - iconsize.cx)/2
5599 * * .right = .left + width
5600 * * height = iconSize.cy + 4
5601 * * .top = position.y - 2
5602 * * .bottom = .top + height
5603 * * separation between items .y = itemSpacing.cy - height
5604 * * .x = itemSpacing.cx - width
5605 * LVIR_LABEL Returns the bounding rectangle of the item text.
5607 * * For LVS_ICON
5608 * * Experiment shows that native control returns:
5609 * * width = text length
5610 * * .left = position.x - width/2
5611 * * .right = .left + width
5612 * * height = ntmH * linecount + 2
5613 * * .top = position.y + iconSize.cy + 6
5614 * * .bottom = .top + height
5615 * * separation between items .y = itemSpacing.cy - height
5616 * * .x = itemSpacing.cx - width
5617 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5618 * rectangles, but excludes columns in report view.
5620 * RETURN:
5621 * SUCCESS : TRUE
5622 * FAILURE : FALSE
5624 * NOTES
5625 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5626 * upon whether the window has the focus currently and on whether the item
5627 * is the one with the focus. Ensure that the control's record of which
5628 * item has the focus agrees with the items' records.
5630 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5632 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5633 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5634 BOOL doLabel = TRUE, oversizedBox = FALSE;
5635 POINT Position, Origin;
5636 LVITEMW lvItem;
5638 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5640 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5642 LISTVIEW_GetOrigin(infoPtr, &Origin);
5643 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5645 /* Be smart and try to figure out the minimum we have to do */
5646 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5647 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5648 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5649 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5650 oversizedBox = TRUE;
5652 /* get what we need from the item before hand, so we make
5653 * only one request. This can speed up things, if data
5654 * is stored on the app side */
5655 lvItem.mask = 0;
5656 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5657 if (doLabel) lvItem.mask |= LVIF_TEXT;
5658 lvItem.iItem = nItem;
5659 lvItem.iSubItem = 0;
5660 lvItem.pszText = szDispText;
5661 lvItem.cchTextMax = DISP_TEXT_SIZE;
5662 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5663 /* we got the state already up, simulate it here, to avoid a reget */
5664 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5666 lvItem.mask |= LVIF_STATE;
5667 lvItem.stateMask = LVIS_FOCUSED;
5668 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5671 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5672 lprc->left = LVIR_BOUNDS;
5673 switch(lprc->left)
5675 case LVIR_ICON:
5676 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5677 break;
5679 case LVIR_LABEL:
5680 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5681 break;
5683 case LVIR_BOUNDS:
5684 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5685 break;
5687 case LVIR_SELECTBOUNDS:
5688 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5689 break;
5691 default:
5692 WARN("Unknown value: %d\n", lprc->left);
5693 return FALSE;
5696 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5698 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5700 return TRUE;
5703 /***
5704 * DESCRIPTION:
5705 * Retrieves the spacing between listview control items.
5707 * PARAMETER(S):
5708 * [I] infoPtr : valid pointer to the listview structure
5709 * [IO] lprc : rectangle to receive the output
5710 * on input, lprc->top = nSubItem
5711 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5713 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5714 * not only those of the first column.
5715 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5717 * RETURN:
5718 * TRUE: success
5719 * FALSE: failure
5721 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5723 POINT Position;
5724 LVITEMW lvItem;
5725 INT nColumn;
5727 if (!lprc) return FALSE;
5729 nColumn = lprc->top;
5731 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5732 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5733 if (lprc->top == 0)
5734 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5736 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5738 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5740 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5742 lvItem.mask = 0;
5743 lvItem.iItem = nItem;
5744 lvItem.iSubItem = nColumn;
5746 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5747 switch(lprc->left)
5749 case LVIR_ICON:
5750 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5751 break;
5753 case LVIR_LABEL:
5754 case LVIR_BOUNDS:
5755 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5756 break;
5758 default:
5759 ERR("Unknown bounds=%d\n", lprc->left);
5760 return FALSE;
5763 OffsetRect(lprc, Position.x, Position.y);
5764 return TRUE;
5768 /***
5769 * DESCRIPTION:
5770 * Retrieves the width of a label.
5772 * PARAMETER(S):
5773 * [I] infoPtr : valid pointer to the listview structure
5775 * RETURN:
5776 * SUCCESS : string width (in pixels)
5777 * FAILURE : zero
5779 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5781 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5782 LVITEMW lvItem;
5784 TRACE("(nItem=%d)\n", nItem);
5786 lvItem.mask = LVIF_TEXT;
5787 lvItem.iItem = nItem;
5788 lvItem.iSubItem = 0;
5789 lvItem.pszText = szDispText;
5790 lvItem.cchTextMax = DISP_TEXT_SIZE;
5791 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5793 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5796 /***
5797 * DESCRIPTION:
5798 * Retrieves the spacing between listview control items.
5800 * PARAMETER(S):
5801 * [I] infoPtr : valid pointer to the listview structure
5802 * [I] bSmall : flag for small or large icon
5804 * RETURN:
5805 * Horizontal + vertical spacing
5807 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5809 LONG lResult;
5811 if (!bSmall)
5813 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5815 else
5817 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5818 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5819 else
5820 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5822 return lResult;
5825 /***
5826 * DESCRIPTION:
5827 * Retrieves the state of a listview control item.
5829 * PARAMETER(S):
5830 * [I] infoPtr : valid pointer to the listview structure
5831 * [I] nItem : item index
5832 * [I] uMask : state mask
5834 * RETURN:
5835 * State specified by the mask.
5837 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5839 LVITEMW lvItem;
5841 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5843 lvItem.iItem = nItem;
5844 lvItem.iSubItem = 0;
5845 lvItem.mask = LVIF_STATE;
5846 lvItem.stateMask = uMask;
5847 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5849 return lvItem.state & uMask;
5852 /***
5853 * DESCRIPTION:
5854 * Retrieves the text of a listview control item or subitem.
5856 * PARAMETER(S):
5857 * [I] hwnd : window handle
5858 * [I] nItem : item index
5859 * [IO] lpLVItem : item information
5860 * [I] isW : TRUE if lpLVItem is Unicode
5862 * RETURN:
5863 * SUCCESS : string length
5864 * FAILURE : 0
5866 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5868 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5870 lpLVItem->mask = LVIF_TEXT;
5871 lpLVItem->iItem = nItem;
5872 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5874 return textlenT(lpLVItem->pszText, isW);
5877 /***
5878 * DESCRIPTION:
5879 * Searches for an item based on properties + relationships.
5881 * PARAMETER(S):
5882 * [I] infoPtr : valid pointer to the listview structure
5883 * [I] nItem : item index
5884 * [I] uFlags : relationship flag
5886 * RETURN:
5887 * SUCCESS : item index
5888 * FAILURE : -1
5890 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5892 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5893 UINT uMask = 0;
5894 LVFINDINFOW lvFindInfo;
5895 INT nCountPerColumn;
5896 INT nCountPerRow;
5897 INT i;
5899 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5900 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5902 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5904 if (uFlags & LVNI_CUT)
5905 uMask |= LVIS_CUT;
5907 if (uFlags & LVNI_DROPHILITED)
5908 uMask |= LVIS_DROPHILITED;
5910 if (uFlags & LVNI_FOCUSED)
5911 uMask |= LVIS_FOCUSED;
5913 if (uFlags & LVNI_SELECTED)
5914 uMask |= LVIS_SELECTED;
5916 /* if we're asked for the focused item, that's only one,
5917 * so it's worth optimizing */
5918 if (uFlags & LVNI_FOCUSED)
5920 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
5921 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5924 if (uFlags & LVNI_ABOVE)
5926 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5928 while (nItem >= 0)
5930 nItem--;
5931 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5932 return nItem;
5935 else
5937 /* Special case for autoarrange - move 'til the top of a list */
5938 if (is_autoarrange(infoPtr))
5940 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5941 while (nItem - nCountPerRow >= 0)
5943 nItem -= nCountPerRow;
5944 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5945 return nItem;
5947 return -1;
5949 lvFindInfo.flags = LVFI_NEARESTXY;
5950 lvFindInfo.vkDirection = VK_UP;
5951 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5952 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5954 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5955 return nItem;
5959 else if (uFlags & LVNI_BELOW)
5961 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5963 while (nItem < infoPtr->nItemCount)
5965 nItem++;
5966 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5967 return nItem;
5970 else
5972 /* Special case for autoarrange - move 'til the bottom of a list */
5973 if (is_autoarrange(infoPtr))
5975 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5976 while (nItem + nCountPerRow < infoPtr->nItemCount )
5978 nItem += nCountPerRow;
5979 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5980 return nItem;
5982 return -1;
5984 lvFindInfo.flags = LVFI_NEARESTXY;
5985 lvFindInfo.vkDirection = VK_DOWN;
5986 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5987 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5989 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5990 return nItem;
5994 else if (uFlags & LVNI_TOLEFT)
5996 if (uView == LVS_LIST)
5998 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5999 while (nItem - nCountPerColumn >= 0)
6001 nItem -= nCountPerColumn;
6002 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6003 return nItem;
6006 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6008 /* Special case for autoarrange - move 'ti the beginning of a row */
6009 if (is_autoarrange(infoPtr))
6011 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6012 while (nItem % nCountPerRow > 0)
6014 nItem --;
6015 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6016 return nItem;
6018 return -1;
6020 lvFindInfo.flags = LVFI_NEARESTXY;
6021 lvFindInfo.vkDirection = VK_LEFT;
6022 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6023 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6025 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6026 return nItem;
6030 else if (uFlags & LVNI_TORIGHT)
6032 if (uView == LVS_LIST)
6034 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6035 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6037 nItem += nCountPerColumn;
6038 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6039 return nItem;
6042 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6044 /* Special case for autoarrange - move 'til the end of a row */
6045 if (is_autoarrange(infoPtr))
6047 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6048 while (nItem % nCountPerRow < nCountPerRow - 1 )
6050 nItem ++;
6051 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6052 return nItem;
6054 return -1;
6056 lvFindInfo.flags = LVFI_NEARESTXY;
6057 lvFindInfo.vkDirection = VK_RIGHT;
6058 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6059 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6061 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6062 return nItem;
6066 else
6068 nItem++;
6070 /* search by index */
6071 for (i = nItem; i < infoPtr->nItemCount; i++)
6073 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6074 return i;
6078 return -1;
6081 /* LISTVIEW_GetNumberOfWorkAreas */
6083 /***
6084 * DESCRIPTION:
6085 * Retrieves the origin coordinates when in icon or small icon display mode.
6087 * PARAMETER(S):
6088 * [I] infoPtr : valid pointer to the listview structure
6089 * [O] lpptOrigin : coordinate information
6091 * RETURN:
6092 * None.
6094 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6096 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6097 INT nHorzPos = 0, nVertPos = 0;
6098 SCROLLINFO scrollInfo;
6100 scrollInfo.cbSize = sizeof(SCROLLINFO);
6101 scrollInfo.fMask = SIF_POS;
6103 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6104 nHorzPos = scrollInfo.nPos;
6105 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6106 nVertPos = scrollInfo.nPos;
6108 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6110 lpptOrigin->x = infoPtr->rcList.left;
6111 lpptOrigin->y = infoPtr->rcList.top;
6112 if (uView == LVS_LIST)
6113 nHorzPos *= infoPtr->nItemWidth;
6114 else if (uView == LVS_REPORT)
6115 nVertPos *= infoPtr->nItemHeight;
6117 lpptOrigin->x -= nHorzPos;
6118 lpptOrigin->y -= nVertPos;
6120 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6123 /***
6124 * DESCRIPTION:
6125 * Retrieves the width of a string.
6127 * PARAMETER(S):
6128 * [I] hwnd : window handle
6129 * [I] lpszText : text string to process
6130 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6132 * RETURN:
6133 * SUCCESS : string width (in pixels)
6134 * FAILURE : zero
6136 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6138 SIZE stringSize;
6140 stringSize.cx = 0;
6141 if (is_textT(lpszText, isW))
6143 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6144 HDC hdc = GetDC(infoPtr->hwndSelf);
6145 HFONT hOldFont = SelectObject(hdc, hFont);
6147 if (isW)
6148 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6149 else
6150 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6151 SelectObject(hdc, hOldFont);
6152 ReleaseDC(infoPtr->hwndSelf, hdc);
6154 return stringSize.cx;
6157 /***
6158 * DESCRIPTION:
6159 * Determines which listview item is located at the specified position.
6161 * PARAMETER(S):
6162 * [I] infoPtr : valid pointer to the listview structure
6163 * [IO] lpht : hit test information
6164 * [I] subitem : fill out iSubItem.
6165 * [I] select : return the index only if the hit selects the item
6167 * NOTE:
6168 * (mm 20001022): We must not allow iSubItem to be touched, for
6169 * an app might pass only a structure with space up to iItem!
6170 * (MS Office 97 does that for instance in the file open dialog)
6172 * RETURN:
6173 * SUCCESS : item index
6174 * FAILURE : -1
6176 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6178 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6179 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6180 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6181 POINT Origin, Position, opt;
6182 LVITEMW lvItem;
6183 ITERATOR i;
6184 INT iItem;
6186 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6188 lpht->flags = 0;
6189 lpht->iItem = -1;
6190 if (subitem) lpht->iSubItem = 0;
6192 if (infoPtr->rcList.left > lpht->pt.x)
6193 lpht->flags |= LVHT_TOLEFT;
6194 else if (infoPtr->rcList.right < lpht->pt.x)
6195 lpht->flags |= LVHT_TORIGHT;
6197 if (infoPtr->rcList.top > lpht->pt.y)
6198 lpht->flags |= LVHT_ABOVE;
6199 else if (infoPtr->rcList.bottom < lpht->pt.y)
6200 lpht->flags |= LVHT_BELOW;
6202 TRACE("lpht->flags=0x%x\n", lpht->flags);
6203 if (lpht->flags) return -1;
6205 lpht->flags |= LVHT_NOWHERE;
6207 LISTVIEW_GetOrigin(infoPtr, &Origin);
6209 /* first deal with the large items */
6210 rcSearch.left = lpht->pt.x;
6211 rcSearch.top = lpht->pt.y;
6212 rcSearch.right = rcSearch.left + 1;
6213 rcSearch.bottom = rcSearch.top + 1;
6215 iterator_frameditems(&i, infoPtr, &rcSearch);
6216 iterator_next(&i); /* go to first item in the sequence */
6217 iItem = i.nItem;
6218 iterator_destroy(&i);
6220 TRACE("lpht->iItem=%d\n", iItem);
6221 if (iItem == -1) return -1;
6223 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6224 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6225 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6226 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6227 lvItem.iItem = iItem;
6228 lvItem.iSubItem = 0;
6229 lvItem.pszText = szDispText;
6230 lvItem.cchTextMax = DISP_TEXT_SIZE;
6231 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6232 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6234 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6235 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6236 opt.x = lpht->pt.x - Position.x - Origin.x;
6237 opt.y = lpht->pt.y - Position.y - Origin.y;
6239 if (uView == LVS_REPORT)
6240 rcBounds = rcBox;
6241 else
6242 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6243 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6244 if (!PtInRect(&rcBounds, opt)) return -1;
6246 if (PtInRect(&rcIcon, opt))
6247 lpht->flags |= LVHT_ONITEMICON;
6248 else if (PtInRect(&rcLabel, opt))
6249 lpht->flags |= LVHT_ONITEMLABEL;
6250 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6251 lpht->flags |= LVHT_ONITEMSTATEICON;
6252 if (lpht->flags & LVHT_ONITEM)
6253 lpht->flags &= ~LVHT_NOWHERE;
6255 TRACE("lpht->flags=0x%x\n", lpht->flags);
6256 if (uView == LVS_REPORT && subitem)
6258 INT j;
6260 rcBounds.right = rcBounds.left;
6261 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6263 rcBounds.left = rcBounds.right;
6264 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6265 if (PtInRect(&rcBounds, opt))
6267 lpht->iSubItem = j;
6268 break;
6273 if (select && !(uView == LVS_REPORT &&
6274 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6275 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6277 if (uView == LVS_REPORT)
6279 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6280 UnionRect(&rcBounds, &rcBounds, &rcState);
6282 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6284 return lpht->iItem = iItem;
6288 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6289 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6290 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6291 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6292 their own sort proc. when sending LVM_SORTITEMS.
6294 /* Platform SDK:
6295 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6297 LVS_SORTXXX must be specified,
6298 LVS_OWNERDRAW is not set,
6299 <item>.pszText is not LPSTR_TEXTCALLBACK.
6301 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6302 are sorted based on item text..."
6304 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6306 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6307 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6308 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6310 /* if we're sorting descending, negate the return value */
6311 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6314 /***
6315 * DESCRIPTION:
6316 * Inserts a new item in the listview control.
6318 * PARAMETER(S):
6319 * [I] infoPtr : valid pointer to the listview structure
6320 * [I] lpLVItem : item information
6321 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6323 * RETURN:
6324 * SUCCESS : new item index
6325 * FAILURE : -1
6327 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6329 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6330 INT nItem;
6331 HDPA hdpaSubItems;
6332 NMLISTVIEW nmlv;
6333 ITEM_INFO *lpItem;
6334 BOOL is_sorted, has_changed;
6335 LVITEMW item;
6336 HWND hwndSelf = infoPtr->hwndSelf;
6338 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6340 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6342 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6343 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6345 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6347 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6349 /* insert item in listview control data structure */
6350 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6351 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6353 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6354 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6356 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6358 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6359 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6360 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6361 if (nItem == -1) goto fail;
6362 infoPtr->nItemCount++;
6364 /* shift indices first so they don't get tangled */
6365 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6367 /* set the item attributes */
6368 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6370 /* full size structure expected - _WIN32IE >= 0x560 */
6371 item = *lpLVItem;
6373 else if (lpLVItem->mask & LVIF_INDENT)
6375 /* indent member expected - _WIN32IE >= 0x300 */
6376 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6378 else
6380 /* minimal structure expected */
6381 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6383 item.iItem = nItem;
6384 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6386 item.mask |= LVIF_STATE;
6387 item.stateMask |= LVIS_STATEIMAGEMASK;
6388 item.state &= ~LVIS_STATEIMAGEMASK;
6389 item.state |= INDEXTOSTATEIMAGEMASK(1);
6391 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6393 /* if we're sorted, sort the list, and update the index */
6394 if (is_sorted)
6396 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6397 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6398 assert(nItem != -1);
6401 /* make room for the position, if we are in the right mode */
6402 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6404 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6405 goto undo;
6406 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6408 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6409 goto undo;
6413 /* send LVN_INSERTITEM notification */
6414 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6415 nmlv.iItem = nItem;
6416 nmlv.lParam = lpItem->lParam;
6417 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6418 if (!IsWindow(hwndSelf))
6419 return -1;
6421 /* align items (set position of each item) */
6422 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6424 POINT pt;
6426 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6427 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6428 else
6429 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6431 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6434 /* now is the invalidation fun */
6435 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6436 return nItem;
6438 undo:
6439 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6440 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6441 infoPtr->nItemCount--;
6442 fail:
6443 DPA_DeletePtr(hdpaSubItems, 0);
6444 DPA_Destroy (hdpaSubItems);
6445 Free (lpItem);
6446 return -1;
6449 /***
6450 * DESCRIPTION:
6451 * Redraws a range of items.
6453 * PARAMETER(S):
6454 * [I] infoPtr : valid pointer to the listview structure
6455 * [I] nFirst : first item
6456 * [I] nLast : last item
6458 * RETURN:
6459 * SUCCESS : TRUE
6460 * FAILURE : FALSE
6462 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6464 INT i;
6466 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6467 max(nFirst, nLast) >= infoPtr->nItemCount)
6468 return FALSE;
6470 for (i = nFirst; i <= nLast; i++)
6471 LISTVIEW_InvalidateItem(infoPtr, i);
6473 return TRUE;
6476 /***
6477 * DESCRIPTION:
6478 * Scroll the content of a listview.
6480 * PARAMETER(S):
6481 * [I] infoPtr : valid pointer to the listview structure
6482 * [I] dx : horizontal scroll amount in pixels
6483 * [I] dy : vertical scroll amount in pixels
6485 * RETURN:
6486 * SUCCESS : TRUE
6487 * FAILURE : FALSE
6489 * COMMENTS:
6490 * If the control is in report mode (LVS_REPORT) the control can
6491 * be scrolled only in line increments. "dy" will be rounded to the
6492 * nearest number of pixels that are a whole line. Ex: if line height
6493 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6494 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6496 * For: (per experimentaion with native control and CSpy ListView)
6497 * LVS_ICON dy=1 = 1 pixel (vertical only)
6498 * dx ignored
6499 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6500 * dx ignored
6501 * LVS_LIST dx=1 = 1 column (horizontal only)
6502 * but will only scroll 1 column per message
6503 * no matter what the value.
6504 * dy must be 0 or FALSE returned.
6505 * LVS_REPORT dx=1 = 1 pixel
6506 * dy= see above
6509 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6511 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6512 case LVS_REPORT:
6513 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6514 dy /= infoPtr->nItemHeight;
6515 break;
6516 case LVS_LIST:
6517 if (dy != 0) return FALSE;
6518 break;
6519 default: /* icon */
6520 dx = 0;
6521 break;
6524 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6525 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6527 return TRUE;
6530 /***
6531 * DESCRIPTION:
6532 * Sets the background color.
6534 * PARAMETER(S):
6535 * [I] infoPtr : valid pointer to the listview structure
6536 * [I] clrBk : background color
6538 * RETURN:
6539 * SUCCESS : TRUE
6540 * FAILURE : FALSE
6542 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6544 TRACE("(clrBk=%x)\n", clrBk);
6546 if(infoPtr->clrBk != clrBk) {
6547 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6548 infoPtr->clrBk = clrBk;
6549 if (clrBk == CLR_NONE)
6550 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6551 else
6552 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6553 LISTVIEW_InvalidateList(infoPtr);
6556 return TRUE;
6559 /* LISTVIEW_SetBkImage */
6561 /*** Helper for {Insert,Set}ColumnT *only* */
6562 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6563 const LVCOLUMNW *lpColumn, BOOL isW)
6565 if (lpColumn->mask & LVCF_FMT)
6567 /* format member is valid */
6568 lphdi->mask |= HDI_FORMAT;
6570 /* set text alignment (leftmost column must be left-aligned) */
6571 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6572 lphdi->fmt |= HDF_LEFT;
6573 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6574 lphdi->fmt |= HDF_RIGHT;
6575 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6576 lphdi->fmt |= HDF_CENTER;
6578 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6579 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6581 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6583 lphdi->fmt |= HDF_IMAGE;
6584 lphdi->iImage = I_IMAGECALLBACK;
6588 if (lpColumn->mask & LVCF_WIDTH)
6590 lphdi->mask |= HDI_WIDTH;
6591 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6593 /* make it fill the remainder of the controls width */
6594 RECT rcHeader;
6595 INT item_index;
6597 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6599 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6600 lphdi->cxy += rcHeader.right - rcHeader.left;
6603 /* retrieve the layout of the header */
6604 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6605 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6607 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6609 else
6610 lphdi->cxy = lpColumn->cx;
6613 if (lpColumn->mask & LVCF_TEXT)
6615 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6616 lphdi->fmt |= HDF_STRING;
6617 lphdi->pszText = lpColumn->pszText;
6618 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6621 if (lpColumn->mask & LVCF_IMAGE)
6623 lphdi->mask |= HDI_IMAGE;
6624 lphdi->iImage = lpColumn->iImage;
6627 if (lpColumn->mask & LVCF_ORDER)
6629 lphdi->mask |= HDI_ORDER;
6630 lphdi->iOrder = lpColumn->iOrder;
6635 /***
6636 * DESCRIPTION:
6637 * Inserts a new column.
6639 * PARAMETER(S):
6640 * [I] infoPtr : valid pointer to the listview structure
6641 * [I] nColumn : column index
6642 * [I] lpColumn : column information
6643 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6645 * RETURN:
6646 * SUCCESS : new column index
6647 * FAILURE : -1
6649 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6650 const LVCOLUMNW *lpColumn, BOOL isW)
6652 COLUMN_INFO *lpColumnInfo;
6653 INT nNewColumn;
6654 HDITEMW hdi;
6656 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6658 if (!lpColumn || nColumn < 0) return -1;
6659 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6661 ZeroMemory(&hdi, sizeof(HDITEMW));
6662 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6665 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6666 * (can be seen in SPY) otherwise column never gets added.
6668 if (!(lpColumn->mask & LVCF_WIDTH)) {
6669 hdi.mask |= HDI_WIDTH;
6670 hdi.cxy = 10;
6674 * when the iSubItem is available Windows copies it to the header lParam. It seems
6675 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6677 if (lpColumn->mask & LVCF_SUBITEM)
6679 hdi.mask |= HDI_LPARAM;
6680 hdi.lParam = lpColumn->iSubItem;
6683 /* insert item in header control */
6684 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6685 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6686 (WPARAM)nColumn, (LPARAM)&hdi);
6687 if (nNewColumn == -1) return -1;
6688 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6690 /* create our own column info */
6691 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6692 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6694 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6695 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6697 /* now we have to actually adjust the data */
6698 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6700 SUBITEM_INFO *lpSubItem;
6701 HDPA hdpaSubItems;
6702 INT nItem, i;
6704 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6706 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6707 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6709 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6710 if (lpSubItem->iSubItem >= nNewColumn)
6711 lpSubItem->iSubItem++;
6716 /* make space for the new column */
6717 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6718 LISTVIEW_UpdateItemSize(infoPtr);
6720 return nNewColumn;
6722 fail:
6723 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6724 if (lpColumnInfo)
6726 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6727 Free(lpColumnInfo);
6729 return -1;
6732 /***
6733 * DESCRIPTION:
6734 * Sets the attributes of a header item.
6736 * PARAMETER(S):
6737 * [I] infoPtr : valid pointer to the listview structure
6738 * [I] nColumn : column index
6739 * [I] lpColumn : column attributes
6740 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6742 * RETURN:
6743 * SUCCESS : TRUE
6744 * FAILURE : FALSE
6746 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6747 const LVCOLUMNW *lpColumn, BOOL isW)
6749 HDITEMW hdi, hdiget;
6750 BOOL bResult;
6752 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6754 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6756 ZeroMemory(&hdi, sizeof(HDITEMW));
6757 if (lpColumn->mask & LVCF_FMT)
6759 hdi.mask |= HDI_FORMAT;
6760 hdiget.mask = HDI_FORMAT;
6761 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6762 hdi.fmt = hdiget.fmt & HDF_STRING;
6764 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6766 /* set header item attributes */
6767 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6768 if (!bResult) return FALSE;
6770 if (lpColumn->mask & LVCF_FMT)
6772 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6773 int oldFmt = lpColumnInfo->fmt;
6775 lpColumnInfo->fmt = lpColumn->fmt;
6776 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6778 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6779 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6783 return TRUE;
6786 /***
6787 * DESCRIPTION:
6788 * Sets the column order array
6790 * PARAMETERS:
6791 * [I] infoPtr : valid pointer to the listview structure
6792 * [I] iCount : number of elements in column order array
6793 * [I] lpiArray : pointer to column order array
6795 * RETURN:
6796 * SUCCESS : TRUE
6797 * FAILURE : FALSE
6799 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6801 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6803 if (!lpiArray)
6804 return FALSE;
6806 return TRUE;
6810 /***
6811 * DESCRIPTION:
6812 * Sets the width of a column
6814 * PARAMETERS:
6815 * [I] infoPtr : valid pointer to the listview structure
6816 * [I] nColumn : column index
6817 * [I] cx : column width
6819 * RETURN:
6820 * SUCCESS : TRUE
6821 * FAILURE : FALSE
6823 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6825 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6826 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6827 INT max_cx = 0;
6828 HDITEMW hdi;
6830 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6832 /* set column width only if in report or list mode */
6833 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6835 /* take care of invalid cx values */
6836 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6837 else if (uView == LVS_LIST && cx < 1) return FALSE;
6839 /* resize all columns if in LVS_LIST mode */
6840 if(uView == LVS_LIST)
6842 infoPtr->nItemWidth = cx;
6843 LISTVIEW_InvalidateList(infoPtr);
6844 return TRUE;
6847 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6849 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6851 INT nLabelWidth;
6852 LVITEMW lvItem;
6854 lvItem.mask = LVIF_TEXT;
6855 lvItem.iItem = 0;
6856 lvItem.iSubItem = nColumn;
6857 lvItem.pszText = szDispText;
6858 lvItem.cchTextMax = DISP_TEXT_SIZE;
6859 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6861 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6862 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6863 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6865 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6866 max_cx += infoPtr->iconSize.cx;
6867 max_cx += TRAILING_LABEL_PADDING;
6870 /* autosize based on listview items width */
6871 if(cx == LVSCW_AUTOSIZE)
6872 cx = max_cx;
6873 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6875 /* if iCol is the last column make it fill the remainder of the controls width */
6876 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6878 RECT rcHeader;
6879 POINT Origin;
6881 LISTVIEW_GetOrigin(infoPtr, &Origin);
6882 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6884 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6886 else
6888 /* Despite what the MS docs say, if this is not the last
6889 column, then MS resizes the column to the width of the
6890 largest text string in the column, including headers
6891 and items. This is different from LVSCW_AUTOSIZE in that
6892 LVSCW_AUTOSIZE ignores the header string length. */
6893 cx = 0;
6895 /* retrieve header text */
6896 hdi.mask = HDI_TEXT;
6897 hdi.cchTextMax = DISP_TEXT_SIZE;
6898 hdi.pszText = szDispText;
6899 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
6901 HDC hdc = GetDC(infoPtr->hwndSelf);
6902 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6903 SIZE size;
6905 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6906 cx = size.cx + TRAILING_HEADER_PADDING;
6907 /* FIXME: Take into account the header image, if one is present */
6908 SelectObject(hdc, old_font);
6909 ReleaseDC(infoPtr->hwndSelf, hdc);
6911 cx = max (cx, max_cx);
6915 if (cx < 0) return FALSE;
6917 /* call header to update the column change */
6918 hdi.mask = HDI_WIDTH;
6919 hdi.cxy = cx;
6920 TRACE("hdi.cxy=%d\n", hdi.cxy);
6921 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
6924 /***
6925 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6928 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
6930 HDC hdc_wnd, hdc;
6931 HBITMAP hbm_im, hbm_mask, hbm_orig;
6932 RECT rc;
6933 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6934 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6935 HIMAGELIST himl;
6937 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6938 ILC_COLOR | ILC_MASK, 2, 2);
6939 hdc_wnd = GetDC(infoPtr->hwndSelf);
6940 hdc = CreateCompatibleDC(hdc_wnd);
6941 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6942 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6943 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6945 rc.left = rc.top = 0;
6946 rc.right = GetSystemMetrics(SM_CXSMICON);
6947 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6949 hbm_orig = SelectObject(hdc, hbm_mask);
6950 FillRect(hdc, &rc, hbr_white);
6951 InflateRect(&rc, -3, -3);
6952 FillRect(hdc, &rc, hbr_black);
6954 SelectObject(hdc, hbm_im);
6955 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6956 SelectObject(hdc, hbm_orig);
6957 ImageList_Add(himl, hbm_im, hbm_mask);
6959 SelectObject(hdc, hbm_im);
6960 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6961 SelectObject(hdc, hbm_orig);
6962 ImageList_Add(himl, hbm_im, hbm_mask);
6964 DeleteObject(hbm_mask);
6965 DeleteObject(hbm_im);
6966 DeleteDC(hdc);
6968 return himl;
6971 /***
6972 * DESCRIPTION:
6973 * Sets the extended listview style.
6975 * PARAMETERS:
6976 * [I] infoPtr : valid pointer to the listview structure
6977 * [I] dwMask : mask
6978 * [I] dwStyle : style
6980 * RETURN:
6981 * SUCCESS : previous style
6982 * FAILURE : 0
6984 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6986 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6988 /* set new style */
6989 if (dwMask)
6990 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6991 else
6992 infoPtr->dwLvExStyle = dwStyle;
6994 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6996 HIMAGELIST himl = 0;
6997 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6999 LVITEMW item;
7000 item.mask = LVIF_STATE;
7001 item.stateMask = LVIS_STATEIMAGEMASK;
7002 item.state = INDEXTOSTATEIMAGEMASK(1);
7003 LISTVIEW_SetItemState(infoPtr, -1, &item);
7005 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7007 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7010 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_HEADERDRAGDROP)
7012 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7013 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7014 dwStyle |= HDS_DRAGDROP;
7015 else
7016 dwStyle &= ~HDS_DRAGDROP;
7017 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7020 return dwOldStyle;
7023 /***
7024 * DESCRIPTION:
7025 * Sets the new hot cursor used during hot tracking and hover selection.
7027 * PARAMETER(S):
7028 * [I] infoPtr : valid pointer to the listview structure
7029 * [I] hCursor : the new hot cursor handle
7031 * RETURN:
7032 * Returns the previous hot cursor
7034 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7036 HCURSOR oldCursor = infoPtr->hHotCursor;
7038 infoPtr->hHotCursor = hCursor;
7040 return oldCursor;
7044 /***
7045 * DESCRIPTION:
7046 * Sets the hot item index.
7048 * PARAMETERS:
7049 * [I] infoPtr : valid pointer to the listview structure
7050 * [I] iIndex : index
7052 * RETURN:
7053 * SUCCESS : previous hot item index
7054 * FAILURE : -1 (no hot item)
7056 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7058 INT iOldIndex = infoPtr->nHotItem;
7060 infoPtr->nHotItem = iIndex;
7062 return iOldIndex;
7066 /***
7067 * DESCRIPTION:
7068 * Sets the amount of time the cursor must hover over an item before it is selected.
7070 * PARAMETER(S):
7071 * [I] infoPtr : valid pointer to the listview structure
7072 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7074 * RETURN:
7075 * Returns the previous hover time
7077 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7079 DWORD oldHoverTime = infoPtr->dwHoverTime;
7081 infoPtr->dwHoverTime = dwHoverTime;
7083 return oldHoverTime;
7086 /***
7087 * DESCRIPTION:
7088 * Sets spacing for icons of LVS_ICON style.
7090 * PARAMETER(S):
7091 * [I] infoPtr : valid pointer to the listview structure
7092 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7093 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7095 * RETURN:
7096 * MAKELONG(oldcx, oldcy)
7098 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7100 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7101 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7103 TRACE("requested=(%d,%d)\n", cx, cy);
7105 /* this is supported only for LVS_ICON style */
7106 if (uView != LVS_ICON) return oldspacing;
7108 /* set to defaults, if instructed to */
7109 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7110 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7112 /* if 0 then compute width
7113 * FIXME: Should scan each item and determine max width of
7114 * icon or label, then make that the width */
7115 if (cx == 0)
7116 cx = infoPtr->iconSpacing.cx;
7118 /* if 0 then compute height */
7119 if (cy == 0)
7120 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7121 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7124 infoPtr->iconSpacing.cx = cx;
7125 infoPtr->iconSpacing.cy = cy;
7127 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7128 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7129 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7130 infoPtr->ntmHeight);
7132 /* these depend on the iconSpacing */
7133 LISTVIEW_UpdateItemSize(infoPtr);
7135 return oldspacing;
7138 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7140 INT cx, cy;
7142 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7144 size->cx = cx;
7145 size->cy = cy;
7147 else
7149 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7150 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7154 /***
7155 * DESCRIPTION:
7156 * Sets image lists.
7158 * PARAMETER(S):
7159 * [I] infoPtr : valid pointer to the listview structure
7160 * [I] nType : image list type
7161 * [I] himl : image list handle
7163 * RETURN:
7164 * SUCCESS : old image list
7165 * FAILURE : NULL
7167 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7169 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7170 INT oldHeight = infoPtr->nItemHeight;
7171 HIMAGELIST himlOld = 0;
7173 TRACE("(nType=%d, himl=%p\n", nType, himl);
7175 switch (nType)
7177 case LVSIL_NORMAL:
7178 himlOld = infoPtr->himlNormal;
7179 infoPtr->himlNormal = himl;
7180 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7181 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7182 break;
7184 case LVSIL_SMALL:
7185 himlOld = infoPtr->himlSmall;
7186 infoPtr->himlSmall = himl;
7187 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7188 break;
7190 case LVSIL_STATE:
7191 himlOld = infoPtr->himlState;
7192 infoPtr->himlState = himl;
7193 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7194 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7195 break;
7197 default:
7198 ERR("Unknown icon type=%d\n", nType);
7199 return NULL;
7202 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7203 if (infoPtr->nItemHeight != oldHeight)
7204 LISTVIEW_UpdateScroll(infoPtr);
7206 return himlOld;
7209 /***
7210 * DESCRIPTION:
7211 * Preallocates memory (does *not* set the actual count of items !)
7213 * PARAMETER(S):
7214 * [I] infoPtr : valid pointer to the listview structure
7215 * [I] nItems : item count (projected number of items to allocate)
7216 * [I] dwFlags : update flags
7218 * RETURN:
7219 * SUCCESS : TRUE
7220 * FAILURE : FALSE
7222 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7224 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7226 if (infoPtr->dwStyle & LVS_OWNERDATA)
7228 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7229 INT nOldCount = infoPtr->nItemCount;
7231 if (nItems < nOldCount)
7233 RANGE range = { nItems, nOldCount };
7234 ranges_del(infoPtr->selectionRanges, range);
7235 if (infoPtr->nFocusedItem >= nItems)
7237 infoPtr->nFocusedItem = -1;
7238 SetRectEmpty(&infoPtr->rcFocus);
7242 infoPtr->nItemCount = nItems;
7243 LISTVIEW_UpdateScroll(infoPtr);
7245 /* the flags are valid only in ownerdata report and list modes */
7246 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7248 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7249 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7251 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7252 LISTVIEW_InvalidateList(infoPtr);
7253 else
7255 INT nFrom, nTo;
7256 POINT Origin;
7257 RECT rcErase;
7259 LISTVIEW_GetOrigin(infoPtr, &Origin);
7260 nFrom = min(nOldCount, nItems);
7261 nTo = max(nOldCount, nItems);
7263 if (uView == LVS_REPORT)
7265 rcErase.left = 0;
7266 rcErase.top = nFrom * infoPtr->nItemHeight;
7267 rcErase.right = infoPtr->nItemWidth;
7268 rcErase.bottom = nTo * infoPtr->nItemHeight;
7269 OffsetRect(&rcErase, Origin.x, Origin.y);
7270 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7271 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7273 else /* LVS_LIST */
7275 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7277 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7278 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7279 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7280 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7281 OffsetRect(&rcErase, Origin.x, Origin.y);
7282 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7283 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7285 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7286 rcErase.top = 0;
7287 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7288 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7289 OffsetRect(&rcErase, Origin.x, Origin.y);
7290 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7291 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7295 else
7297 /* According to MSDN for non-LVS_OWNERDATA this is just
7298 * a performance issue. The control allocates its internal
7299 * data structures for the number of items specified. It
7300 * cuts down on the number of memory allocations. Therefore
7301 * we will just issue a WARN here
7303 WARN("for non-ownerdata performance option not implemented.\n");
7306 return TRUE;
7309 /***
7310 * DESCRIPTION:
7311 * Sets the position of an item.
7313 * PARAMETER(S):
7314 * [I] infoPtr : valid pointer to the listview structure
7315 * [I] nItem : item index
7316 * [I] pt : coordinate
7318 * RETURN:
7319 * SUCCESS : TRUE
7320 * FAILURE : FALSE
7322 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7324 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7325 POINT Origin;
7327 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7329 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7330 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7332 LISTVIEW_GetOrigin(infoPtr, &Origin);
7334 /* This point value seems to be an undocumented feature.
7335 * The best guess is that it means either at the origin,
7336 * or at true beginning of the list. I will assume the origin. */
7337 if ((pt.x == -1) && (pt.y == -1))
7338 pt = Origin;
7340 if (uView == LVS_ICON)
7342 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7343 pt.y -= ICON_TOP_PADDING;
7345 pt.x -= Origin.x;
7346 pt.y -= Origin.y;
7348 infoPtr->bAutoarrange = FALSE;
7350 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7353 /***
7354 * DESCRIPTION:
7355 * Sets the state of one or many items.
7357 * PARAMETER(S):
7358 * [I] infoPtr : valid pointer to the listview structure
7359 * [I] nItem : item index
7360 * [I] lpLVItem : item or subitem info
7362 * RETURN:
7363 * SUCCESS : TRUE
7364 * FAILURE : FALSE
7366 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7368 BOOL bResult = TRUE;
7369 LVITEMW lvItem;
7371 lvItem.iItem = nItem;
7372 lvItem.iSubItem = 0;
7373 lvItem.mask = LVIF_STATE;
7374 lvItem.state = lpLVItem->state;
7375 lvItem.stateMask = lpLVItem->stateMask;
7376 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7378 if (nItem == -1)
7380 /* apply to all items */
7381 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7382 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7384 else
7385 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7388 * Update selection mark
7390 * Investigation on windows 2k showed that selection mark was updated
7391 * whenever a new selection was made, but if the selected item was
7392 * unselected it was not updated.
7394 * we are probably still not 100% accurate, but this at least sets the
7395 * proper selection mark when it is needed
7398 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7399 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7401 int i;
7402 infoPtr->nSelectionMark = -1;
7403 for (i = 0; i < infoPtr->nItemCount; i++)
7405 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7407 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7409 infoPtr->nSelectionMark = i;
7410 break;
7413 else if (ranges_contain(infoPtr->selectionRanges, i))
7415 infoPtr->nSelectionMark = i;
7416 break;
7421 return bResult;
7424 /***
7425 * DESCRIPTION:
7426 * Sets the text of an item or subitem.
7428 * PARAMETER(S):
7429 * [I] hwnd : window handle
7430 * [I] nItem : item index
7431 * [I] lpLVItem : item or subitem info
7432 * [I] isW : TRUE if input is Unicode
7434 * RETURN:
7435 * SUCCESS : TRUE
7436 * FAILURE : FALSE
7438 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7440 LVITEMW lvItem;
7442 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7444 lvItem.iItem = nItem;
7445 lvItem.iSubItem = lpLVItem->iSubItem;
7446 lvItem.mask = LVIF_TEXT;
7447 lvItem.pszText = lpLVItem->pszText;
7448 lvItem.cchTextMax = lpLVItem->cchTextMax;
7450 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7452 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7455 /***
7456 * DESCRIPTION:
7457 * Set item index that marks the start of a multiple selection.
7459 * PARAMETER(S):
7460 * [I] infoPtr : valid pointer to the listview structure
7461 * [I] nIndex : index
7463 * RETURN:
7464 * Index number or -1 if there is no selection mark.
7466 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7468 INT nOldIndex = infoPtr->nSelectionMark;
7470 TRACE("(nIndex=%d)\n", nIndex);
7472 infoPtr->nSelectionMark = nIndex;
7474 return nOldIndex;
7477 /***
7478 * DESCRIPTION:
7479 * Sets the text background color.
7481 * PARAMETER(S):
7482 * [I] infoPtr : valid pointer to the listview structure
7483 * [I] clrTextBk : text background color
7485 * RETURN:
7486 * SUCCESS : TRUE
7487 * FAILURE : FALSE
7489 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7491 TRACE("(clrTextBk=%x)\n", clrTextBk);
7493 if (infoPtr->clrTextBk != clrTextBk)
7495 infoPtr->clrTextBk = clrTextBk;
7496 LISTVIEW_InvalidateList(infoPtr);
7499 return TRUE;
7502 /***
7503 * DESCRIPTION:
7504 * Sets the text foreground color.
7506 * PARAMETER(S):
7507 * [I] infoPtr : valid pointer to the listview structure
7508 * [I] clrText : text color
7510 * RETURN:
7511 * SUCCESS : TRUE
7512 * FAILURE : FALSE
7514 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7516 TRACE("(clrText=%x)\n", clrText);
7518 if (infoPtr->clrText != clrText)
7520 infoPtr->clrText = clrText;
7521 LISTVIEW_InvalidateList(infoPtr);
7524 return TRUE;
7527 /***
7528 * DESCRIPTION:
7529 * Determines which listview item is located at the specified position.
7531 * PARAMETER(S):
7532 * [I] infoPtr : valid pointer to the listview structure
7533 * [I] hwndNewToolTip : handle to new ToolTip
7535 * RETURN:
7536 * old tool tip
7538 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7540 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7541 infoPtr->hwndToolTip = hwndNewToolTip;
7542 return hwndOldToolTip;
7546 * DESCRIPTION:
7547 * sets the Unicode character format flag for the control
7548 * PARAMETER(S):
7549 * [I] infoPtr :valid pointer to the listview structure
7550 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7552 * RETURN:
7553 * Old Unicode Format
7555 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7557 BOOL rc = infoPtr->notifyFormat;
7558 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7559 return rc;
7562 /* LISTVIEW_SetWorkAreas */
7564 /***
7565 * DESCRIPTION:
7566 * Callback internally used by LISTVIEW_SortItems()
7568 * PARAMETER(S):
7569 * [I] first : pointer to first ITEM_INFO to compare
7570 * [I] second : pointer to second ITEM_INFO to compare
7571 * [I] lParam : HWND of control
7573 * RETURN:
7574 * if first comes before second : negative
7575 * if first comes after second : positive
7576 * if first and second are equivalent : zero
7578 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7580 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7581 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7582 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7584 /* Forward the call to the client defined callback */
7585 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7588 /***
7589 * DESCRIPTION:
7590 * Sorts the listview items.
7592 * PARAMETER(S):
7593 * [I] infoPtr : valid pointer to the listview structure
7594 * [I] pfnCompare : application-defined value
7595 * [I] lParamSort : pointer to comparision callback
7597 * RETURN:
7598 * SUCCESS : TRUE
7599 * FAILURE : FALSE
7601 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7603 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7604 HDPA hdpaSubItems;
7605 ITEM_INFO *lpItem;
7606 LPVOID selectionMarkItem;
7607 LVITEMW item;
7608 int i;
7610 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7612 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7614 if (!pfnCompare) return FALSE;
7615 if (!infoPtr->hdpaItems) return FALSE;
7617 /* if there are 0 or 1 items, there is no need to sort */
7618 if (infoPtr->nItemCount < 2) return TRUE;
7620 if (infoPtr->nFocusedItem >= 0)
7622 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7623 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7624 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7626 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7627 /* clear the lpItem->state for non-selected ones */
7628 /* remove the selection ranges */
7630 infoPtr->pfnCompare = pfnCompare;
7631 infoPtr->lParamSort = lParamSort;
7632 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7634 /* Adjust selections and indices so that they are the way they should
7635 * be after the sort (otherwise, the list items move around, but
7636 * whatever is at the item's previous original position will be
7637 * selected instead)
7639 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7640 for (i=0; i < infoPtr->nItemCount; i++)
7642 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7643 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7645 if (lpItem->state & LVIS_SELECTED)
7647 item.state = LVIS_SELECTED;
7648 item.stateMask = LVIS_SELECTED;
7649 LISTVIEW_SetItemState(infoPtr, i, &item);
7651 if (lpItem->state & LVIS_FOCUSED)
7653 infoPtr->nFocusedItem = i;
7654 lpItem->state &= ~LVIS_FOCUSED;
7657 if (selectionMarkItem != NULL)
7658 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7659 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7661 /* refresh the display */
7662 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7663 LISTVIEW_InvalidateList(infoPtr);
7665 return TRUE;
7668 /***
7669 * DESCRIPTION:
7670 * Update theme handle after a theme change.
7672 * PARAMETER(S):
7673 * [I] infoPtr : valid pointer to the listview structure
7675 * RETURN:
7676 * SUCCESS : 0
7677 * FAILURE : something else
7679 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7681 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7682 CloseThemeData(theme);
7683 OpenThemeData(infoPtr->hwndSelf, themeClass);
7684 return 0;
7687 /***
7688 * DESCRIPTION:
7689 * Updates an items or rearranges the listview control.
7691 * PARAMETER(S):
7692 * [I] infoPtr : valid pointer to the listview structure
7693 * [I] nItem : item index
7695 * RETURN:
7696 * SUCCESS : TRUE
7697 * FAILURE : FALSE
7699 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7701 TRACE("(nItem=%d)\n", nItem);
7703 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7705 /* rearrange with default alignment style */
7706 if (is_autoarrange(infoPtr))
7707 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7708 else
7709 LISTVIEW_InvalidateItem(infoPtr, nItem);
7711 return TRUE;
7714 /***
7715 * DESCRIPTION:
7716 * Draw the track line at the place defined in the infoPtr structure.
7717 * The line is drawn with a XOR pen so drawing the line for the second time
7718 * in the same place erases the line.
7720 * PARAMETER(S):
7721 * [I] infoPtr : valid pointer to the listview structure
7723 * RETURN:
7724 * SUCCESS : TRUE
7725 * FAILURE : FALSE
7727 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7729 HPEN hOldPen;
7730 HDC hdc;
7731 INT oldROP;
7733 if (infoPtr->xTrackLine == -1)
7734 return FALSE;
7736 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7737 return FALSE;
7738 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7739 oldROP = SetROP2(hdc, R2_XORPEN);
7740 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7741 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7742 SetROP2(hdc, oldROP);
7743 SelectObject(hdc, hOldPen);
7744 ReleaseDC(infoPtr->hwndSelf, hdc);
7745 return TRUE;
7748 /***
7749 * DESCRIPTION:
7750 * Called when an edit control should be displayed. This function is called after
7751 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7753 * PARAMETER(S):
7754 * [I] hwnd : Handle to the listview
7755 * [I] uMsg : WM_TIMER (ignored)
7756 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7757 * [I] dwTimer : The elapsed time (ignored)
7759 * RETURN:
7760 * None.
7762 static CALLBACK VOID LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7764 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7765 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7767 KillTimer(hwnd, idEvent);
7768 editItem->fEnabled = FALSE;
7769 /* check if the item is still selected */
7770 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7771 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7774 /***
7775 * DESCRIPTION:
7776 * Creates the listview control - the WM_NCCREATE phase.
7778 * PARAMETER(S):
7779 * [I] hwnd : window handle
7780 * [I] lpcs : the create parameters
7782 * RETURN:
7783 * Success: TRUE
7784 * Failure: FALSE
7786 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7788 LISTVIEW_INFO *infoPtr;
7789 LOGFONTW logFont;
7791 TRACE("(lpcs=%p)\n", lpcs);
7793 /* initialize info pointer */
7794 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7795 if (!infoPtr) return FALSE;
7797 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7799 infoPtr->hwndSelf = hwnd;
7800 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7801 /* determine the type of structures to use */
7802 infoPtr->hwndNotify = lpcs->hwndParent;
7803 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7805 /* initialize color information */
7806 infoPtr->clrBk = CLR_NONE;
7807 infoPtr->clrText = CLR_DEFAULT;
7808 infoPtr->clrTextBk = CLR_DEFAULT;
7809 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7811 /* set default values */
7812 infoPtr->nFocusedItem = -1;
7813 infoPtr->nSelectionMark = -1;
7814 infoPtr->nHotItem = -1;
7815 infoPtr->bRedraw = TRUE;
7816 infoPtr->bNoItemMetrics = TRUE;
7817 infoPtr->bDoChangeNotify = TRUE;
7818 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7819 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7820 infoPtr->nEditLabelItem = -1;
7821 infoPtr->dwHoverTime = -1; /* default system hover time */
7822 infoPtr->nMeasureItemHeight = 0;
7823 infoPtr->xTrackLine = -1; /* no track line */
7824 infoPtr->itemEdit.fEnabled = FALSE;
7826 /* get default font (icon title) */
7827 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7828 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7829 infoPtr->hFont = infoPtr->hDefaultFont;
7830 LISTVIEW_SaveTextMetrics(infoPtr);
7832 /* allocate memory for the data structure */
7833 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7834 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7835 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7836 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7837 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7838 return TRUE;
7840 fail:
7841 DestroyWindow(infoPtr->hwndHeader);
7842 ranges_destroy(infoPtr->selectionRanges);
7843 DPA_Destroy(infoPtr->hdpaItems);
7844 DPA_Destroy(infoPtr->hdpaPosX);
7845 DPA_Destroy(infoPtr->hdpaPosY);
7846 DPA_Destroy(infoPtr->hdpaColumns);
7847 Free(infoPtr);
7848 return FALSE;
7851 /***
7852 * DESCRIPTION:
7853 * Creates the listview control - the WM_CREATE phase. Most of the data is
7854 * already set up in LISTVIEW_NCCreate
7856 * PARAMETER(S):
7857 * [I] hwnd : window handle
7858 * [I] lpcs : the create parameters
7860 * RETURN:
7861 * Success: 0
7862 * Failure: -1
7864 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7866 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7867 UINT uView = lpcs->style & LVS_TYPEMASK;
7869 TRACE("(lpcs=%p)\n", lpcs);
7871 infoPtr->dwStyle = lpcs->style;
7872 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7873 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7875 /* create header */
7876 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7877 WS_CHILD | HDS_HORZ | HDS_FULLDRAG | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7878 0, 0, 0, 0, hwnd, NULL,
7879 lpcs->hInstance, NULL);
7880 if (!infoPtr->hwndHeader) return -1;
7882 /* set header unicode format */
7883 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7885 /* set header font */
7886 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7888 /* init item size to avoid division by 0 */
7889 LISTVIEW_UpdateItemSize (infoPtr);
7891 if (uView == LVS_REPORT)
7893 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7895 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7897 else
7899 /* set HDS_HIDDEN flag to hide the header bar */
7900 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7901 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7905 OpenThemeData(hwnd, themeClass);
7907 /* initialize the icon sizes */
7908 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7909 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7910 return 0;
7913 /***
7914 * DESCRIPTION:
7915 * Destroys the listview control.
7917 * PARAMETER(S):
7918 * [I] infoPtr : valid pointer to the listview structure
7920 * RETURN:
7921 * Success: 0
7922 * Failure: -1
7924 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
7926 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7927 CloseThemeData(theme);
7928 return 0;
7931 /***
7932 * DESCRIPTION:
7933 * Enables the listview control.
7935 * PARAMETER(S):
7936 * [I] infoPtr : valid pointer to the listview structure
7937 * [I] bEnable : specifies whether to enable or disable the window
7939 * RETURN:
7940 * SUCCESS : TRUE
7941 * FAILURE : FALSE
7943 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
7945 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
7946 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
7947 return TRUE;
7950 /***
7951 * DESCRIPTION:
7952 * Erases the background of the listview control.
7954 * PARAMETER(S):
7955 * [I] infoPtr : valid pointer to the listview structure
7956 * [I] hdc : device context handle
7958 * RETURN:
7959 * SUCCESS : TRUE
7960 * FAILURE : FALSE
7962 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
7964 RECT rc;
7966 TRACE("(hdc=%p)\n", hdc);
7968 if (!GetClipBox(hdc, &rc)) return FALSE;
7970 /* for double buffered controls we need to do this during refresh */
7971 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
7973 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7977 /***
7978 * DESCRIPTION:
7979 * Helper function for LISTVIEW_[HV]Scroll *only*.
7980 * Performs vertical/horizontal scrolling by a give amount.
7982 * PARAMETER(S):
7983 * [I] infoPtr : valid pointer to the listview structure
7984 * [I] dx : amount of horizontal scroll
7985 * [I] dy : amount of vertical scroll
7987 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7989 /* now we can scroll the list */
7990 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7991 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7992 /* if we have focus, adjust rect */
7993 OffsetRect(&infoPtr->rcFocus, dx, dy);
7994 UpdateWindow(infoPtr->hwndSelf);
7997 /***
7998 * DESCRIPTION:
7999 * Performs vertical scrolling.
8001 * PARAMETER(S):
8002 * [I] infoPtr : valid pointer to the listview structure
8003 * [I] nScrollCode : scroll code
8004 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8005 * [I] hScrollWnd : scrollbar control window handle
8007 * RETURN:
8008 * Zero
8010 * NOTES:
8011 * SB_LINEUP/SB_LINEDOWN:
8012 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8013 * for LVS_REPORT is 1 line
8014 * for LVS_LIST cannot occur
8017 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8018 INT nScrollDiff, HWND hScrollWnd)
8020 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8021 INT nOldScrollPos, nNewScrollPos;
8022 SCROLLINFO scrollInfo;
8023 BOOL is_an_icon;
8025 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8026 debugscrollcode(nScrollCode), nScrollDiff);
8028 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8030 scrollInfo.cbSize = sizeof(SCROLLINFO);
8031 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8033 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8035 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8037 nOldScrollPos = scrollInfo.nPos;
8038 switch (nScrollCode)
8040 case SB_INTERNAL:
8041 break;
8043 case SB_LINEUP:
8044 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8045 break;
8047 case SB_LINEDOWN:
8048 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8049 break;
8051 case SB_PAGEUP:
8052 nScrollDiff = -scrollInfo.nPage;
8053 break;
8055 case SB_PAGEDOWN:
8056 nScrollDiff = scrollInfo.nPage;
8057 break;
8059 case SB_THUMBPOSITION:
8060 case SB_THUMBTRACK:
8061 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8062 break;
8064 default:
8065 nScrollDiff = 0;
8068 /* quit right away if pos isn't changing */
8069 if (nScrollDiff == 0) return 0;
8071 /* calculate new position, and handle overflows */
8072 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8073 if (nScrollDiff > 0) {
8074 if (nNewScrollPos < nOldScrollPos ||
8075 nNewScrollPos > scrollInfo.nMax)
8076 nNewScrollPos = scrollInfo.nMax;
8077 } else {
8078 if (nNewScrollPos > nOldScrollPos ||
8079 nNewScrollPos < scrollInfo.nMin)
8080 nNewScrollPos = scrollInfo.nMin;
8083 /* set the new position, and reread in case it changed */
8084 scrollInfo.fMask = SIF_POS;
8085 scrollInfo.nPos = nNewScrollPos;
8086 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8088 /* carry on only if it really changed */
8089 if (nNewScrollPos == nOldScrollPos) return 0;
8091 /* now adjust to client coordinates */
8092 nScrollDiff = nOldScrollPos - nNewScrollPos;
8093 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8095 /* and scroll the window */
8096 scroll_list(infoPtr, 0, nScrollDiff);
8098 return 0;
8101 /***
8102 * DESCRIPTION:
8103 * Performs horizontal scrolling.
8105 * PARAMETER(S):
8106 * [I] infoPtr : valid pointer to the listview structure
8107 * [I] nScrollCode : scroll code
8108 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8109 * [I] hScrollWnd : scrollbar control window handle
8111 * RETURN:
8112 * Zero
8114 * NOTES:
8115 * SB_LINELEFT/SB_LINERIGHT:
8116 * for LVS_ICON, LVS_SMALLICON 1 pixel
8117 * for LVS_REPORT is 1 pixel
8118 * for LVS_LIST is 1 column --> which is a 1 because the
8119 * scroll is based on columns not pixels
8122 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8123 INT nScrollDiff, HWND hScrollWnd)
8125 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8126 INT nOldScrollPos, nNewScrollPos;
8127 SCROLLINFO scrollInfo;
8129 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8130 debugscrollcode(nScrollCode), nScrollDiff);
8132 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8134 scrollInfo.cbSize = sizeof(SCROLLINFO);
8135 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8137 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8139 nOldScrollPos = scrollInfo.nPos;
8141 switch (nScrollCode)
8143 case SB_INTERNAL:
8144 break;
8146 case SB_LINELEFT:
8147 nScrollDiff = -1;
8148 break;
8150 case SB_LINERIGHT:
8151 nScrollDiff = 1;
8152 break;
8154 case SB_PAGELEFT:
8155 nScrollDiff = -scrollInfo.nPage;
8156 break;
8158 case SB_PAGERIGHT:
8159 nScrollDiff = scrollInfo.nPage;
8160 break;
8162 case SB_THUMBPOSITION:
8163 case SB_THUMBTRACK:
8164 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8165 break;
8167 default:
8168 nScrollDiff = 0;
8171 /* quit right away if pos isn't changing */
8172 if (nScrollDiff == 0) return 0;
8174 /* calculate new position, and handle overflows */
8175 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8176 if (nScrollDiff > 0) {
8177 if (nNewScrollPos < nOldScrollPos ||
8178 nNewScrollPos > scrollInfo.nMax)
8179 nNewScrollPos = scrollInfo.nMax;
8180 } else {
8181 if (nNewScrollPos > nOldScrollPos ||
8182 nNewScrollPos < scrollInfo.nMin)
8183 nNewScrollPos = scrollInfo.nMin;
8186 /* set the new position, and reread in case it changed */
8187 scrollInfo.fMask = SIF_POS;
8188 scrollInfo.nPos = nNewScrollPos;
8189 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8191 /* carry on only if it really changed */
8192 if (nNewScrollPos == nOldScrollPos) return 0;
8194 if(uView == LVS_REPORT)
8195 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8197 /* now adjust to client coordinates */
8198 nScrollDiff = nOldScrollPos - nNewScrollPos;
8199 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8201 /* and scroll the window */
8202 scroll_list(infoPtr, nScrollDiff, 0);
8204 return 0;
8207 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8209 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8210 INT gcWheelDelta = 0;
8211 INT pulScrollLines = 3;
8212 SCROLLINFO scrollInfo;
8214 TRACE("(wheelDelta=%d)\n", wheelDelta);
8216 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8217 gcWheelDelta -= wheelDelta;
8219 scrollInfo.cbSize = sizeof(SCROLLINFO);
8220 scrollInfo.fMask = SIF_POS;
8222 switch(uView)
8224 case LVS_ICON:
8225 case LVS_SMALLICON:
8227 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8228 * should be fixed in the future.
8230 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8231 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8232 break;
8234 case LVS_REPORT:
8235 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8237 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8238 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8239 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8241 break;
8243 case LVS_LIST:
8244 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8245 break;
8247 return 0;
8250 /***
8251 * DESCRIPTION:
8252 * ???
8254 * PARAMETER(S):
8255 * [I] infoPtr : valid pointer to the listview structure
8256 * [I] nVirtualKey : virtual key
8257 * [I] lKeyData : key data
8259 * RETURN:
8260 * Zero
8262 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8264 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8265 HWND hwndSelf = infoPtr->hwndSelf;
8266 INT nItem = -1;
8267 NMLVKEYDOWN nmKeyDown;
8269 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8271 /* send LVN_KEYDOWN notification */
8272 nmKeyDown.wVKey = nVirtualKey;
8273 nmKeyDown.flags = 0;
8274 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8275 if (!IsWindow(hwndSelf))
8276 return 0;
8278 switch (nVirtualKey)
8280 case VK_SPACE:
8281 nItem = infoPtr->nFocusedItem;
8282 break;
8284 case VK_RETURN:
8285 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8287 if (!notify(infoPtr, NM_RETURN)) return 0;
8288 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8290 break;
8292 case VK_HOME:
8293 if (infoPtr->nItemCount > 0)
8294 nItem = 0;
8295 break;
8297 case VK_END:
8298 if (infoPtr->nItemCount > 0)
8299 nItem = infoPtr->nItemCount - 1;
8300 break;
8302 case VK_LEFT:
8303 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8304 break;
8306 case VK_UP:
8307 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8308 break;
8310 case VK_RIGHT:
8311 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8312 break;
8314 case VK_DOWN:
8315 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8316 break;
8318 case VK_PRIOR:
8319 if (uView == LVS_REPORT)
8321 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8322 if (infoPtr->nFocusedItem == topidx)
8323 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8324 else
8325 nItem = topidx;
8327 else
8328 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8329 * LISTVIEW_GetCountPerRow(infoPtr);
8330 if(nItem < 0) nItem = 0;
8331 break;
8333 case VK_NEXT:
8334 if (uView == LVS_REPORT)
8336 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8337 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8338 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8339 nItem = infoPtr->nFocusedItem + cnt - 1;
8340 else
8341 nItem = topidx + cnt - 1;
8343 else
8344 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8345 * LISTVIEW_GetCountPerRow(infoPtr);
8346 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8347 break;
8350 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8351 LISTVIEW_KeySelection(infoPtr, nItem);
8353 return 0;
8356 /***
8357 * DESCRIPTION:
8358 * Kills the focus.
8360 * PARAMETER(S):
8361 * [I] infoPtr : valid pointer to the listview structure
8363 * RETURN:
8364 * Zero
8366 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8368 TRACE("()\n");
8370 /* if we did not have the focus, there's nothing to do */
8371 if (!infoPtr->bFocus) return 0;
8373 /* send NM_KILLFOCUS notification */
8374 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8376 /* if we have a focus rectagle, get rid of it */
8377 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8379 /* set window focus flag */
8380 infoPtr->bFocus = FALSE;
8382 /* invalidate the selected items before reseting focus flag */
8383 LISTVIEW_InvalidateSelectedItems(infoPtr);
8385 return 0;
8388 /***
8389 * DESCRIPTION:
8390 * Processes double click messages (left mouse button).
8392 * PARAMETER(S):
8393 * [I] infoPtr : valid pointer to the listview structure
8394 * [I] wKey : key flag
8395 * [I] x,y : mouse coordinate
8397 * RETURN:
8398 * Zero
8400 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8402 LVHITTESTINFO htInfo;
8404 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8406 /* Cancel the item edition if any */
8407 if (infoPtr->itemEdit.fEnabled)
8409 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8410 infoPtr->itemEdit.fEnabled = FALSE;
8413 /* send NM_RELEASEDCAPTURE notification */
8414 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8416 htInfo.pt.x = x;
8417 htInfo.pt.y = y;
8419 /* send NM_DBLCLK notification */
8420 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8421 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8423 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8424 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8426 return 0;
8429 /***
8430 * DESCRIPTION:
8431 * Processes mouse down messages (left mouse button).
8433 * PARAMETERS:
8434 * infoPtr [I ] valid pointer to the listview structure
8435 * wKey [I ] key flag
8436 * x,y [I ] mouse coordinate
8438 * RETURN:
8439 * Zero
8441 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8443 LVHITTESTINFO lvHitTestInfo;
8444 static BOOL bGroupSelect = TRUE;
8445 BOOL bReceivedFocus = FALSE;
8446 POINT pt = { x, y };
8447 INT nItem;
8449 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8451 /* send NM_RELEASEDCAPTURE notification */
8452 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8454 if (!infoPtr->bFocus)
8456 bReceivedFocus = TRUE;
8457 SetFocus(infoPtr->hwndSelf);
8460 /* set left button down flag and record the click position */
8461 infoPtr->bLButtonDown = TRUE;
8462 infoPtr->ptClickPos = pt;
8464 lvHitTestInfo.pt.x = x;
8465 lvHitTestInfo.pt.y = y;
8467 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8468 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8469 infoPtr->nEditLabelItem = -1;
8470 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8472 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8474 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
8475 if(state == 1 || state == 2)
8477 LVITEMW lvitem;
8478 state ^= 3;
8479 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
8480 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8481 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8483 return 0;
8486 if (infoPtr->dwStyle & LVS_SINGLESEL)
8488 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8489 infoPtr->nEditLabelItem = nItem;
8490 else
8491 LISTVIEW_SetSelection(infoPtr, nItem);
8493 else
8495 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8497 if (bGroupSelect)
8499 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8500 LISTVIEW_SetItemFocus(infoPtr, nItem);
8501 infoPtr->nSelectionMark = nItem;
8503 else
8505 LVITEMW item;
8507 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8508 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8510 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8511 infoPtr->nSelectionMark = nItem;
8514 else if (wKey & MK_CONTROL)
8516 LVITEMW item;
8518 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8520 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8521 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8522 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8523 infoPtr->nSelectionMark = nItem;
8525 else if (wKey & MK_SHIFT)
8527 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8529 else
8531 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8532 infoPtr->nEditLabelItem = nItem;
8534 /* set selection (clears other pre-existing selections) */
8535 LISTVIEW_SetSelection(infoPtr, nItem);
8539 else
8541 /* remove all selections */
8542 LISTVIEW_DeselectAll(infoPtr);
8543 ReleaseCapture();
8546 if (bReceivedFocus)
8547 infoPtr->nEditLabelItem = -1;
8549 return 0;
8552 /***
8553 * DESCRIPTION:
8554 * Processes mouse up messages (left mouse button).
8556 * PARAMETERS:
8557 * infoPtr [I ] valid pointer to the listview structure
8558 * wKey [I ] key flag
8559 * x,y [I ] mouse coordinate
8561 * RETURN:
8562 * Zero
8564 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8566 LVHITTESTINFO lvHitTestInfo;
8568 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8570 if (!infoPtr->bLButtonDown) return 0;
8572 lvHitTestInfo.pt.x = x;
8573 lvHitTestInfo.pt.y = y;
8575 /* send NM_CLICK notification */
8576 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8577 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8579 /* set left button flag */
8580 infoPtr->bLButtonDown = FALSE;
8582 /* if we clicked on a selected item, edit the label */
8583 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8585 /* we want to make sure the user doesn't want to do a double click. So we will
8586 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8588 infoPtr->itemEdit.fEnabled = TRUE;
8589 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8590 SetTimer(infoPtr->hwndSelf,
8591 (UINT_PTR)&infoPtr->itemEdit,
8592 GetDoubleClickTime(),
8593 LISTVIEW_DelayedEditItem);
8596 return 0;
8599 /***
8600 * DESCRIPTION:
8601 * Destroys the listview control (called after WM_DESTROY).
8603 * PARAMETER(S):
8604 * [I] infoPtr : valid pointer to the listview structure
8606 * RETURN:
8607 * Zero
8609 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8611 TRACE("()\n");
8613 /* delete all items */
8614 LISTVIEW_DeleteAllItems(infoPtr);
8616 /* destroy data structure */
8617 DPA_Destroy(infoPtr->hdpaItems);
8618 DPA_Destroy(infoPtr->hdpaPosX);
8619 DPA_Destroy(infoPtr->hdpaPosY);
8620 DPA_Destroy(infoPtr->hdpaColumns);
8621 ranges_destroy(infoPtr->selectionRanges);
8623 /* destroy image lists */
8624 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8626 if (infoPtr->himlNormal)
8627 ImageList_Destroy(infoPtr->himlNormal);
8628 if (infoPtr->himlSmall)
8629 ImageList_Destroy(infoPtr->himlSmall);
8630 if (infoPtr->himlState)
8631 ImageList_Destroy(infoPtr->himlState);
8634 /* destroy font, bkgnd brush */
8635 infoPtr->hFont = 0;
8636 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8637 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8639 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8641 /* free listview info pointer*/
8642 Free(infoPtr);
8644 return 0;
8647 /***
8648 * DESCRIPTION:
8649 * Handles notifications from header.
8651 * PARAMETER(S):
8652 * [I] infoPtr : valid pointer to the listview structure
8653 * [I] nCtrlId : control identifier
8654 * [I] lpnmh : notification information
8656 * RETURN:
8657 * Zero
8659 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8661 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8662 HWND hwndSelf = infoPtr->hwndSelf;
8664 TRACE("(lpnmh=%p)\n", lpnmh);
8666 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8668 switch (lpnmh->hdr.code)
8670 case HDN_TRACKW:
8671 case HDN_TRACKA:
8673 COLUMN_INFO *lpColumnInfo;
8674 POINT ptOrigin;
8675 INT x;
8677 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8678 break;
8680 /* remove the old line (if any) */
8681 LISTVIEW_DrawTrackLine(infoPtr);
8683 /* compute & draw the new line */
8684 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8685 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8686 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8687 infoPtr->xTrackLine = x + ptOrigin.x;
8688 LISTVIEW_DrawTrackLine(infoPtr);
8689 break;
8692 case HDN_ENDTRACKA:
8693 case HDN_ENDTRACKW:
8694 /* remove the track line (if any) */
8695 LISTVIEW_DrawTrackLine(infoPtr);
8696 infoPtr->xTrackLine = -1;
8697 break;
8699 case HDN_ENDDRAG:
8700 FIXME("Changing column order not implemented\n");
8701 return TRUE;
8703 case HDN_ITEMCHANGINGW:
8704 case HDN_ITEMCHANGINGA:
8705 return notify_forward_header(infoPtr, lpnmh);
8707 case HDN_ITEMCHANGEDW:
8708 case HDN_ITEMCHANGEDA:
8710 COLUMN_INFO *lpColumnInfo;
8711 INT dx, cxy;
8713 notify_forward_header(infoPtr, lpnmh);
8714 if (!IsWindow(hwndSelf))
8715 break;
8717 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8719 HDITEMW hdi;
8721 hdi.mask = HDI_WIDTH;
8722 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8723 cxy = hdi.cxy;
8725 else
8726 cxy = lpnmh->pitem->cxy;
8728 /* determine how much we change since the last know position */
8729 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8730 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8731 if (dx != 0)
8733 lpColumnInfo->rcHeader.right += dx;
8734 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8735 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8736 else
8738 /* only needs to update the scrolls */
8739 infoPtr->nItemWidth += dx;
8740 LISTVIEW_UpdateScroll(infoPtr);
8742 LISTVIEW_UpdateItemSize(infoPtr);
8743 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8745 POINT ptOrigin;
8746 RECT rcCol = lpColumnInfo->rcHeader;
8748 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8749 OffsetRect(&rcCol, ptOrigin.x, 0);
8751 rcCol.top = infoPtr->rcList.top;
8752 rcCol.bottom = infoPtr->rcList.bottom;
8754 /* resizing left-aligned columns leaves most of the left side untouched */
8755 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8757 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8758 if (dx > 0)
8759 nMaxDirty += dx;
8760 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8763 /* when shrinking the last column clear the now unused field */
8764 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8765 rcCol.right -= dx;
8767 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8771 break;
8773 case HDN_ITEMCLICKW:
8774 case HDN_ITEMCLICKA:
8776 /* Handle sorting by Header Column */
8777 NMLISTVIEW nmlv;
8779 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8780 nmlv.iItem = -1;
8781 nmlv.iSubItem = lpnmh->iItem;
8782 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8784 break;
8786 case HDN_DIVIDERDBLCLICKW:
8787 case HDN_DIVIDERDBLCLICKA:
8788 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8789 break;
8792 return 0;
8795 /***
8796 * DESCRIPTION:
8797 * Paint non-client area of control.
8799 * PARAMETER(S):
8800 * [I] infoPtr : valid pointer to the listview structureof the sender
8801 * [I] region : update region
8803 * RETURN:
8804 * TRUE - frame was painted
8805 * FALSE - call default window proc
8807 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8809 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8810 HDC dc;
8811 RECT r;
8812 HRGN cliprgn;
8813 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8814 cyEdge = GetSystemMetrics (SM_CYEDGE);
8816 if (!theme) return FALSE;
8818 GetWindowRect(infoPtr->hwndSelf, &r);
8820 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8821 r.right - cxEdge, r.bottom - cyEdge);
8822 if (region != (HRGN)1)
8823 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8824 OffsetRect(&r, -r.left, -r.top);
8826 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8827 OffsetRect(&r, -r.left, -r.top);
8829 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8830 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8831 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8832 ReleaseDC(infoPtr->hwndSelf, dc);
8834 /* Call default proc to get the scrollbars etc. painted */
8835 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8837 return TRUE;
8840 /***
8841 * DESCRIPTION:
8842 * Determines the type of structure to use.
8844 * PARAMETER(S):
8845 * [I] infoPtr : valid pointer to the listview structureof the sender
8846 * [I] hwndFrom : listview window handle
8847 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8849 * RETURN:
8850 * Zero
8852 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8854 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8856 if (nCommand != NF_REQUERY) return 0;
8858 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8860 return 0;
8863 /***
8864 * DESCRIPTION:
8865 * Paints/Repaints the listview control.
8867 * PARAMETER(S):
8868 * [I] infoPtr : valid pointer to the listview structure
8869 * [I] hdc : device context handle
8871 * RETURN:
8872 * Zero
8874 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8876 TRACE("(hdc=%p)\n", hdc);
8878 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8880 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8882 infoPtr->bNoItemMetrics = FALSE;
8883 LISTVIEW_UpdateItemSize(infoPtr);
8884 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8885 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8886 LISTVIEW_UpdateScroll(infoPtr);
8889 UpdateWindow(infoPtr->hwndHeader);
8891 if (hdc)
8892 LISTVIEW_Refresh(infoPtr, hdc, NULL);
8893 else
8895 PAINTSTRUCT ps;
8897 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8898 if (!hdc) return 1;
8899 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
8900 EndPaint(infoPtr->hwndSelf, &ps);
8903 return 0;
8907 /***
8908 * DESCRIPTION:
8909 * Paints/Repaints the listview control.
8911 * PARAMETER(S):
8912 * [I] infoPtr : valid pointer to the listview structure
8913 * [I] hdc : device context handle
8914 * [I] options : drawing options
8916 * RETURN:
8917 * Zero
8919 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8921 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
8923 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8924 return 0;
8926 if (options & PRF_ERASEBKGND)
8927 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8929 if (options & PRF_CLIENT)
8930 LISTVIEW_Paint(infoPtr, hdc);
8932 return 0;
8936 /***
8937 * DESCRIPTION:
8938 * Processes double click messages (right mouse button).
8940 * PARAMETER(S):
8941 * [I] infoPtr : valid pointer to the listview structure
8942 * [I] wKey : key flag
8943 * [I] x,y : mouse coordinate
8945 * RETURN:
8946 * Zero
8948 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8950 LVHITTESTINFO lvHitTestInfo;
8952 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8954 /* send NM_RELEASEDCAPTURE notification */
8955 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8957 /* send NM_RDBLCLK notification */
8958 lvHitTestInfo.pt.x = x;
8959 lvHitTestInfo.pt.y = y;
8960 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8961 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8963 return 0;
8966 /***
8967 * DESCRIPTION:
8968 * Processes mouse down messages (right mouse button).
8970 * PARAMETER(S):
8971 * [I] infoPtr : valid pointer to the listview structure
8972 * [I] wKey : key flag
8973 * [I] x,y : mouse coordinate
8975 * RETURN:
8976 * Zero
8978 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8980 LVHITTESTINFO lvHitTestInfo;
8981 INT nItem;
8983 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8985 /* send NM_RELEASEDCAPTURE notification */
8986 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8988 /* make sure the listview control window has the focus */
8989 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8991 /* set right button down flag */
8992 infoPtr->bRButtonDown = TRUE;
8994 /* determine the index of the selected item */
8995 lvHitTestInfo.pt.x = x;
8996 lvHitTestInfo.pt.y = y;
8997 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8999 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9001 LISTVIEW_SetItemFocus(infoPtr, nItem);
9002 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9003 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9004 LISTVIEW_SetSelection(infoPtr, nItem);
9006 else
9008 LISTVIEW_DeselectAll(infoPtr);
9011 return 0;
9014 /***
9015 * DESCRIPTION:
9016 * Processes mouse up messages (right mouse button).
9018 * PARAMETER(S):
9019 * [I] infoPtr : valid pointer to the listview structure
9020 * [I] wKey : key flag
9021 * [I] x,y : mouse coordinate
9023 * RETURN:
9024 * Zero
9026 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9028 LVHITTESTINFO lvHitTestInfo;
9029 POINT pt;
9031 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9033 if (!infoPtr->bRButtonDown) return 0;
9035 /* set button flag */
9036 infoPtr->bRButtonDown = FALSE;
9038 /* Send NM_RClICK notification */
9039 lvHitTestInfo.pt.x = x;
9040 lvHitTestInfo.pt.y = y;
9041 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9042 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9044 /* Change to screen coordinate for WM_CONTEXTMENU */
9045 pt = lvHitTestInfo.pt;
9046 ClientToScreen(infoPtr->hwndSelf, &pt);
9048 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9049 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9050 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9052 return 0;
9056 /***
9057 * DESCRIPTION:
9058 * Sets the cursor.
9060 * PARAMETER(S):
9061 * [I] infoPtr : valid pointer to the listview structure
9062 * [I] hwnd : window handle of window containing the cursor
9063 * [I] nHittest : hit-test code
9064 * [I] wMouseMsg : ideintifier of the mouse message
9066 * RETURN:
9067 * TRUE if cursor is set
9068 * FALSE otherwise
9070 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9072 LVHITTESTINFO lvHitTestInfo;
9074 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9076 if(!infoPtr->hHotCursor) return FALSE;
9078 GetCursorPos(&lvHitTestInfo.pt);
9079 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9081 SetCursor(infoPtr->hHotCursor);
9083 return TRUE;
9086 /***
9087 * DESCRIPTION:
9088 * Sets the focus.
9090 * PARAMETER(S):
9091 * [I] infoPtr : valid pointer to the listview structure
9092 * [I] hwndLoseFocus : handle of previously focused window
9094 * RETURN:
9095 * Zero
9097 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9099 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9101 /* if we have the focus already, there's nothing to do */
9102 if (infoPtr->bFocus) return 0;
9104 /* send NM_SETFOCUS notification */
9105 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9107 /* set window focus flag */
9108 infoPtr->bFocus = TRUE;
9110 /* put the focus rect back on */
9111 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9113 /* redraw all visible selected items */
9114 LISTVIEW_InvalidateSelectedItems(infoPtr);
9116 return 0;
9119 /***
9120 * DESCRIPTION:
9121 * Sets the font.
9123 * PARAMETER(S):
9124 * [I] infoPtr : valid pointer to the listview structure
9125 * [I] fRedraw : font handle
9126 * [I] fRedraw : redraw flag
9128 * RETURN:
9129 * Zero
9131 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9133 HFONT oldFont = infoPtr->hFont;
9135 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9137 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9138 if (infoPtr->hFont == oldFont) return 0;
9140 LISTVIEW_SaveTextMetrics(infoPtr);
9142 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9143 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9145 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9147 return 0;
9150 /***
9151 * DESCRIPTION:
9152 * Message handling for WM_SETREDRAW.
9153 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9155 * PARAMETER(S):
9156 * [I] infoPtr : valid pointer to the listview structure
9157 * [I] bRedraw: state of redraw flag
9159 * RETURN:
9160 * DefWinProc return value
9162 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9164 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9166 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9167 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9169 infoPtr->bRedraw = bRedraw;
9171 if(!bRedraw) return 0;
9173 if (is_autoarrange(infoPtr))
9174 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9175 LISTVIEW_UpdateScroll(infoPtr);
9177 /* despite what the WM_SETREDRAW docs says, apps expect us
9178 * to invalidate the listview here... stupid! */
9179 LISTVIEW_InvalidateList(infoPtr);
9181 return 0;
9184 /***
9185 * DESCRIPTION:
9186 * Resizes the listview control. This function processes WM_SIZE
9187 * messages. At this time, the width and height are not used.
9189 * PARAMETER(S):
9190 * [I] infoPtr : valid pointer to the listview structure
9191 * [I] Width : new width
9192 * [I] Height : new height
9194 * RETURN:
9195 * Zero
9197 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9199 RECT rcOld = infoPtr->rcList;
9201 TRACE("(width=%d, height=%d)\n", Width, Height);
9203 LISTVIEW_UpdateSize(infoPtr);
9204 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9206 /* do not bother with display related stuff if we're not redrawing */
9207 if (!is_redrawing(infoPtr)) return 0;
9209 if (is_autoarrange(infoPtr))
9210 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9212 LISTVIEW_UpdateScroll(infoPtr);
9214 /* refresh all only for lists whose height changed significantly */
9215 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9216 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9217 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9218 LISTVIEW_InvalidateList(infoPtr);
9220 return 0;
9223 /***
9224 * DESCRIPTION:
9225 * Sets the size information.
9227 * PARAMETER(S):
9228 * [I] infoPtr : valid pointer to the listview structure
9230 * RETURN:
9231 * None
9233 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9235 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9237 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9239 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9241 if (uView == LVS_LIST)
9243 /* Apparently the "LIST" style is supposed to have the same
9244 * number of items in a column even if there is no scroll bar.
9245 * Since if a scroll bar already exists then the bottom is already
9246 * reduced, only reduce if the scroll bar does not currently exist.
9247 * The "2" is there to mimic the native control. I think it may be
9248 * related to either padding or edges. (GLA 7/2002)
9250 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9251 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9252 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9254 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
9256 HDLAYOUT hl;
9257 WINDOWPOS wp;
9259 hl.prc = &infoPtr->rcList;
9260 hl.pwpos = &wp;
9261 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9263 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9265 infoPtr->rcList.top = max(wp.cy, 0);
9268 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9271 /***
9272 * DESCRIPTION:
9273 * Processes WM_STYLECHANGED messages.
9275 * PARAMETER(S):
9276 * [I] infoPtr : valid pointer to the listview structure
9277 * [I] wStyleType : window style type (normal or extended)
9278 * [I] lpss : window style information
9280 * RETURN:
9281 * Zero
9283 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9284 const STYLESTRUCT *lpss)
9286 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9287 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9289 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9290 wStyleType, lpss->styleOld, lpss->styleNew);
9292 if (wStyleType != GWL_STYLE) return 0;
9294 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9295 /* what if LVS_OWNERDATA changed? */
9296 /* or LVS_SINGLESEL */
9297 /* or LVS_SORT{AS,DES}CENDING */
9299 infoPtr->dwStyle = lpss->styleNew;
9301 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9302 ((lpss->styleNew & WS_HSCROLL) == 0))
9303 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9305 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9306 ((lpss->styleNew & WS_VSCROLL) == 0))
9307 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9309 if (uNewView != uOldView)
9311 SIZE oldIconSize = infoPtr->iconSize;
9312 HIMAGELIST himl;
9314 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9315 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9317 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9318 SetRectEmpty(&infoPtr->rcFocus);
9320 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9321 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9323 if (uNewView == LVS_ICON)
9325 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9327 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9328 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9329 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9332 else if (uNewView == LVS_REPORT)
9334 HDLAYOUT hl;
9335 WINDOWPOS wp;
9337 hl.prc = &infoPtr->rcList;
9338 hl.pwpos = &wp;
9339 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9340 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9343 LISTVIEW_UpdateItemSize(infoPtr);
9346 if (uNewView == LVS_REPORT)
9347 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
9349 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9350 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9351 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9353 /* update the size of the client area */
9354 LISTVIEW_UpdateSize(infoPtr);
9356 /* add scrollbars if needed */
9357 LISTVIEW_UpdateScroll(infoPtr);
9359 /* invalidate client area + erase background */
9360 LISTVIEW_InvalidateList(infoPtr);
9362 return 0;
9365 /***
9366 * DESCRIPTION:
9367 * Window procedure of the listview control.
9370 static LRESULT WINAPI
9371 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9373 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9375 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9377 if (!infoPtr && (uMsg != WM_NCCREATE))
9378 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9380 switch (uMsg)
9382 case LVM_APPROXIMATEVIEWRECT:
9383 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9384 LOWORD(lParam), HIWORD(lParam));
9385 case LVM_ARRANGE:
9386 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9388 /* case LVM_CANCELEDITLABEL: */
9390 case LVM_CREATEDRAGIMAGE:
9391 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9393 case LVM_DELETEALLITEMS:
9394 return LISTVIEW_DeleteAllItems(infoPtr);
9396 case LVM_DELETECOLUMN:
9397 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9399 case LVM_DELETEITEM:
9400 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9402 case LVM_EDITLABELW:
9403 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9405 case LVM_EDITLABELA:
9406 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9408 /* case LVM_ENABLEGROUPVIEW: */
9410 case LVM_ENSUREVISIBLE:
9411 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9413 case LVM_FINDITEMW:
9414 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9416 case LVM_FINDITEMA:
9417 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9419 case LVM_GETBKCOLOR:
9420 return infoPtr->clrBk;
9422 /* case LVM_GETBKIMAGE: */
9424 case LVM_GETCALLBACKMASK:
9425 return infoPtr->uCallbackMask;
9427 case LVM_GETCOLUMNA:
9428 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9430 case LVM_GETCOLUMNW:
9431 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9433 case LVM_GETCOLUMNORDERARRAY:
9434 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9436 case LVM_GETCOLUMNWIDTH:
9437 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9439 case LVM_GETCOUNTPERPAGE:
9440 return LISTVIEW_GetCountPerPage(infoPtr);
9442 case LVM_GETEDITCONTROL:
9443 return (LRESULT)infoPtr->hwndEdit;
9445 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9446 return infoPtr->dwLvExStyle;
9448 /* case LVM_GETGROUPINFO: */
9450 /* case LVM_GETGROUPMETRICS: */
9452 case LVM_GETHEADER:
9453 return (LRESULT)infoPtr->hwndHeader;
9455 case LVM_GETHOTCURSOR:
9456 return (LRESULT)infoPtr->hHotCursor;
9458 case LVM_GETHOTITEM:
9459 return infoPtr->nHotItem;
9461 case LVM_GETHOVERTIME:
9462 return infoPtr->dwHoverTime;
9464 case LVM_GETIMAGELIST:
9465 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9467 /* case LVM_GETINSERTMARK: */
9469 /* case LVM_GETINSERTMARKCOLOR: */
9471 /* case LVM_GETINSERTMARKRECT: */
9473 case LVM_GETISEARCHSTRINGA:
9474 case LVM_GETISEARCHSTRINGW:
9475 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9476 return FALSE;
9478 case LVM_GETITEMA:
9479 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9481 case LVM_GETITEMW:
9482 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9484 case LVM_GETITEMCOUNT:
9485 return infoPtr->nItemCount;
9487 case LVM_GETITEMPOSITION:
9488 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9490 case LVM_GETITEMRECT:
9491 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9493 case LVM_GETITEMSPACING:
9494 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9496 case LVM_GETITEMSTATE:
9497 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9499 case LVM_GETITEMTEXTA:
9500 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9502 case LVM_GETITEMTEXTW:
9503 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9505 case LVM_GETNEXTITEM:
9506 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9508 case LVM_GETNUMBEROFWORKAREAS:
9509 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9510 return 1;
9512 case LVM_GETORIGIN:
9513 if (!lParam) return FALSE;
9514 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9515 return TRUE;
9517 /* case LVM_GETOUTLINECOLOR: */
9519 /* case LVM_GETSELECTEDCOLUMN: */
9521 case LVM_GETSELECTEDCOUNT:
9522 return LISTVIEW_GetSelectedCount(infoPtr);
9524 case LVM_GETSELECTIONMARK:
9525 return infoPtr->nSelectionMark;
9527 case LVM_GETSTRINGWIDTHA:
9528 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9530 case LVM_GETSTRINGWIDTHW:
9531 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9533 case LVM_GETSUBITEMRECT:
9534 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9536 case LVM_GETTEXTBKCOLOR:
9537 return infoPtr->clrTextBk;
9539 case LVM_GETTEXTCOLOR:
9540 return infoPtr->clrText;
9542 /* case LVM_GETTILEINFO: */
9544 /* case LVM_GETTILEVIEWINFO: */
9546 case LVM_GETTOOLTIPS:
9547 if( !infoPtr->hwndToolTip )
9548 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9549 return (LRESULT)infoPtr->hwndToolTip;
9551 case LVM_GETTOPINDEX:
9552 return LISTVIEW_GetTopIndex(infoPtr);
9554 /*case LVM_GETUNICODEFORMAT:
9555 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9556 return FALSE;*/
9558 /* case LVM_GETVIEW: */
9560 case LVM_GETVIEWRECT:
9561 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9563 case LVM_GETWORKAREAS:
9564 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9565 return FALSE;
9567 /* case LVM_HASGROUP: */
9569 case LVM_HITTEST:
9570 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9572 case LVM_INSERTCOLUMNA:
9573 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9575 case LVM_INSERTCOLUMNW:
9576 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9578 /* case LVM_INSERTGROUP: */
9580 /* case LVM_INSERTGROUPSORTED: */
9582 case LVM_INSERTITEMA:
9583 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9585 case LVM_INSERTITEMW:
9586 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9588 /* case LVM_INSERTMARKHITTEST: */
9590 /* case LVM_ISGROUPVIEWENABLED: */
9592 /* case LVM_MAPIDTOINDEX: */
9594 /* case LVM_MAPINDEXTOID: */
9596 /* case LVM_MOVEGROUP: */
9598 /* case LVM_MOVEITEMTOGROUP: */
9600 case LVM_REDRAWITEMS:
9601 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9603 /* case LVM_REMOVEALLGROUPS: */
9605 /* case LVM_REMOVEGROUP: */
9607 case LVM_SCROLL:
9608 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9610 case LVM_SETBKCOLOR:
9611 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9613 /* case LVM_SETBKIMAGE: */
9615 case LVM_SETCALLBACKMASK:
9616 infoPtr->uCallbackMask = (UINT)wParam;
9617 return TRUE;
9619 case LVM_SETCOLUMNA:
9620 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9622 case LVM_SETCOLUMNW:
9623 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9625 case LVM_SETCOLUMNORDERARRAY:
9626 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9628 case LVM_SETCOLUMNWIDTH:
9629 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9631 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9632 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9634 /* case LVM_SETGROUPINFO: */
9636 /* case LVM_SETGROUPMETRICS: */
9638 case LVM_SETHOTCURSOR:
9639 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9641 case LVM_SETHOTITEM:
9642 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9644 case LVM_SETHOVERTIME:
9645 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9647 case LVM_SETICONSPACING:
9648 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9650 case LVM_SETIMAGELIST:
9651 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9653 /* case LVM_SETINFOTIP: */
9655 /* case LVM_SETINSERTMARK: */
9657 /* case LVM_SETINSERTMARKCOLOR: */
9659 case LVM_SETITEMA:
9660 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9662 case LVM_SETITEMW:
9663 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9665 case LVM_SETITEMCOUNT:
9666 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9668 case LVM_SETITEMPOSITION:
9670 POINT pt;
9671 pt.x = (short)LOWORD(lParam);
9672 pt.y = (short)HIWORD(lParam);
9673 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9676 case LVM_SETITEMPOSITION32:
9677 if (lParam == 0) return FALSE;
9678 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9680 case LVM_SETITEMSTATE:
9681 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9683 case LVM_SETITEMTEXTA:
9684 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9686 case LVM_SETITEMTEXTW:
9687 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9689 /* case LVM_SETOUTLINECOLOR: */
9691 /* case LVM_SETSELECTEDCOLUMN: */
9693 case LVM_SETSELECTIONMARK:
9694 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9696 case LVM_SETTEXTBKCOLOR:
9697 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9699 case LVM_SETTEXTCOLOR:
9700 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9702 /* case LVM_SETTILEINFO: */
9704 /* case LVM_SETTILEVIEWINFO: */
9706 /* case LVM_SETTILEWIDTH: */
9708 case LVM_SETTOOLTIPS:
9709 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9711 case LVM_SETUNICODEFORMAT:
9712 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9714 /* case LVM_SETVIEW: */
9716 /* case LVM_SETWORKAREAS: */
9718 /* case LVM_SORTGROUPS: */
9720 case LVM_SORTITEMS:
9721 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9723 /* LVM_SORTITEMSEX: */
9725 case LVM_SUBITEMHITTEST:
9726 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9728 case LVM_UPDATE:
9729 return LISTVIEW_Update(infoPtr, (INT)wParam);
9731 case WM_CHAR:
9732 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9734 case WM_COMMAND:
9735 return LISTVIEW_Command(infoPtr, wParam, lParam);
9737 case WM_NCCREATE:
9738 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9740 case WM_CREATE:
9741 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9743 case WM_DESTROY:
9744 return LISTVIEW_Destroy(infoPtr);
9746 case WM_ENABLE:
9747 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9749 case WM_ERASEBKGND:
9750 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9752 case WM_GETDLGCODE:
9753 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9755 case WM_GETFONT:
9756 return (LRESULT)infoPtr->hFont;
9758 case WM_HSCROLL:
9759 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9761 case WM_KEYDOWN:
9762 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9764 case WM_KILLFOCUS:
9765 return LISTVIEW_KillFocus(infoPtr);
9767 case WM_LBUTTONDBLCLK:
9768 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9770 case WM_LBUTTONDOWN:
9771 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9773 case WM_LBUTTONUP:
9774 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9776 case WM_MOUSEMOVE:
9777 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9779 case WM_MOUSEHOVER:
9780 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9782 case WM_NCDESTROY:
9783 return LISTVIEW_NCDestroy(infoPtr);
9785 case WM_NCPAINT:
9786 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9787 return 0;
9788 goto fwd_msg;
9790 case WM_NOTIFY:
9791 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9792 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9793 else return 0;
9795 case WM_NOTIFYFORMAT:
9796 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9798 case WM_PRINTCLIENT:
9799 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9801 case WM_PAINT:
9802 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9804 case WM_RBUTTONDBLCLK:
9805 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9807 case WM_RBUTTONDOWN:
9808 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9810 case WM_RBUTTONUP:
9811 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9813 case WM_SETCURSOR:
9814 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9815 return TRUE;
9816 goto fwd_msg;
9818 case WM_SETFOCUS:
9819 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9821 case WM_SETFONT:
9822 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9824 case WM_SETREDRAW:
9825 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9827 case WM_SIZE:
9828 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9830 case WM_STYLECHANGED:
9831 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9833 case WM_SYSCOLORCHANGE:
9834 COMCTL32_RefreshSysColors();
9835 return 0;
9837 /* case WM_TIMER: */
9838 case WM_THEMECHANGED:
9839 return LISTVIEW_ThemeChanged(infoPtr);
9841 case WM_VSCROLL:
9842 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9844 case WM_MOUSEWHEEL:
9845 if (wParam & (MK_SHIFT | MK_CONTROL))
9846 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9847 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9849 case WM_WINDOWPOSCHANGED:
9850 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9852 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9853 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9854 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9856 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9858 MEASUREITEMSTRUCT mis;
9859 mis.CtlType = ODT_LISTVIEW;
9860 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9861 mis.itemID = -1;
9862 mis.itemWidth = 0;
9863 mis.itemData = 0;
9864 mis.itemHeight= infoPtr->nItemHeight;
9865 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9866 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9867 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9870 LISTVIEW_UpdateSize(infoPtr);
9871 LISTVIEW_UpdateScroll(infoPtr);
9873 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9875 /* case WM_WININICHANGE: */
9877 default:
9878 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9879 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
9881 fwd_msg:
9882 /* call default window procedure */
9883 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9888 /***
9889 * DESCRIPTION:
9890 * Registers the window class.
9892 * PARAMETER(S):
9893 * None
9895 * RETURN:
9896 * None
9898 void LISTVIEW_Register(void)
9900 WNDCLASSW wndClass;
9902 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9903 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9904 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9905 wndClass.cbClsExtra = 0;
9906 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9907 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9908 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9909 wndClass.lpszClassName = WC_LISTVIEWW;
9910 RegisterClassW(&wndClass);
9913 /***
9914 * DESCRIPTION:
9915 * Unregisters the window class.
9917 * PARAMETER(S):
9918 * None
9920 * RETURN:
9921 * None
9923 void LISTVIEW_Unregister(void)
9925 UnregisterClassW(WC_LISTVIEWW, NULL);
9928 /***
9929 * DESCRIPTION:
9930 * Handle any WM_COMMAND messages
9932 * PARAMETER(S):
9933 * [I] infoPtr : valid pointer to the listview structure
9934 * [I] wParam : the first message parameter
9935 * [I] lParam : the second message parameter
9937 * RETURN:
9938 * Zero.
9940 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9942 switch (HIWORD(wParam))
9944 case EN_UPDATE:
9947 * Adjust the edit window size
9949 WCHAR buffer[1024];
9950 HDC hdc = GetDC(infoPtr->hwndEdit);
9951 HFONT hFont, hOldFont = 0;
9952 RECT rect;
9953 SIZE sz;
9954 int len;
9956 if (!infoPtr->hwndEdit || !hdc) return 0;
9957 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9958 GetWindowRect(infoPtr->hwndEdit, &rect);
9960 /* Select font to get the right dimension of the string */
9961 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9962 if(hFont != 0)
9964 hOldFont = SelectObject(hdc, hFont);
9967 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9969 TEXTMETRICW textMetric;
9971 /* Add Extra spacing for the next character */
9972 GetTextMetricsW(hdc, &textMetric);
9973 sz.cx += (textMetric.tmMaxCharWidth * 2);
9975 SetWindowPos (
9976 infoPtr->hwndEdit,
9977 HWND_TOP,
9980 sz.cx,
9981 rect.bottom - rect.top,
9982 SWP_DRAWFRAME|SWP_NOMOVE);
9984 if(hFont != 0)
9985 SelectObject(hdc, hOldFont);
9987 ReleaseDC(infoPtr->hwndEdit, hdc);
9989 break;
9992 default:
9993 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9996 return 0;
10000 /***
10001 * DESCRIPTION:
10002 * Subclassed edit control windproc function
10004 * PARAMETER(S):
10005 * [I] hwnd : the edit window handle
10006 * [I] uMsg : the message that is to be processed
10007 * [I] wParam : first message parameter
10008 * [I] lParam : second message parameter
10009 * [I] isW : TRUE if input is Unicode
10011 * RETURN:
10012 * Zero.
10014 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10016 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10017 BOOL cancel = FALSE;
10019 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10020 hwnd, uMsg, wParam, lParam, isW);
10022 switch (uMsg)
10024 case WM_GETDLGCODE:
10025 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10027 case WM_KILLFOCUS:
10028 break;
10030 case WM_DESTROY:
10032 WNDPROC editProc = infoPtr->EditWndProc;
10033 infoPtr->EditWndProc = 0;
10034 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10035 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10038 case WM_KEYDOWN:
10039 if (VK_ESCAPE == (INT)wParam)
10041 cancel = TRUE;
10042 break;
10044 else if (VK_RETURN == (INT)wParam)
10045 break;
10047 default:
10048 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10051 /* kill the edit */
10052 if (infoPtr->hwndEdit)
10054 LPWSTR buffer = NULL;
10056 infoPtr->hwndEdit = 0;
10057 if (!cancel)
10059 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10061 if (len)
10063 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10065 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10066 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10070 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10072 Free(buffer);
10075 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10076 return 0;
10079 /***
10080 * DESCRIPTION:
10081 * Subclassed edit control Unicode windproc function
10083 * PARAMETER(S):
10084 * [I] hwnd : the edit window handle
10085 * [I] uMsg : the message that is to be processed
10086 * [I] wParam : first message parameter
10087 * [I] lParam : second message parameter
10089 * RETURN:
10091 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10093 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10096 /***
10097 * DESCRIPTION:
10098 * Subclassed edit control ANSI windproc function
10100 * PARAMETER(S):
10101 * [I] hwnd : the edit window handle
10102 * [I] uMsg : the message that is to be processed
10103 * [I] wParam : first message parameter
10104 * [I] lParam : second message parameter
10106 * RETURN:
10108 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10110 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10113 /***
10114 * DESCRIPTION:
10115 * Creates a subclassed edit cotrol
10117 * PARAMETER(S):
10118 * [I] infoPtr : valid pointer to the listview structure
10119 * [I] text : initial text for the edit
10120 * [I] style : the window style
10121 * [I] isW : TRUE if input is Unicode
10123 * RETURN:
10125 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10126 INT x, INT y, INT width, INT height, BOOL isW)
10128 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10129 HWND hedit;
10130 SIZE sz;
10131 HDC hdc;
10132 HDC hOldFont=0;
10133 TEXTMETRICW textMetric;
10134 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10136 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10138 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10139 hdc = GetDC(infoPtr->hwndSelf);
10141 /* Select the font to get appropriate metric dimensions */
10142 if(infoPtr->hFont != 0)
10143 hOldFont = SelectObject(hdc, infoPtr->hFont);
10145 /*Get String Length in pixels */
10146 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10148 /*Add Extra spacing for the next character */
10149 GetTextMetricsW(hdc, &textMetric);
10150 sz.cx += (textMetric.tmMaxCharWidth * 2);
10152 if(infoPtr->hFont != 0)
10153 SelectObject(hdc, hOldFont);
10155 ReleaseDC(infoPtr->hwndSelf, hdc);
10156 if (isW)
10157 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10158 else
10159 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10161 if (!hedit) return 0;
10163 infoPtr->EditWndProc = (WNDPROC)
10164 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10165 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10167 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10169 return hedit;