comctl32/listview: Add tests for LVS_NOSORTHEADER switchover.
[wine/multimedia.git] / dlls / comctl32 / listview.c
bloba8ecd91e287a9693a6d92b69441273e42a548d96
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_WINDOWPOSCHANGED: arrange the list items if the current view is icon
40 * or small icon and the LVS_AUTOARRANGE style is specified.
41 * -- WM_TIMER
42 * -- WM_WININICHANGE
44 * Features
45 * -- Hot item handling, mouse hovering
46 * -- Workareas support
47 * -- Tilemode support
48 * -- Groups support
50 * Bugs
51 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
52 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
53 * -- LVA_SNAPTOGRID not implemented
54 * -- LISTVIEW_ApproximateViewRect partially implemented
55 * -- LISTVIEW_[GS]etColumnOrderArray stubs
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
60 * Speedups
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
71 * Flags
72 * -- LVIF_COLUMNS
73 * -- LVIF_GROUPID
75 * States
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
77 * -- LVIS_CUT
78 * -- LVIS_DROPHILITED
79 * -- LVIS_OVERLAYMASK
81 * Styles
82 * -- LVS_NOLABELWRAP
83 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
85 * -- LVS_ALIGNTOP
86 * -- LVS_TYPESTYLEMASK
88 * Extended Styles
89 * -- LVS_EX_BORDERSELECT
90 * -- LVS_EX_FLATSB
91 * -- LVS_EX_HEADERDRAGDROP
92 * -- LVS_EX_INFOTIP
93 * -- LVS_EX_LABELTIP
94 * -- LVS_EX_MULTIWORKAREAS
95 * -- LVS_EX_REGIONAL
96 * -- LVS_EX_SIMPLESELECT
97 * -- LVS_EX_TWOCLICKACTIVATE
98 * -- LVS_EX_UNDERLINECOLD
99 * -- LVS_EX_UNDERLINEHOT
101 * Notifications:
102 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
103 * -- LVN_GETINFOTIP
104 * -- LVN_HOTTRACK
105 * -- LVN_MARQUEEBEGIN
106 * -- LVN_SETDISPINFO
107 * -- NM_HOVER
108 * -- LVN_BEGINRDRAG
110 * Messages:
111 * -- LVM_CANCELEDITLABEL
112 * -- LVM_ENABLEGROUPVIEW
113 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
114 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
115 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
116 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
117 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
118 * -- LVM_GETINSERTMARKRECT
119 * -- LVM_GETNUMBEROFWORKAREAS
120 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
121 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
122 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
123 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
124 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
125 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
126 * -- LVM_GETVIEW, LVM_SETVIEW
127 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
128 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
129 * -- LVM_INSERTGROUPSORTED
130 * -- LVM_INSERTMARKHITTEST
131 * -- LVM_ISGROUPVIEWENABLED
132 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
133 * -- LVM_MOVEGROUP
134 * -- LVM_MOVEITEMTOGROUP
135 * -- LVM_SETINFOTIP
136 * -- LVM_SETTILEWIDTH
137 * -- LVM_SORTGROUPS
139 * Macros:
140 * -- ListView_GetCheckSate, ListView_SetCheckState
141 * -- ListView_GetHoverTime, ListView_SetHoverTime
142 * -- ListView_GetISearchString
143 * -- ListView_GetNumberOfWorkAreas
144 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
145 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
147 * Functions:
148 * -- LVGroupComparE
150 * Known differences in message stream from native control (not known if
151 * these differences cause problems):
152 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
153 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
154 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
155 * processing for "USEDOUBLECLICKTIME".
158 #include "config.h"
159 #include "wine/port.h"
161 #include <assert.h>
162 #include <ctype.h>
163 #include <string.h>
164 #include <stdlib.h>
165 #include <stdarg.h>
166 #include <stdio.h>
168 #include "windef.h"
169 #include "winbase.h"
170 #include "winnt.h"
171 #include "wingdi.h"
172 #include "winuser.h"
173 #include "winnls.h"
174 #include "commctrl.h"
175 #include "comctl32.h"
176 #include "uxtheme.h"
178 #include "wine/debug.h"
179 #include "wine/unicode.h"
181 WINE_DEFAULT_DEBUG_CHANNEL(listview);
183 /* make sure you set this to 0 for production use! */
184 #define DEBUG_RANGES 1
186 typedef struct tagCOLUMN_INFO
188 RECT rcHeader; /* tracks the header's rectangle */
189 int fmt; /* same as LVCOLUMN.fmt */
190 } COLUMN_INFO;
192 typedef struct tagITEMHDR
194 LPWSTR pszText;
195 INT iImage;
196 } ITEMHDR, *LPITEMHDR;
198 typedef struct tagSUBITEM_INFO
200 ITEMHDR hdr;
201 INT iSubItem;
202 } SUBITEM_INFO;
204 typedef struct tagITEM_INFO
206 ITEMHDR hdr;
207 UINT state;
208 LPARAM lParam;
209 INT iIndent;
210 } ITEM_INFO;
212 typedef struct tagRANGE
214 INT lower;
215 INT upper;
216 } RANGE;
218 typedef struct tagRANGES
220 HDPA hdpa;
221 } *RANGES;
223 typedef struct tagITERATOR
225 INT nItem;
226 INT nSpecial;
227 RANGE range;
228 RANGES ranges;
229 INT index;
230 } ITERATOR;
232 typedef struct tagDELAYED_ITEM_EDIT
234 BOOL fEnabled;
235 INT iItem;
236 } DELAYED_ITEM_EDIT;
238 typedef struct tagLISTVIEW_INFO
240 HWND hwndSelf;
241 HBRUSH hBkBrush;
242 COLORREF clrBk;
243 COLORREF clrText;
244 COLORREF clrTextBk;
245 HIMAGELIST himlNormal;
246 HIMAGELIST himlSmall;
247 HIMAGELIST himlState;
248 BOOL bLButtonDown;
249 BOOL bRButtonDown;
250 BOOL bDragging;
251 POINT ptClickPos; /* point where the user clicked */
252 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
253 INT nItemHeight;
254 INT nItemWidth;
255 RANGES selectionRanges;
256 INT nSelectionMark;
257 INT nHotItem;
258 SHORT notifyFormat;
259 HWND hwndNotify;
260 RECT rcList; /* This rectangle is really the window
261 * client rectangle possibly reduced by the
262 * horizontal scroll bar and/or header - see
263 * LISTVIEW_UpdateSize. This rectangle offset
264 * by the LISTVIEW_GetOrigin value is in
265 * client coordinates */
266 SIZE iconSize;
267 SIZE iconSpacing;
268 SIZE iconStateSize;
269 UINT uCallbackMask;
270 HWND hwndHeader;
271 HCURSOR hHotCursor;
272 HFONT hDefaultFont;
273 HFONT hFont;
274 INT ntmHeight; /* Some cached metrics of the font used */
275 INT ntmMaxCharWidth; /* by the listview to draw items */
276 INT nEllipsisWidth;
277 BOOL bRedraw; /* Turns on/off repaints & invalidations */
278 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
279 BOOL bFocus;
280 BOOL bDoChangeNotify; /* send change notification messages? */
281 INT nFocusedItem;
282 RECT rcFocus;
283 DWORD dwStyle; /* the cached window GWL_STYLE */
284 DWORD dwLvExStyle; /* extended listview style */
285 INT nItemCount; /* the number of items in the list */
286 HDPA hdpaItems; /* array ITEM_INFO pointers */
287 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
288 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
289 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
290 POINT currIconPos; /* this is the position next icon will be placed */
291 PFNLVCOMPARE pfnCompare;
292 LPARAM lParamSort;
293 HWND hwndEdit;
294 WNDPROC EditWndProc;
295 INT nEditLabelItem;
296 DWORD dwHoverTime;
297 HWND hwndToolTip;
299 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
301 DWORD lastKeyPressTimestamp;
302 WPARAM charCode;
303 INT nSearchParamLength;
304 WCHAR szSearchParam[ MAX_PATH ];
305 BOOL bIsDrawing;
306 INT nMeasureItemHeight;
307 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
308 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
310 DWORD iVersion; /* CCM_[G,S]ETVERSION */
311 } LISTVIEW_INFO;
314 * constants
316 /* How many we debug buffer to allocate */
317 #define DEBUG_BUFFERS 20
318 /* The size of a single debug bbuffer */
319 #define DEBUG_BUFFER_SIZE 256
321 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
322 #define SB_INTERNAL -1
324 /* maximum size of a label */
325 #define DISP_TEXT_SIZE 512
327 /* padding for items in list and small icon display modes */
328 #define WIDTH_PADDING 12
330 /* padding for items in list, report and small icon display modes */
331 #define HEIGHT_PADDING 1
333 /* offset of items in report display mode */
334 #define REPORT_MARGINX 2
336 /* padding for icon in large icon display mode
337 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
338 * that HITTEST will see.
339 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
340 * ICON_TOP_PADDING - sum of the two above.
341 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
342 * LABEL_HOR_PADDING - between text and sides of box
343 * LABEL_VERT_PADDING - between bottom of text and end of box
345 * ICON_LR_PADDING - additional width above icon size.
346 * ICON_LR_HALF - half of the above value
348 #define ICON_TOP_PADDING_NOTHITABLE 2
349 #define ICON_TOP_PADDING_HITABLE 2
350 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
351 #define ICON_BOTTOM_PADDING 4
352 #define LABEL_HOR_PADDING 5
353 #define LABEL_VERT_PADDING 7
354 #define ICON_LR_PADDING 16
355 #define ICON_LR_HALF (ICON_LR_PADDING/2)
357 /* default label width for items in list and small icon display modes */
358 #define DEFAULT_LABEL_WIDTH 40
360 /* default column width for items in list display mode */
361 #define DEFAULT_COLUMN_WIDTH 128
363 /* Size of "line" scroll for V & H scrolls */
364 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
366 /* Padding between image and label */
367 #define IMAGE_PADDING 2
369 /* Padding behind the label */
370 #define TRAILING_LABEL_PADDING 12
371 #define TRAILING_HEADER_PADDING 11
373 /* Border for the icon caption */
374 #define CAPTION_BORDER 2
376 /* Standard DrawText flags */
377 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
378 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
379 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
381 /* Image index from state */
382 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
384 /* The time in milliseconds to reset the search in the list */
385 #define KEY_DELAY 450
387 /* Dump the LISTVIEW_INFO structure to the debug channel */
388 #define LISTVIEW_DUMP(iP) do { \
389 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
390 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
391 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
392 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
393 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
394 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
395 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
396 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
397 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
398 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
399 } while(0)
401 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
404 * forward declarations
406 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
407 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
408 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
409 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
410 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
411 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
412 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
413 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
414 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
415 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
416 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
417 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
418 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
419 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
420 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
421 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
422 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
423 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
424 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
425 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
426 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
427 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
428 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
429 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
430 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
431 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
433 /******** Text handling functions *************************************/
435 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
436 * text string. The string may be ANSI or Unicode, in which case
437 * the boolean isW tells us the type of the string.
439 * The name of the function tell what type of strings it expects:
440 * W: Unicode, T: ANSI/Unicode - function of isW
443 static inline BOOL is_textW(LPCWSTR text)
445 return text != NULL && text != LPSTR_TEXTCALLBACKW;
448 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
450 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
451 return is_textW(text);
454 static inline int textlenT(LPCWSTR text, BOOL isW)
456 return !is_textT(text, isW) ? 0 :
457 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
460 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
462 if (isDestW)
463 if (isSrcW) lstrcpynW(dest, src, max);
464 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
465 else
466 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
467 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
470 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
472 LPWSTR wstr = (LPWSTR)text;
474 if (!isW && is_textT(text, isW))
476 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
477 wstr = Alloc(len * sizeof(WCHAR));
478 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
480 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
481 return wstr;
484 static inline void textfreeT(LPWSTR wstr, BOOL isW)
486 if (!isW && is_textT(wstr, isW)) Free (wstr);
490 * dest is a pointer to a Unicode string
491 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
493 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
495 BOOL bResult = TRUE;
497 if (src == LPSTR_TEXTCALLBACKW)
499 if (is_textW(*dest)) Free(*dest);
500 *dest = LPSTR_TEXTCALLBACKW;
502 else
504 LPWSTR pszText = textdupTtoW(src, isW);
505 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
506 bResult = Str_SetPtrW(dest, pszText);
507 textfreeT(pszText, isW);
509 return bResult;
513 * compares a Unicode to a Unicode/ANSI text string
515 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
517 if (!aw) return bt ? -1 : 0;
518 if (!bt) return aw ? 1 : 0;
519 if (aw == LPSTR_TEXTCALLBACKW)
520 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
521 if (bt != LPSTR_TEXTCALLBACKW)
523 LPWSTR bw = textdupTtoW(bt, isW);
524 int r = bw ? lstrcmpW(aw, bw) : 1;
525 textfreeT(bw, isW);
526 return r;
529 return 1;
532 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
534 int res;
536 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
537 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
538 return res ? res - sizeof(WCHAR) : res;
541 /******** Debugging functions *****************************************/
543 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
545 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
546 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
549 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
551 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
552 n = min(textlenT(text, isW), n);
553 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
556 static char* debug_getbuf(void)
558 static int index = 0;
559 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
560 return buffers[index++ % DEBUG_BUFFERS];
563 static inline const char* debugrange(const RANGE *lprng)
565 if (!lprng) return "(null)";
566 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
569 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
571 char* buf = debug_getbuf(), *text = buf;
572 int len, size = DEBUG_BUFFER_SIZE;
574 if (pScrollInfo == NULL) return "(null)";
575 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
576 if (len == -1) goto end; buf += len; size -= len;
577 if (pScrollInfo->fMask & SIF_RANGE)
578 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
579 else len = 0;
580 if (len == -1) goto end; buf += len; size -= len;
581 if (pScrollInfo->fMask & SIF_PAGE)
582 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
583 else len = 0;
584 if (len == -1) goto end; buf += len; size -= len;
585 if (pScrollInfo->fMask & SIF_POS)
586 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
587 else len = 0;
588 if (len == -1) goto end; buf += len; size -= len;
589 if (pScrollInfo->fMask & SIF_TRACKPOS)
590 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
591 else len = 0;
592 if (len == -1) goto end; buf += len; size -= len;
593 goto undo;
594 end:
595 buf = text + strlen(text);
596 undo:
597 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
598 return text;
601 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
603 if (!plvnm) return "(null)";
604 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
605 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
606 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
607 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
610 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
612 char* buf = debug_getbuf(), *text = buf;
613 int len, size = DEBUG_BUFFER_SIZE;
615 if (lpLVItem == NULL) return "(null)";
616 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
617 if (len == -1) goto end; buf += len; size -= len;
618 if (lpLVItem->mask & LVIF_STATE)
619 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
620 else len = 0;
621 if (len == -1) goto end; buf += len; size -= len;
622 if (lpLVItem->mask & LVIF_TEXT)
623 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
624 else len = 0;
625 if (len == -1) goto end; buf += len; size -= len;
626 if (lpLVItem->mask & LVIF_IMAGE)
627 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
628 else len = 0;
629 if (len == -1) goto end; buf += len; size -= len;
630 if (lpLVItem->mask & LVIF_PARAM)
631 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
632 else len = 0;
633 if (len == -1) goto end; buf += len; size -= len;
634 if (lpLVItem->mask & LVIF_INDENT)
635 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
636 else len = 0;
637 if (len == -1) goto end; buf += len; size -= len;
638 goto undo;
639 end:
640 buf = text + strlen(text);
641 undo:
642 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
643 return text;
646 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
648 char* buf = debug_getbuf(), *text = buf;
649 int len, size = DEBUG_BUFFER_SIZE;
651 if (lpColumn == NULL) return "(null)";
652 len = snprintf(buf, size, "{");
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpColumn->mask & LVCF_SUBITEM)
655 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
656 else len = 0;
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpColumn->mask & LVCF_FMT)
659 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
660 else len = 0;
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_WIDTH)
663 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
664 else len = 0;
665 if (len == -1) goto end; buf += len; size -= len;
666 if (lpColumn->mask & LVCF_TEXT)
667 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
668 else len = 0;
669 if (len == -1) goto end; buf += len; size -= len;
670 if (lpColumn->mask & LVCF_IMAGE)
671 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
672 else len = 0;
673 if (len == -1) goto end; buf += len; size -= len;
674 if (lpColumn->mask & LVCF_ORDER)
675 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
676 else len = 0;
677 if (len == -1) goto end; buf += len; size -= len;
678 goto undo;
679 end:
680 buf = text + strlen(text);
681 undo:
682 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
683 return text;
686 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
688 if (!lpht) return "(null)";
690 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
691 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
694 /* Return the corresponding text for a given scroll value */
695 static inline LPCSTR debugscrollcode(int nScrollCode)
697 switch(nScrollCode)
699 case SB_LINELEFT: return "SB_LINELEFT";
700 case SB_LINERIGHT: return "SB_LINERIGHT";
701 case SB_PAGELEFT: return "SB_PAGELEFT";
702 case SB_PAGERIGHT: return "SB_PAGERIGHT";
703 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
704 case SB_THUMBTRACK: return "SB_THUMBTRACK";
705 case SB_ENDSCROLL: return "SB_ENDSCROLL";
706 case SB_INTERNAL: return "SB_INTERNAL";
707 default: return "unknown";
712 /******** Notification functions ************************************/
714 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
716 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
717 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
720 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
722 LRESULT result;
724 TRACE("(code=%d)\n", code);
726 pnmh->hwndFrom = infoPtr->hwndSelf;
727 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
728 pnmh->code = code;
729 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
731 TRACE(" <= %ld\n", result);
733 return result;
736 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
738 NMHDR nmh;
739 HWND hwnd = infoPtr->hwndSelf;
740 notify_hdr(infoPtr, code, &nmh);
741 return IsWindow(hwnd);
744 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
746 NMITEMACTIVATE nmia;
747 LVITEMW item;
749 if (htInfo) {
750 nmia.uNewState = 0;
751 nmia.uOldState = 0;
752 nmia.uChanged = 0;
753 nmia.uKeyFlags = 0;
755 item.mask = LVIF_PARAM|LVIF_STATE;
756 item.iItem = htInfo->iItem;
757 item.iSubItem = 0;
758 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
759 nmia.lParam = item.lParam;
760 nmia.uOldState = item.state;
761 nmia.uNewState = item.state | LVIS_ACTIVATING;
762 nmia.uChanged = LVIF_STATE;
765 nmia.iItem = htInfo->iItem;
766 nmia.iSubItem = htInfo->iSubItem;
767 nmia.ptAction = htInfo->pt;
769 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
770 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
771 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
773 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
776 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
778 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
779 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
782 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
784 NMLISTVIEW nmlv;
785 LVITEMW item;
786 HWND hwnd = infoPtr->hwndSelf;
788 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
789 ZeroMemory(&nmlv, sizeof(nmlv));
790 nmlv.iItem = lvht->iItem;
791 nmlv.iSubItem = lvht->iSubItem;
792 nmlv.ptAction = lvht->pt;
793 item.mask = LVIF_PARAM;
794 item.iItem = lvht->iItem;
795 item.iSubItem = 0;
796 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
797 notify_listview(infoPtr, code, &nmlv);
798 return IsWindow(hwnd);
801 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
803 NMLISTVIEW nmlv;
804 LVITEMW item;
805 HWND hwnd = infoPtr->hwndSelf;
807 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
808 nmlv.iItem = nItem;
809 item.mask = LVIF_PARAM;
810 item.iItem = nItem;
811 item.iSubItem = 0;
812 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
813 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
814 return IsWindow(hwnd);
817 static int get_ansi_notification(UINT unicodeNotificationCode)
819 switch (unicodeNotificationCode)
821 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
822 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
823 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
824 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
825 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
826 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
828 ERR("unknown notification %x\n", unicodeNotificationCode);
829 assert(FALSE);
830 return 0;
834 Send notification. depends on dispinfoW having same
835 structure as dispinfoA.
836 infoPtr : listview struct
837 notificationCode : *Unicode* notification code
838 pdi : dispinfo structure (can be unicode or ansi)
839 isW : TRUE if dispinfo is Unicode
841 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
843 BOOL bResult = FALSE;
844 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
845 INT cchTempBufMax = 0, savCchTextMax = 0;
846 UINT realNotifCode;
847 LPWSTR pszTempBuf = NULL, savPszText = NULL;
849 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
851 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
852 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
855 if (convertToAnsi || convertToUnicode)
857 if (notificationCode != LVN_GETDISPINFOW)
859 cchTempBufMax = convertToUnicode ?
860 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
861 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
863 else
865 cchTempBufMax = pdi->item.cchTextMax;
866 *pdi->item.pszText = 0; /* make sure we don't process garbage */
869 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
870 if (!pszTempBuf) return FALSE;
872 if (convertToUnicode)
873 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
874 pszTempBuf, cchTempBufMax);
875 else
876 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
877 cchTempBufMax, NULL, NULL);
879 savCchTextMax = pdi->item.cchTextMax;
880 savPszText = pdi->item.pszText;
881 pdi->item.pszText = pszTempBuf;
882 pdi->item.cchTextMax = cchTempBufMax;
885 if (infoPtr->notifyFormat == NFR_ANSI)
886 realNotifCode = get_ansi_notification(notificationCode);
887 else
888 realNotifCode = notificationCode;
889 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
890 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
892 if (convertToUnicode || convertToAnsi)
894 if (convertToUnicode) /* note : pointer can be changed by app ! */
895 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
896 savCchTextMax, NULL, NULL);
897 else
898 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
899 savPszText, savCchTextMax);
900 pdi->item.pszText = savPszText; /* restores our buffer */
901 pdi->item.cchTextMax = savCchTextMax;
902 Free (pszTempBuf);
904 return bResult;
907 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
908 const RECT *rcBounds, const LVITEMW *lplvItem)
910 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
911 lpnmlvcd->nmcd.hdc = hdc;
912 lpnmlvcd->nmcd.rc = *rcBounds;
913 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
914 lpnmlvcd->clrText = infoPtr->clrText;
915 if (!lplvItem) return;
916 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
917 lpnmlvcd->iSubItem = lplvItem->iSubItem;
918 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
919 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
920 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
921 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
924 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
926 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
927 DWORD result;
929 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
930 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
931 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
932 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
933 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
934 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
935 return result;
938 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
940 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
941 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
942 if (lpnmlvcd->clrText == CLR_DEFAULT)
943 lpnmlvcd->clrText = comctl32_color.clrWindowText;
945 /* apparently, for selected items, we have to override the returned values */
946 if (!SubItem)
948 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
950 if (infoPtr->bFocus)
952 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
953 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
955 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
957 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
958 lpnmlvcd->clrText = comctl32_color.clrBtnText;
963 /* Set the text attributes */
964 if (lpnmlvcd->clrTextBk != CLR_NONE)
966 SetBkMode(hdc, OPAQUE);
967 SetBkColor(hdc,lpnmlvcd->clrTextBk);
969 else
970 SetBkMode(hdc, TRANSPARENT);
971 SetTextColor(hdc, lpnmlvcd->clrText);
974 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
976 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
979 /******** Item iterator functions **********************************/
981 static RANGES ranges_create(int count);
982 static void ranges_destroy(RANGES ranges);
983 static BOOL ranges_add(RANGES ranges, RANGE range);
984 static BOOL ranges_del(RANGES ranges, RANGE range);
985 static void ranges_dump(RANGES ranges);
987 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
989 RANGE range = { nItem, nItem + 1 };
991 return ranges_add(ranges, range);
994 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
996 RANGE range = { nItem, nItem + 1 };
998 return ranges_del(ranges, range);
1001 /***
1002 * ITERATOR DOCUMENTATION
1004 * The iterator functions allow for easy, and convenient iteration
1005 * over items of interest in the list. Typically, you create a
1006 * iterator, use it, and destroy it, as such:
1007 * ITERATOR i;
1009 * iterator_xxxitems(&i, ...);
1010 * while (iterator_{prev,next}(&i)
1012 * //code which uses i.nItem
1014 * iterator_destroy(&i);
1016 * where xxx is either: framed, or visible.
1017 * Note that it is important that the code destroys the iterator
1018 * after it's done with it, as the creation of the iterator may
1019 * allocate memory, which thus needs to be freed.
1021 * You can iterate both forwards, and backwards through the list,
1022 * by using iterator_next or iterator_prev respectively.
1024 * Lower numbered items are draw on top of higher number items in
1025 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1026 * items may overlap). So, to test items, you should use
1027 * iterator_next
1028 * which lists the items top to bottom (in Z-order).
1029 * For drawing items, you should use
1030 * iterator_prev
1031 * which lists the items bottom to top (in Z-order).
1032 * If you keep iterating over the items after the end-of-items
1033 * marker (-1) is returned, the iterator will start from the
1034 * beginning. Typically, you don't need to test for -1,
1035 * because iterator_{next,prev} will return TRUE if more items
1036 * are to be iterated over, or FALSE otherwise.
1038 * Note: the iterator is defined to be bidirectional. That is,
1039 * any number of prev followed by any number of next, or
1040 * five versa, should leave the iterator at the same item:
1041 * prev * n, next * n = next * n, prev * n
1043 * The iterator has a notion of an out-of-order, special item,
1044 * which sits at the start of the list. This is used in
1045 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1046 * which needs to be first, as it may overlap other items.
1048 * The code is a bit messy because we have:
1049 * - a special item to deal with
1050 * - simple range, or composite range
1051 * - empty range.
1052 * If you find bugs, or want to add features, please make sure you
1053 * always check/modify *both* iterator_prev, and iterator_next.
1056 /****
1057 * This function iterates through the items in increasing order,
1058 * but prefixed by the special item, then -1. That is:
1059 * special, 1, 2, 3, ..., n, -1.
1060 * Each item is listed only once.
1062 static inline BOOL iterator_next(ITERATOR* i)
1064 if (i->nItem == -1)
1066 i->nItem = i->nSpecial;
1067 if (i->nItem != -1) return TRUE;
1069 if (i->nItem == i->nSpecial)
1071 if (i->ranges) i->index = 0;
1072 goto pickarange;
1075 i->nItem++;
1076 testitem:
1077 if (i->nItem == i->nSpecial) i->nItem++;
1078 if (i->nItem < i->range.upper) return TRUE;
1080 pickarange:
1081 if (i->ranges)
1083 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1084 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1085 else goto end;
1087 else if (i->nItem >= i->range.upper) goto end;
1089 i->nItem = i->range.lower;
1090 if (i->nItem >= 0) goto testitem;
1091 end:
1092 i->nItem = -1;
1093 return FALSE;
1096 /****
1097 * This function iterates through the items in decreasing order,
1098 * followed by the special item, then -1. That is:
1099 * n, n-1, ..., 3, 2, 1, special, -1.
1100 * Each item is listed only once.
1102 static inline BOOL iterator_prev(ITERATOR* i)
1104 BOOL start = FALSE;
1106 if (i->nItem == -1)
1108 start = TRUE;
1109 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1110 goto pickarange;
1112 if (i->nItem == i->nSpecial)
1114 i->nItem = -1;
1115 return FALSE;
1118 testitem:
1119 i->nItem--;
1120 if (i->nItem == i->nSpecial) i->nItem--;
1121 if (i->nItem >= i->range.lower) return TRUE;
1123 pickarange:
1124 if (i->ranges)
1126 if (i->index > 0)
1127 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1128 else goto end;
1130 else if (!start && i->nItem < i->range.lower) goto end;
1132 i->nItem = i->range.upper;
1133 if (i->nItem > 0) goto testitem;
1134 end:
1135 return (i->nItem = i->nSpecial) != -1;
1138 static RANGE iterator_range(const ITERATOR *i)
1140 RANGE range;
1142 if (!i->ranges) return i->range;
1144 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1146 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1147 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1149 else range.lower = range.upper = 0;
1151 return range;
1154 /***
1155 * Releases resources associated with this ierator.
1157 static inline void iterator_destroy(const ITERATOR *i)
1159 ranges_destroy(i->ranges);
1162 /***
1163 * Create an empty iterator.
1165 static inline BOOL iterator_empty(ITERATOR* i)
1167 ZeroMemory(i, sizeof(*i));
1168 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1169 return TRUE;
1172 /***
1173 * Create an iterator over a range.
1175 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1177 iterator_empty(i);
1178 i->range = range;
1179 return TRUE;
1182 /***
1183 * Create an iterator over a bunch of ranges.
1184 * Please note that the iterator will take ownership of the ranges,
1185 * and will free them upon destruction.
1187 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1189 iterator_empty(i);
1190 i->ranges = ranges;
1191 return TRUE;
1194 /***
1195 * Creates an iterator over the items which intersect lprc.
1197 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1199 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1200 RECT frame = *lprc, rcItem, rcTemp;
1201 POINT Origin;
1203 /* in case we fail, we want to return an empty iterator */
1204 if (!iterator_empty(i)) return FALSE;
1206 LISTVIEW_GetOrigin(infoPtr, &Origin);
1208 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1209 OffsetRect(&frame, -Origin.x, -Origin.y);
1211 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1213 INT nItem;
1215 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1217 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1218 if (IntersectRect(&rcTemp, &rcItem, lprc))
1219 i->nSpecial = infoPtr->nFocusedItem;
1221 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1222 /* to do better here, we need to have PosX, and PosY sorted */
1223 TRACE("building icon ranges:\n");
1224 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1226 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1227 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1228 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1229 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1230 if (IntersectRect(&rcTemp, &rcItem, &frame))
1231 ranges_additem(i->ranges, nItem);
1233 return TRUE;
1235 else if (uView == LVS_REPORT)
1237 RANGE range;
1239 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1240 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1242 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1243 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1244 if (range.upper <= range.lower) return TRUE;
1245 if (!iterator_rangeitems(i, range)) return FALSE;
1246 TRACE(" report=%s\n", debugrange(&i->range));
1248 else
1250 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1251 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1252 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1253 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1254 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1255 INT lower = nFirstCol * nPerCol + nFirstRow;
1256 RANGE item_range;
1257 INT nCol;
1259 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1260 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1262 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1264 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1265 TRACE("building list ranges:\n");
1266 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1268 item_range.lower = nCol * nPerCol + nFirstRow;
1269 if(item_range.lower >= infoPtr->nItemCount) break;
1270 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1271 TRACE(" list=%s\n", debugrange(&item_range));
1272 ranges_add(i->ranges, item_range);
1276 return TRUE;
1279 /***
1280 * Creates an iterator over the items which intersect the visible region of hdc.
1282 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1284 POINT Origin, Position;
1285 RECT rcItem, rcClip;
1286 INT rgntype;
1288 rgntype = GetClipBox(hdc, &rcClip);
1289 if (rgntype == NULLREGION) return iterator_empty(i);
1290 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1291 if (rgntype == SIMPLEREGION) return TRUE;
1293 /* first deal with the special item */
1294 if (i->nSpecial != -1)
1296 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1297 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1300 /* if we can't deal with the region, we'll just go with the simple range */
1301 LISTVIEW_GetOrigin(infoPtr, &Origin);
1302 TRACE("building visible range:\n");
1303 if (!i->ranges && i->range.lower < i->range.upper)
1305 if (!(i->ranges = ranges_create(50))) return TRUE;
1306 if (!ranges_add(i->ranges, i->range))
1308 ranges_destroy(i->ranges);
1309 i->ranges = 0;
1310 return TRUE;
1314 /* now delete the invisible items from the list */
1315 while(iterator_next(i))
1317 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1318 rcItem.left = Position.x + Origin.x;
1319 rcItem.top = Position.y + Origin.y;
1320 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1321 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1322 if (!RectVisible(hdc, &rcItem))
1323 ranges_delitem(i->ranges, i->nItem);
1325 /* the iterator should restart on the next iterator_next */
1326 TRACE("done\n");
1328 return TRUE;
1331 /******** Misc helper functions ************************************/
1333 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1334 WPARAM wParam, LPARAM lParam, BOOL isW)
1336 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1337 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1340 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1342 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1344 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1345 (uView == LVS_ICON || uView == LVS_SMALLICON);
1348 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1350 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1351 if(state == 1 || state == 2)
1353 LVITEMW lvitem;
1354 state ^= 3;
1355 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1356 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1357 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1361 /******** Internal API functions ************************************/
1363 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1365 static COLUMN_INFO mainItem;
1367 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1368 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1369 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1372 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1374 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1375 HINSTANCE hInst;
1377 if (infoPtr->hwndHeader) return 0;
1379 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1381 /* setup creation flags */
1382 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1383 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1385 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1387 /* create header */
1388 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1389 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1390 if (!infoPtr->hwndHeader) return -1;
1392 /* set header unicode format */
1393 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1395 /* set header font */
1396 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1398 LISTVIEW_UpdateSize(infoPtr);
1400 return 0;
1403 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1405 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1408 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1410 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1413 /* used to handle collapse main item column case */
1414 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1416 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1417 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1420 /* Listview invalidation functions: use _only_ these functions to invalidate */
1422 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1424 return infoPtr->bRedraw;
1427 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1429 if(!is_redrawing(infoPtr)) return;
1430 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1431 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1434 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1436 RECT rcBox;
1438 if(!is_redrawing(infoPtr)) return;
1439 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1440 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1443 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1445 POINT Origin, Position;
1446 RECT rcBox;
1448 if(!is_redrawing(infoPtr)) return;
1449 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1450 LISTVIEW_GetOrigin(infoPtr, &Origin);
1451 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1452 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1453 rcBox.top = 0;
1454 rcBox.bottom = infoPtr->nItemHeight;
1455 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1456 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1459 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1461 LISTVIEW_InvalidateRect(infoPtr, NULL);
1464 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1466 RECT rcCol;
1468 if(!is_redrawing(infoPtr)) return;
1469 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1470 rcCol.top = infoPtr->rcList.top;
1471 rcCol.bottom = infoPtr->rcList.bottom;
1472 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1475 /***
1476 * DESCRIPTION:
1477 * Retrieves the number of items that can fit vertically in the client area.
1479 * PARAMETER(S):
1480 * [I] infoPtr : valid pointer to the listview structure
1482 * RETURN:
1483 * Number of items per row.
1485 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1487 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1489 return max(nListWidth/infoPtr->nItemWidth, 1);
1492 /***
1493 * DESCRIPTION:
1494 * Retrieves the number of items that can fit horizontally in the client
1495 * area.
1497 * PARAMETER(S):
1498 * [I] infoPtr : valid pointer to the listview structure
1500 * RETURN:
1501 * Number of items per column.
1503 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1505 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1507 return max(nListHeight / infoPtr->nItemHeight, 1);
1511 /*************************************************************************
1512 * LISTVIEW_ProcessLetterKeys
1514 * Processes keyboard messages generated by pressing the letter keys
1515 * on the keyboard.
1516 * What this does is perform a case insensitive search from the
1517 * current position with the following quirks:
1518 * - If two chars or more are pressed in quick succession we search
1519 * for the corresponding string (e.g. 'abc').
1520 * - If there is a delay we wipe away the current search string and
1521 * restart with just that char.
1522 * - If the user keeps pressing the same character, whether slowly or
1523 * fast, so that the search string is entirely composed of this
1524 * character ('aaaaa' for instance), then we search for first item
1525 * that starting with that character.
1526 * - If the user types the above character in quick succession, then
1527 * we must also search for the corresponding string ('aaaaa'), and
1528 * go to that string if there is a match.
1530 * PARAMETERS
1531 * [I] hwnd : handle to the window
1532 * [I] charCode : the character code, the actual character
1533 * [I] keyData : key data
1535 * RETURNS
1537 * Zero.
1539 * BUGS
1541 * - The current implementation has a list of characters it will
1542 * accept and it ignores everything else. In particular it will
1543 * ignore accentuated characters which seems to match what
1544 * Windows does. But I'm not sure it makes sense to follow
1545 * Windows there.
1546 * - We don't sound a beep when the search fails.
1548 * SEE ALSO
1550 * TREEVIEW_ProcessLetterKeys
1552 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1554 INT nItem;
1555 INT endidx,idx;
1556 LVITEMW item;
1557 WCHAR buffer[MAX_PATH];
1558 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1560 /* simple parameter checking */
1561 if (!charCode || !keyData) return 0;
1563 /* only allow the valid WM_CHARs through */
1564 if (!isalnumW(charCode) &&
1565 charCode != '.' && charCode != '`' && charCode != '!' &&
1566 charCode != '@' && charCode != '#' && charCode != '$' &&
1567 charCode != '%' && charCode != '^' && charCode != '&' &&
1568 charCode != '*' && charCode != '(' && charCode != ')' &&
1569 charCode != '-' && charCode != '_' && charCode != '+' &&
1570 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1571 charCode != '}' && charCode != '[' && charCode != '{' &&
1572 charCode != '/' && charCode != '?' && charCode != '>' &&
1573 charCode != '<' && charCode != ',' && charCode != '~')
1574 return 0;
1576 /* if there's one item or less, there is no where to go */
1577 if (infoPtr->nItemCount <= 1) return 0;
1579 /* update the search parameters */
1580 infoPtr->lastKeyPressTimestamp = GetTickCount();
1581 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1582 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1583 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1584 if (infoPtr->charCode != charCode)
1585 infoPtr->charCode = charCode = 0;
1586 } else {
1587 infoPtr->charCode=charCode;
1588 infoPtr->szSearchParam[0]=charCode;
1589 infoPtr->nSearchParamLength=1;
1590 /* Redundant with the 1 char string */
1591 charCode=0;
1594 /* and search from the current position */
1595 nItem=-1;
1596 if (infoPtr->nFocusedItem >= 0) {
1597 endidx=infoPtr->nFocusedItem;
1598 idx=endidx;
1599 /* if looking for single character match,
1600 * then we must always move forward
1602 if (infoPtr->nSearchParamLength == 1)
1603 idx++;
1604 } else {
1605 endidx=infoPtr->nItemCount;
1606 idx=0;
1609 /* Let application handle this for virtual listview */
1610 if (infoPtr->dwStyle & LVS_OWNERDATA)
1612 NMLVFINDITEMW nmlv;
1613 LVFINDINFOW lvfi;
1615 ZeroMemory(&lvfi, sizeof(lvfi));
1616 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1617 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1618 lvfi.psz = infoPtr->szSearchParam;
1619 nmlv.iStart = idx;
1620 nmlv.lvfi = lvfi;
1622 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1624 if (nItem != -1)
1625 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1627 return 0;
1630 do {
1631 if (idx == infoPtr->nItemCount) {
1632 if (endidx == infoPtr->nItemCount || endidx == 0)
1633 break;
1634 idx=0;
1637 /* get item */
1638 item.mask = LVIF_TEXT;
1639 item.iItem = idx;
1640 item.iSubItem = 0;
1641 item.pszText = buffer;
1642 item.cchTextMax = MAX_PATH;
1643 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1645 /* check for a match */
1646 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1647 nItem=idx;
1648 break;
1649 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1650 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1651 /* This would work but we must keep looking for a longer match */
1652 nItem=idx;
1654 idx++;
1655 } while (idx != endidx);
1657 if (nItem != -1)
1658 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1660 return 0;
1663 /*************************************************************************
1664 * LISTVIEW_UpdateHeaderSize [Internal]
1666 * Function to resize the header control
1668 * PARAMS
1669 * [I] hwnd : handle to a window
1670 * [I] nNewScrollPos : scroll pos to set
1672 * RETURNS
1673 * None.
1675 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1677 RECT winRect;
1678 POINT point[2];
1680 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1682 if (!infoPtr->hwndHeader) return;
1684 GetWindowRect(infoPtr->hwndHeader, &winRect);
1685 point[0].x = winRect.left;
1686 point[0].y = winRect.top;
1687 point[1].x = winRect.right;
1688 point[1].y = winRect.bottom;
1690 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1691 point[0].x = -nNewScrollPos;
1692 point[1].x += nNewScrollPos;
1694 SetWindowPos(infoPtr->hwndHeader,0,
1695 point[0].x,point[0].y,point[1].x,point[1].y,
1696 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1697 SWP_NOZORDER | SWP_NOACTIVATE);
1700 /***
1701 * DESCRIPTION:
1702 * Update the scrollbars. This functions should be called whenever
1703 * the content, size or view changes.
1705 * PARAMETER(S):
1706 * [I] infoPtr : valid pointer to the listview structure
1708 * RETURN:
1709 * None
1711 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1713 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1714 SCROLLINFO horzInfo, vertInfo;
1715 INT dx, dy;
1717 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1719 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1720 horzInfo.cbSize = sizeof(SCROLLINFO);
1721 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1723 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1724 if (uView == LVS_LIST)
1726 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1727 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1729 /* scroll by at least one column per page */
1730 if(horzInfo.nPage < infoPtr->nItemWidth)
1731 horzInfo.nPage = infoPtr->nItemWidth;
1733 horzInfo.nPage /= infoPtr->nItemWidth;
1735 else if (uView == LVS_REPORT)
1737 horzInfo.nMax = infoPtr->nItemWidth;
1739 else /* LVS_ICON, or LVS_SMALLICON */
1741 RECT rcView;
1743 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1746 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1747 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1748 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1749 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1750 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1752 /* Setting the horizontal scroll can change the listview size
1753 * (and potentially everything else) so we need to recompute
1754 * everything again for the vertical scroll
1757 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1758 vertInfo.cbSize = sizeof(SCROLLINFO);
1759 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1761 if (uView == LVS_REPORT)
1763 vertInfo.nMax = infoPtr->nItemCount;
1765 /* scroll by at least one page */
1766 if(vertInfo.nPage < infoPtr->nItemHeight)
1767 vertInfo.nPage = infoPtr->nItemHeight;
1769 if (infoPtr->nItemHeight > 0)
1770 vertInfo.nPage /= infoPtr->nItemHeight;
1772 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1774 RECT rcView;
1776 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1779 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1780 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1781 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1782 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1783 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1785 /* Change of the range may have changed the scroll pos. If so move the content */
1786 if (dx != 0 || dy != 0)
1788 RECT listRect;
1789 listRect = infoPtr->rcList;
1790 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1791 SW_ERASE | SW_INVALIDATE);
1794 /* Update the Header Control */
1795 if (uView == LVS_REPORT)
1797 horzInfo.fMask = SIF_POS;
1798 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1799 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1804 /***
1805 * DESCRIPTION:
1806 * Shows/hides the focus rectangle.
1808 * PARAMETER(S):
1809 * [I] infoPtr : valid pointer to the listview structure
1810 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1812 * RETURN:
1813 * None
1815 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1817 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1818 HDC hdc;
1820 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1822 if (infoPtr->nFocusedItem < 0) return;
1824 /* we need some gymnastics in ICON mode to handle large items */
1825 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1827 RECT rcBox;
1829 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1830 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1832 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1833 return;
1837 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1839 /* for some reason, owner draw should work only in report mode */
1840 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1842 DRAWITEMSTRUCT dis;
1843 LVITEMW item;
1845 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1846 HFONT hOldFont = SelectObject(hdc, hFont);
1848 item.iItem = infoPtr->nFocusedItem;
1849 item.iSubItem = 0;
1850 item.mask = LVIF_PARAM;
1851 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1853 ZeroMemory(&dis, sizeof(dis));
1854 dis.CtlType = ODT_LISTVIEW;
1855 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1856 dis.itemID = item.iItem;
1857 dis.itemAction = ODA_FOCUS;
1858 if (fShow) dis.itemState |= ODS_FOCUS;
1859 dis.hwndItem = infoPtr->hwndSelf;
1860 dis.hDC = hdc;
1861 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1862 dis.itemData = item.lParam;
1864 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1866 SelectObject(hdc, hOldFont);
1868 else
1870 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1872 done:
1873 ReleaseDC(infoPtr->hwndSelf, hdc);
1876 /***
1877 * Invalidates all visible selected items.
1879 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1881 ITERATOR i;
1883 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1884 while(iterator_next(&i))
1886 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1887 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1889 iterator_destroy(&i);
1893 /***
1894 * DESCRIPTION: [INTERNAL]
1895 * Computes an item's (left,top) corner, relative to rcView.
1896 * That is, the position has NOT been made relative to the Origin.
1897 * This is deliberate, to avoid computing the Origin over, and
1898 * over again, when this function is called in a loop. Instead,
1899 * one can factor the computation of the Origin before the loop,
1900 * and offset the value returned by this function, on every iteration.
1902 * PARAMETER(S):
1903 * [I] infoPtr : valid pointer to the listview structure
1904 * [I] nItem : item number
1905 * [O] lpptOrig : item top, left corner
1907 * RETURN:
1908 * None.
1910 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1912 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1914 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1916 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1918 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1919 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1921 else if (uView == LVS_LIST)
1923 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1924 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1925 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1927 else /* LVS_REPORT */
1929 lpptPosition->x = 0;
1930 lpptPosition->y = nItem * infoPtr->nItemHeight;
1934 /***
1935 * DESCRIPTION: [INTERNAL]
1936 * Compute the rectangles of an item. This is to localize all
1937 * the computations in one place. If you are not interested in some
1938 * of these values, simply pass in a NULL -- the function is smart
1939 * enough to compute only what's necessary. The function computes
1940 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1941 * one, the BOX rectangle. This rectangle is very cheap to compute,
1942 * and is guaranteed to contain all the other rectangles. Computing
1943 * the ICON rect is also cheap, but all the others are potentially
1944 * expensive. This gives an easy and effective optimization when
1945 * searching (like point inclusion, or rectangle intersection):
1946 * first test against the BOX, and if TRUE, test against the desired
1947 * rectangle.
1948 * If the function does not have all the necessary information
1949 * to computed the requested rectangles, will crash with a
1950 * failed assertion. This is done so we catch all programming
1951 * errors, given that the function is called only from our code.
1953 * We have the following 'special' meanings for a few fields:
1954 * * If LVIS_FOCUSED is set, we assume the item has the focus
1955 * This is important in ICON mode, where it might get a larger
1956 * then usual rectangle
1958 * Please note that subitem support works only in REPORT mode.
1960 * PARAMETER(S):
1961 * [I] infoPtr : valid pointer to the listview structure
1962 * [I] lpLVItem : item to compute the measures for
1963 * [O] lprcBox : ptr to Box rectangle
1964 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1965 * [0] lprcSelectBox : ptr to select box rectangle
1966 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1967 * [O] lprcIcon : ptr to Icon rectangle
1968 * Same as LVM_GETITEMRECT with LVIR_ICON
1969 * [O] lprcStateIcon: ptr to State Icon rectangle
1970 * [O] lprcLabel : ptr to Label rectangle
1971 * Same as LVM_GETITEMRECT with LVIR_LABEL
1973 * RETURN:
1974 * None.
1976 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1977 LPRECT lprcBox, LPRECT lprcSelectBox,
1978 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1980 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1981 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1982 RECT Box, SelectBox, Icon, Label;
1983 COLUMN_INFO *lpColumnInfo = NULL;
1984 SIZE labelSize = { 0, 0 };
1986 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1988 /* Be smart and try to figure out the minimum we have to do */
1989 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1990 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1992 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1993 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1995 if (lprcSelectBox) doSelectBox = TRUE;
1996 if (lprcLabel) doLabel = TRUE;
1997 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1998 if (doSelectBox)
2000 doIcon = TRUE;
2001 doLabel = TRUE;
2004 /************************************************************/
2005 /* compute the box rectangle (it should be cheap to do) */
2006 /************************************************************/
2007 if (lpLVItem->iSubItem || uView == LVS_REPORT)
2008 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2010 if (lpLVItem->iSubItem)
2012 Box = lpColumnInfo->rcHeader;
2014 else
2016 Box.left = 0;
2017 Box.right = infoPtr->nItemWidth;
2019 Box.top = 0;
2020 Box.bottom = infoPtr->nItemHeight;
2022 /******************************************************************/
2023 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2024 /******************************************************************/
2025 if (doIcon)
2027 LONG state_width = 0;
2029 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2030 state_width = infoPtr->iconStateSize.cx;
2032 if (uView == LVS_ICON)
2034 Icon.left = Box.left + state_width;
2035 if (infoPtr->himlNormal)
2036 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2037 Icon.top = Box.top + ICON_TOP_PADDING;
2038 Icon.right = Icon.left;
2039 Icon.bottom = Icon.top;
2040 if (infoPtr->himlNormal)
2042 Icon.right += infoPtr->iconSize.cx;
2043 Icon.bottom += infoPtr->iconSize.cy;
2046 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2048 Icon.left = Box.left + state_width;
2050 if (uView == LVS_REPORT)
2051 Icon.left += REPORT_MARGINX;
2053 Icon.top = Box.top;
2054 Icon.right = Icon.left;
2055 if (infoPtr->himlSmall &&
2056 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2057 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2058 Icon.right += infoPtr->iconSize.cx;
2059 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2061 if(lprcIcon) *lprcIcon = Icon;
2062 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2064 /* TODO: is this correct? */
2065 if (lprcStateIcon)
2067 lprcStateIcon->left = Icon.left - state_width;
2068 lprcStateIcon->right = Icon.left;
2069 lprcStateIcon->top = Icon.top;
2070 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2071 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2074 else Icon.right = 0;
2076 /************************************************************/
2077 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2078 /************************************************************/
2079 if (doLabel)
2081 /* calculate how far to the right can the label stretch */
2082 Label.right = Box.right;
2083 if (uView == LVS_REPORT)
2085 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2088 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2090 labelSize.cx = infoPtr->nItemWidth;
2091 labelSize.cy = infoPtr->nItemHeight;
2092 goto calc_label;
2095 /* we need the text in non owner draw mode */
2096 assert(lpLVItem->mask & LVIF_TEXT);
2097 if (is_textT(lpLVItem->pszText, TRUE))
2099 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2100 HDC hdc = GetDC(infoPtr->hwndSelf);
2101 HFONT hOldFont = SelectObject(hdc, hFont);
2102 UINT uFormat;
2103 RECT rcText;
2105 /* compute rough rectangle where the label will go */
2106 SetRectEmpty(&rcText);
2107 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2108 rcText.bottom = infoPtr->nItemHeight;
2109 if (uView == LVS_ICON)
2110 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2112 /* now figure out the flags */
2113 if (uView == LVS_ICON)
2114 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2115 else
2116 uFormat = LV_SL_DT_FLAGS;
2118 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2120 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2121 labelSize.cy = rcText.bottom - rcText.top;
2123 SelectObject(hdc, hOldFont);
2124 ReleaseDC(infoPtr->hwndSelf, hdc);
2127 calc_label:
2128 if (uView == LVS_ICON)
2130 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2131 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2132 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2133 Label.right = Label.left + labelSize.cx;
2134 Label.bottom = Label.top + infoPtr->nItemHeight;
2135 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2137 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2138 labelSize.cy /= infoPtr->ntmHeight;
2139 labelSize.cy = max(labelSize.cy, 1);
2140 labelSize.cy *= infoPtr->ntmHeight;
2142 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2144 else if (uView == LVS_REPORT)
2146 Label.left = Icon.right;
2147 Label.top = Box.top;
2148 Label.right = lpColumnInfo->rcHeader.right;
2149 Label.bottom = Label.top + infoPtr->nItemHeight;
2151 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2153 Label.left = Icon.right;
2154 Label.top = Box.top;
2155 Label.right = min(Label.left + labelSize.cx, Label.right);
2156 Label.bottom = Label.top + infoPtr->nItemHeight;
2159 if (lprcLabel) *lprcLabel = Label;
2160 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2163 /************************************************************/
2164 /* compute STATEICON bounding box */
2165 /************************************************************/
2166 if (doSelectBox)
2168 if (uView == LVS_REPORT)
2170 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2171 SelectBox.top = Box.top;
2172 SelectBox.bottom = Box.bottom;
2173 if (lpLVItem->iSubItem == 0)
2175 /* we need the indent in report mode */
2176 assert(lpLVItem->mask & LVIF_INDENT);
2177 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2179 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2181 else
2183 UnionRect(&SelectBox, &Icon, &Label);
2185 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2186 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2189 /* Fix the Box if necessary */
2190 if (lprcBox)
2192 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2193 else *lprcBox = Box;
2195 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2198 /***
2199 * DESCRIPTION: [INTERNAL]
2201 * PARAMETER(S):
2202 * [I] infoPtr : valid pointer to the listview structure
2203 * [I] nItem : item number
2204 * [O] lprcBox : ptr to Box rectangle
2206 * RETURN:
2207 * None.
2209 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2211 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2212 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2213 POINT Position, Origin;
2214 LVITEMW lvItem;
2216 LISTVIEW_GetOrigin(infoPtr, &Origin);
2217 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2219 /* Be smart and try to figure out the minimum we have to do */
2220 lvItem.mask = 0;
2221 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2222 lvItem.mask |= LVIF_TEXT;
2223 lvItem.iItem = nItem;
2224 lvItem.iSubItem = 0;
2225 lvItem.pszText = szDispText;
2226 lvItem.cchTextMax = DISP_TEXT_SIZE;
2227 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2228 if (uView == LVS_ICON)
2230 lvItem.mask |= LVIF_STATE;
2231 lvItem.stateMask = LVIS_FOCUSED;
2232 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2234 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2236 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2240 /***
2241 * DESCRIPTION:
2242 * Returns the current icon position, and advances it along the top.
2243 * The returned position is not offset by Origin.
2245 * PARAMETER(S):
2246 * [I] infoPtr : valid pointer to the listview structure
2247 * [O] lpPos : will get the current icon position
2249 * RETURN:
2250 * None
2252 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2254 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2256 *lpPos = infoPtr->currIconPos;
2258 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2259 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2261 infoPtr->currIconPos.x = 0;
2262 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2266 /***
2267 * DESCRIPTION:
2268 * Returns the current icon position, and advances it down the left edge.
2269 * The returned position is not offset by Origin.
2271 * PARAMETER(S):
2272 * [I] infoPtr : valid pointer to the listview structure
2273 * [O] lpPos : will get the current icon position
2275 * RETURN:
2276 * None
2278 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2280 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2282 *lpPos = infoPtr->currIconPos;
2284 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2285 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2287 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2288 infoPtr->currIconPos.y = 0;
2292 /***
2293 * DESCRIPTION:
2294 * Moves an icon to the specified position.
2295 * It takes care of invalidating the item, etc.
2297 * PARAMETER(S):
2298 * [I] infoPtr : valid pointer to the listview structure
2299 * [I] nItem : the item to move
2300 * [I] lpPos : the new icon position
2301 * [I] isNew : flags the item as being new
2303 * RETURN:
2304 * Success: TRUE
2305 * Failure: FALSE
2307 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2309 POINT old;
2311 if (!isNew)
2313 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2314 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2316 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2317 LISTVIEW_InvalidateItem(infoPtr, nItem);
2320 /* Allocating a POINTER for every item is too resource intensive,
2321 * so we'll keep the (x,y) in different arrays */
2322 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2323 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2325 LISTVIEW_InvalidateItem(infoPtr, nItem);
2327 return TRUE;
2330 /***
2331 * DESCRIPTION:
2332 * Arranges listview items in icon display mode.
2334 * PARAMETER(S):
2335 * [I] infoPtr : valid pointer to the listview structure
2336 * [I] nAlignCode : alignment code
2338 * RETURN:
2339 * SUCCESS : TRUE
2340 * FAILURE : FALSE
2342 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2344 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2345 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2346 POINT pos;
2347 INT i;
2349 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2351 TRACE("nAlignCode=%d\n", nAlignCode);
2353 if (nAlignCode == LVA_DEFAULT)
2355 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2356 else nAlignCode = LVA_ALIGNTOP;
2359 switch (nAlignCode)
2361 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2362 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2363 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2364 default: return FALSE;
2367 infoPtr->bAutoarrange = TRUE;
2368 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2369 for (i = 0; i < infoPtr->nItemCount; i++)
2371 next_pos(infoPtr, &pos);
2372 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2375 return TRUE;
2378 /***
2379 * DESCRIPTION:
2380 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2382 * PARAMETER(S):
2383 * [I] infoPtr : valid pointer to the listview structure
2384 * [O] lprcView : bounding rectangle
2386 * RETURN:
2387 * SUCCESS : TRUE
2388 * FAILURE : FALSE
2390 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2392 INT i, x, y;
2394 SetRectEmpty(lprcView);
2396 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2398 case LVS_ICON:
2399 case LVS_SMALLICON:
2400 for (i = 0; i < infoPtr->nItemCount; i++)
2402 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2403 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2404 lprcView->right = max(lprcView->right, x);
2405 lprcView->bottom = max(lprcView->bottom, y);
2407 if (infoPtr->nItemCount > 0)
2409 lprcView->right += infoPtr->nItemWidth;
2410 lprcView->bottom += infoPtr->nItemHeight;
2412 break;
2414 case LVS_LIST:
2415 y = LISTVIEW_GetCountPerColumn(infoPtr);
2416 x = infoPtr->nItemCount / y;
2417 if (infoPtr->nItemCount % y) x++;
2418 lprcView->right = x * infoPtr->nItemWidth;
2419 lprcView->bottom = y * infoPtr->nItemHeight;
2420 break;
2422 case LVS_REPORT:
2423 lprcView->right = infoPtr->nItemWidth;
2424 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2425 break;
2429 /***
2430 * DESCRIPTION:
2431 * Retrieves the bounding rectangle of all the items.
2433 * PARAMETER(S):
2434 * [I] infoPtr : valid pointer to the listview structure
2435 * [O] lprcView : bounding rectangle
2437 * RETURN:
2438 * SUCCESS : TRUE
2439 * FAILURE : FALSE
2441 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2443 POINT ptOrigin;
2445 TRACE("(lprcView=%p)\n", lprcView);
2447 if (!lprcView) return FALSE;
2449 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2450 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2451 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2453 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2455 return TRUE;
2458 /***
2459 * DESCRIPTION:
2460 * Retrieves the subitem pointer associated with the subitem index.
2462 * PARAMETER(S):
2463 * [I] hdpaSubItems : DPA handle for a specific item
2464 * [I] nSubItem : index of subitem
2466 * RETURN:
2467 * SUCCESS : subitem pointer
2468 * FAILURE : NULL
2470 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2472 SUBITEM_INFO *lpSubItem;
2473 INT i;
2475 /* we should binary search here if need be */
2476 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2478 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2479 if (lpSubItem->iSubItem == nSubItem)
2480 return lpSubItem;
2483 return NULL;
2487 /***
2488 * DESCRIPTION:
2489 * Calculates the desired item width.
2491 * PARAMETER(S):
2492 * [I] infoPtr : valid pointer to the listview structure
2494 * RETURN:
2495 * The desired item width.
2497 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2499 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2500 INT nItemWidth = 0;
2502 TRACE("uView=%d\n", uView);
2504 if (uView == LVS_ICON)
2505 nItemWidth = infoPtr->iconSpacing.cx;
2506 else if (uView == LVS_REPORT)
2508 RECT rcHeader;
2510 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2512 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2513 nItemWidth = rcHeader.right;
2516 else /* LVS_SMALLICON, or LVS_LIST */
2518 INT i;
2520 for (i = 0; i < infoPtr->nItemCount; i++)
2521 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2523 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2524 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2526 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2529 return max(nItemWidth, 1);
2532 /***
2533 * DESCRIPTION:
2534 * Calculates the desired item height.
2536 * PARAMETER(S):
2537 * [I] infoPtr : valid pointer to the listview structure
2539 * RETURN:
2540 * The desired item height.
2542 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2544 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2545 INT nItemHeight;
2547 TRACE("uView=%d\n", uView);
2549 if (uView == LVS_ICON)
2550 nItemHeight = infoPtr->iconSpacing.cy;
2551 else
2553 nItemHeight = infoPtr->ntmHeight;
2554 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2555 nItemHeight++;
2556 if (infoPtr->himlState)
2557 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2558 if (infoPtr->himlSmall)
2559 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2560 if (infoPtr->himlState || infoPtr->himlSmall)
2561 nItemHeight += HEIGHT_PADDING;
2562 if (infoPtr->nMeasureItemHeight > 0)
2563 nItemHeight = infoPtr->nMeasureItemHeight;
2566 return max(nItemHeight, 1);
2569 /***
2570 * DESCRIPTION:
2571 * Updates the width, and height of an item.
2573 * PARAMETER(S):
2574 * [I] infoPtr : valid pointer to the listview structure
2576 * RETURN:
2577 * None.
2579 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2581 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2582 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2586 /***
2587 * DESCRIPTION:
2588 * Retrieves and saves important text metrics info for the current
2589 * Listview font.
2591 * PARAMETER(S):
2592 * [I] infoPtr : valid pointer to the listview structure
2595 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2597 HDC hdc = GetDC(infoPtr->hwndSelf);
2598 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2599 HFONT hOldFont = SelectObject(hdc, hFont);
2600 TEXTMETRICW tm;
2601 SIZE sz;
2603 if (GetTextMetricsW(hdc, &tm))
2605 infoPtr->ntmHeight = tm.tmHeight;
2606 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2609 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2610 infoPtr->nEllipsisWidth = sz.cx;
2612 SelectObject(hdc, hOldFont);
2613 ReleaseDC(infoPtr->hwndSelf, hdc);
2615 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2618 /***
2619 * DESCRIPTION:
2620 * A compare function for ranges
2622 * PARAMETER(S)
2623 * [I] range1 : pointer to range 1;
2624 * [I] range2 : pointer to range 2;
2625 * [I] flags : flags
2627 * RETURNS:
2628 * > 0 : if range 1 > range 2
2629 * < 0 : if range 2 > range 1
2630 * = 0 : if range intersects range 2
2632 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2634 INT cmp;
2636 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2637 cmp = -1;
2638 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2639 cmp = 1;
2640 else
2641 cmp = 0;
2643 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2645 return cmp;
2648 #if DEBUG_RANGES
2649 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2650 #else
2651 #define ranges_check(ranges, desc) do { } while(0)
2652 #endif
2654 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2656 INT i;
2657 RANGE *prev, *curr;
2659 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2660 assert (ranges);
2661 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2662 ranges_dump(ranges);
2663 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2665 prev = DPA_GetPtr(ranges->hdpa, 0);
2666 assert (prev->lower >= 0 && prev->lower < prev->upper);
2667 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2669 curr = DPA_GetPtr(ranges->hdpa, i);
2670 assert (prev->upper <= curr->lower);
2671 assert (curr->lower < curr->upper);
2672 prev = curr;
2675 TRACE("--- Done checking---\n");
2678 static RANGES ranges_create(int count)
2680 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2681 if (!ranges) return NULL;
2682 ranges->hdpa = DPA_Create(count);
2683 if (ranges->hdpa) return ranges;
2684 Free(ranges);
2685 return NULL;
2688 static void ranges_clear(RANGES ranges)
2690 INT i;
2692 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2693 Free(DPA_GetPtr(ranges->hdpa, i));
2694 DPA_DeleteAllPtrs(ranges->hdpa);
2698 static void ranges_destroy(RANGES ranges)
2700 if (!ranges) return;
2701 ranges_clear(ranges);
2702 DPA_Destroy(ranges->hdpa);
2703 Free(ranges);
2706 static RANGES ranges_clone(RANGES ranges)
2708 RANGES clone;
2709 INT i;
2711 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2713 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2715 RANGE *newrng = Alloc(sizeof(RANGE));
2716 if (!newrng) goto fail;
2717 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2718 DPA_SetPtr(clone->hdpa, i, newrng);
2720 return clone;
2722 fail:
2723 TRACE ("clone failed\n");
2724 ranges_destroy(clone);
2725 return NULL;
2728 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2730 INT i;
2732 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2733 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2735 return ranges;
2738 static void ranges_dump(RANGES ranges)
2740 INT i;
2742 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2743 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2746 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2748 RANGE srchrng = { nItem, nItem + 1 };
2750 TRACE("(nItem=%d)\n", nItem);
2751 ranges_check(ranges, "before contain");
2752 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2755 static INT ranges_itemcount(RANGES ranges)
2757 INT i, count = 0;
2759 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2761 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2762 count += sel->upper - sel->lower;
2765 return count;
2768 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2770 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2771 INT index;
2773 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2774 if (index == -1) return TRUE;
2776 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2778 chkrng = DPA_GetPtr(ranges->hdpa, index);
2779 if (chkrng->lower >= nItem)
2780 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2781 if (chkrng->upper > nItem)
2782 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2784 return TRUE;
2787 static BOOL ranges_add(RANGES ranges, RANGE range)
2789 RANGE srchrgn;
2790 INT index;
2792 TRACE("(%s)\n", debugrange(&range));
2793 ranges_check(ranges, "before add");
2795 /* try find overlapping regions first */
2796 srchrgn.lower = range.lower - 1;
2797 srchrgn.upper = range.upper + 1;
2798 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2800 if (index == -1)
2802 RANGE *newrgn;
2804 TRACE("Adding new range\n");
2806 /* create the brand new range to insert */
2807 newrgn = Alloc(sizeof(RANGE));
2808 if(!newrgn) goto fail;
2809 *newrgn = range;
2811 /* figure out where to insert it */
2812 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2813 TRACE("index=%d\n", index);
2814 if (index == -1) index = 0;
2816 /* and get it over with */
2817 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2819 Free(newrgn);
2820 goto fail;
2823 else
2825 RANGE *chkrgn, *mrgrgn;
2826 INT fromindex, mergeindex;
2828 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2829 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2831 chkrgn->lower = min(range.lower, chkrgn->lower);
2832 chkrgn->upper = max(range.upper, chkrgn->upper);
2834 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2836 /* merge now common ranges */
2837 fromindex = 0;
2838 srchrgn.lower = chkrgn->lower - 1;
2839 srchrgn.upper = chkrgn->upper + 1;
2843 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2844 if (mergeindex == -1) break;
2845 if (mergeindex == index)
2847 fromindex = index + 1;
2848 continue;
2851 TRACE("Merge with index %i\n", mergeindex);
2853 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2854 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2855 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2856 Free(mrgrgn);
2857 DPA_DeletePtr(ranges->hdpa, mergeindex);
2858 if (mergeindex < index) index --;
2859 } while(1);
2862 ranges_check(ranges, "after add");
2863 return TRUE;
2865 fail:
2866 ranges_check(ranges, "failed add");
2867 return FALSE;
2870 static BOOL ranges_del(RANGES ranges, RANGE range)
2872 RANGE *chkrgn;
2873 INT index;
2875 TRACE("(%s)\n", debugrange(&range));
2876 ranges_check(ranges, "before del");
2878 /* we don't use DPAS_SORTED here, since we need *
2879 * to find the first overlapping range */
2880 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2881 while(index != -1)
2883 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2885 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2887 /* case 1: Same range */
2888 if ( (chkrgn->upper == range.upper) &&
2889 (chkrgn->lower == range.lower) )
2891 DPA_DeletePtr(ranges->hdpa, index);
2892 break;
2894 /* case 2: engulf */
2895 else if ( (chkrgn->upper <= range.upper) &&
2896 (chkrgn->lower >= range.lower) )
2898 DPA_DeletePtr(ranges->hdpa, index);
2900 /* case 3: overlap upper */
2901 else if ( (chkrgn->upper <= range.upper) &&
2902 (chkrgn->lower < range.lower) )
2904 chkrgn->upper = range.lower;
2906 /* case 4: overlap lower */
2907 else if ( (chkrgn->upper > range.upper) &&
2908 (chkrgn->lower >= range.lower) )
2910 chkrgn->lower = range.upper;
2911 break;
2913 /* case 5: fully internal */
2914 else
2916 RANGE tmprgn = *chkrgn, *newrgn;
2918 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2919 newrgn->lower = chkrgn->lower;
2920 newrgn->upper = range.lower;
2921 chkrgn->lower = range.upper;
2922 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2924 Free(newrgn);
2925 goto fail;
2927 chkrgn = &tmprgn;
2928 break;
2931 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2934 ranges_check(ranges, "after del");
2935 return TRUE;
2937 fail:
2938 ranges_check(ranges, "failed del");
2939 return FALSE;
2942 /***
2943 * DESCRIPTION:
2944 * Removes all selection ranges
2946 * Parameters(s):
2947 * [I] infoPtr : valid pointer to the listview structure
2948 * [I] toSkip : item range to skip removing the selection
2950 * RETURNS:
2951 * SUCCESS : TRUE
2952 * FAILURE : FALSE
2954 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2956 LVITEMW lvItem;
2957 ITERATOR i;
2958 RANGES clone;
2960 TRACE("()\n");
2962 lvItem.state = 0;
2963 lvItem.stateMask = LVIS_SELECTED;
2965 /* need to clone the DPA because callbacks can change it */
2966 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2967 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2968 while(iterator_next(&i))
2969 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2970 /* note that the iterator destructor will free the cloned range */
2971 iterator_destroy(&i);
2973 return TRUE;
2976 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2978 RANGES toSkip;
2980 if (!(toSkip = ranges_create(1))) return FALSE;
2981 if (nItem != -1) ranges_additem(toSkip, nItem);
2982 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2983 ranges_destroy(toSkip);
2984 return TRUE;
2987 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2989 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2992 /***
2993 * DESCRIPTION:
2994 * Retrieves the number of items that are marked as selected.
2996 * PARAMETER(S):
2997 * [I] infoPtr : valid pointer to the listview structure
2999 * RETURN:
3000 * Number of items selected.
3002 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3004 INT nSelectedCount = 0;
3006 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3008 INT i;
3009 for (i = 0; i < infoPtr->nItemCount; i++)
3011 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3012 nSelectedCount++;
3015 else
3016 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3018 TRACE("nSelectedCount=%d\n", nSelectedCount);
3019 return nSelectedCount;
3022 /***
3023 * DESCRIPTION:
3024 * Manages the item focus.
3026 * PARAMETER(S):
3027 * [I] infoPtr : valid pointer to the listview structure
3028 * [I] nItem : item index
3030 * RETURN:
3031 * TRUE : focused item changed
3032 * FALSE : focused item has NOT changed
3034 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3036 INT oldFocus = infoPtr->nFocusedItem;
3037 LVITEMW lvItem;
3039 if (nItem == infoPtr->nFocusedItem) return FALSE;
3041 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3042 lvItem.stateMask = LVIS_FOCUSED;
3043 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3045 return oldFocus != infoPtr->nFocusedItem;
3048 /* Helper function for LISTVIEW_ShiftIndices *only* */
3049 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3051 if (nShiftItem < nItem) return nShiftItem;
3053 if (nShiftItem > nItem) return nShiftItem + direction;
3055 if (direction > 0) return nShiftItem + direction;
3057 return min(nShiftItem, infoPtr->nItemCount - 1);
3061 * DESCRIPTION:
3062 * Updates the various indices after an item has been inserted or deleted.
3064 * PARAMETER(S):
3065 * [I] infoPtr : valid pointer to the listview structure
3066 * [I] nItem : item index
3067 * [I] direction : Direction of shift, +1 or -1.
3069 * RETURN:
3070 * None
3072 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3074 INT nNewFocus;
3075 BOOL bOldChange;
3077 /* temporarily disable change notification while shifting items */
3078 bOldChange = infoPtr->bDoChangeNotify;
3079 infoPtr->bDoChangeNotify = FALSE;
3081 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3083 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3085 assert(abs(direction) == 1);
3087 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3089 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3090 if (nNewFocus != infoPtr->nFocusedItem)
3091 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3093 /* But we are not supposed to modify nHotItem! */
3095 infoPtr->bDoChangeNotify = bOldChange;
3100 * DESCRIPTION:
3101 * Adds a block of selections.
3103 * PARAMETER(S):
3104 * [I] infoPtr : valid pointer to the listview structure
3105 * [I] nItem : item index
3107 * RETURN:
3108 * Whether the window is still valid.
3110 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3112 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3113 INT nLast = max(infoPtr->nSelectionMark, nItem);
3114 HWND hwndSelf = infoPtr->hwndSelf;
3115 NMLVODSTATECHANGE nmlv;
3116 LVITEMW item;
3117 BOOL bOldChange;
3118 INT i;
3120 /* Temporarily disable change notification
3121 * If the control is LVS_OWNERDATA, we need to send
3122 * only one LVN_ODSTATECHANGED notification.
3123 * See MSDN documentation for LVN_ITEMCHANGED.
3125 bOldChange = infoPtr->bDoChangeNotify;
3126 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3128 if (nFirst == -1) nFirst = nItem;
3130 item.state = LVIS_SELECTED;
3131 item.stateMask = LVIS_SELECTED;
3133 for (i = nFirst; i <= nLast; i++)
3134 LISTVIEW_SetItemState(infoPtr,i,&item);
3136 ZeroMemory(&nmlv, sizeof(nmlv));
3137 nmlv.iFrom = nFirst;
3138 nmlv.iTo = nLast;
3139 nmlv.uNewState = 0;
3140 nmlv.uOldState = item.state;
3142 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3143 if (!IsWindow(hwndSelf))
3144 return FALSE;
3145 infoPtr->bDoChangeNotify = bOldChange;
3146 return TRUE;
3150 /***
3151 * DESCRIPTION:
3152 * Sets a single group selection.
3154 * PARAMETER(S):
3155 * [I] infoPtr : valid pointer to the listview structure
3156 * [I] nItem : item index
3158 * RETURN:
3159 * None
3161 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3163 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3164 RANGES selection;
3165 LVITEMW item;
3166 ITERATOR i;
3167 BOOL bOldChange;
3169 if (!(selection = ranges_create(100))) return;
3171 item.state = LVIS_SELECTED;
3172 item.stateMask = LVIS_SELECTED;
3174 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3176 if (infoPtr->nSelectionMark == -1)
3178 infoPtr->nSelectionMark = nItem;
3179 ranges_additem(selection, nItem);
3181 else
3183 RANGE sel;
3185 sel.lower = min(infoPtr->nSelectionMark, nItem);
3186 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3187 ranges_add(selection, sel);
3190 else
3192 RECT rcItem, rcSel, rcSelMark;
3193 POINT ptItem;
3195 rcItem.left = LVIR_BOUNDS;
3196 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3197 rcSelMark.left = LVIR_BOUNDS;
3198 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3199 UnionRect(&rcSel, &rcItem, &rcSelMark);
3200 iterator_frameditems(&i, infoPtr, &rcSel);
3201 while(iterator_next(&i))
3203 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3204 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3206 iterator_destroy(&i);
3209 /* disable per item notifications on LVS_OWNERDATA style
3210 FIXME: single LVN_ODSTATECHANGED should be used */
3211 bOldChange = infoPtr->bDoChangeNotify;
3212 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3214 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3217 iterator_rangesitems(&i, selection);
3218 while(iterator_next(&i))
3219 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3220 /* this will also destroy the selection */
3221 iterator_destroy(&i);
3223 infoPtr->bDoChangeNotify = bOldChange;
3225 LISTVIEW_SetItemFocus(infoPtr, nItem);
3228 /***
3229 * DESCRIPTION:
3230 * Sets a single selection.
3232 * PARAMETER(S):
3233 * [I] infoPtr : valid pointer to the listview structure
3234 * [I] nItem : item index
3236 * RETURN:
3237 * None
3239 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3241 LVITEMW lvItem;
3243 TRACE("nItem=%d\n", nItem);
3245 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3247 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3248 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3249 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3251 infoPtr->nSelectionMark = nItem;
3254 /***
3255 * DESCRIPTION:
3256 * Set selection(s) with keyboard.
3258 * PARAMETER(S):
3259 * [I] infoPtr : valid pointer to the listview structure
3260 * [I] nItem : item index
3261 * [I] space : VK_SPACE code sent
3263 * RETURN:
3264 * SUCCESS : TRUE (needs to be repainted)
3265 * FAILURE : FALSE (nothing has changed)
3267 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3269 /* FIXME: pass in the state */
3270 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3271 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3272 BOOL bResult = FALSE;
3274 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3275 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3277 if (infoPtr->dwStyle & LVS_SINGLESEL)
3279 bResult = TRUE;
3280 LISTVIEW_SetSelection(infoPtr, nItem);
3282 else
3284 if (wShift)
3286 bResult = TRUE;
3287 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3289 else if (wCtrl)
3291 LVITEMW lvItem;
3292 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3293 lvItem.stateMask = LVIS_SELECTED;
3294 if (space)
3296 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3297 if (lvItem.state & LVIS_SELECTED)
3298 infoPtr->nSelectionMark = nItem;
3300 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3302 else
3304 bResult = TRUE;
3305 LISTVIEW_SetSelection(infoPtr, nItem);
3308 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3311 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3312 return bResult;
3315 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3317 LVHITTESTINFO lvHitTestInfo;
3319 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3320 lvHitTestInfo.pt.x = pt.x;
3321 lvHitTestInfo.pt.y = pt.y;
3323 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3325 lpLVItem->mask = LVIF_PARAM;
3326 lpLVItem->iItem = lvHitTestInfo.iItem;
3327 lpLVItem->iSubItem = 0;
3329 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3332 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3334 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3335 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3336 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3339 /***
3340 * DESCRIPTION:
3341 * Called when the mouse is being actively tracked and has hovered for a specified
3342 * amount of time
3344 * PARAMETER(S):
3345 * [I] infoPtr : valid pointer to the listview structure
3346 * [I] fwKeys : key indicator
3347 * [I] x,y : mouse position
3349 * RETURN:
3350 * 0 if the message was processed, non-zero if there was an error
3352 * INFO:
3353 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3354 * over the item for a certain period of time.
3357 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3359 if (LISTVIEW_isHotTracking(infoPtr))
3361 LVITEMW item;
3362 POINT pt;
3364 pt.x = x;
3365 pt.y = y;
3367 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3368 LISTVIEW_SetSelection(infoPtr, item.iItem);
3371 return 0;
3374 /***
3375 * DESCRIPTION:
3376 * Called whenever WM_MOUSEMOVE is received.
3378 * PARAMETER(S):
3379 * [I] infoPtr : valid pointer to the listview structure
3380 * [I] fwKeys : key indicator
3381 * [I] x,y : mouse position
3383 * RETURN:
3384 * 0 if the message is processed, non-zero if there was an error
3386 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3388 TRACKMOUSEEVENT trackinfo;
3390 if (!(fwKeys & MK_LBUTTON))
3391 infoPtr->bLButtonDown = FALSE;
3393 if (infoPtr->bLButtonDown)
3395 POINT tmp;
3396 RECT rect;
3397 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3398 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3400 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3401 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3402 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3403 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3405 tmp.x = x;
3406 tmp.y = y;
3408 if (!PtInRect(&rect, tmp))
3410 LVHITTESTINFO lvHitTestInfo;
3411 NMLISTVIEW nmlv;
3413 lvHitTestInfo.pt = infoPtr->ptClickPos;
3414 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3416 ZeroMemory(&nmlv, sizeof(nmlv));
3417 nmlv.iItem = lvHitTestInfo.iItem;
3418 nmlv.ptAction = infoPtr->ptClickPos;
3420 if (!infoPtr->bDragging)
3422 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3423 infoPtr->bDragging = TRUE;
3426 return 0;
3429 else
3430 infoPtr->bLButtonDown = FALSE;
3432 /* see if we are supposed to be tracking mouse hovering */
3433 if (LISTVIEW_isHotTracking(infoPtr)) {
3434 /* fill in the trackinfo struct */
3435 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3436 trackinfo.dwFlags = TME_QUERY;
3437 trackinfo.hwndTrack = infoPtr->hwndSelf;
3438 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3440 /* see if we are already tracking this hwnd */
3441 _TrackMouseEvent(&trackinfo);
3443 if(!(trackinfo.dwFlags & TME_HOVER)) {
3444 trackinfo.dwFlags = TME_HOVER;
3446 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3447 _TrackMouseEvent(&trackinfo);
3451 return 0;
3455 /***
3456 * Tests whether the item is assignable to a list with style lStyle
3458 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3460 if ( (lpLVItem->mask & LVIF_TEXT) &&
3461 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3462 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3464 return TRUE;
3468 /***
3469 * DESCRIPTION:
3470 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3472 * PARAMETER(S):
3473 * [I] infoPtr : valid pointer to the listview structure
3474 * [I] lpLVItem : valid pointer to new item attributes
3475 * [I] isNew : the item being set is being inserted
3476 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3477 * [O] bChanged : will be set to TRUE if the item really changed
3479 * RETURN:
3480 * SUCCESS : TRUE
3481 * FAILURE : FALSE
3483 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3485 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3486 ITEM_INFO *lpItem;
3487 NMLISTVIEW nmlv;
3488 UINT uChanged = 0;
3489 LVITEMW item;
3490 /* stateMask is ignored for LVM_INSERTITEM */
3491 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3493 TRACE("()\n");
3495 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3497 if (lpLVItem->mask == 0) return TRUE;
3499 if (infoPtr->dwStyle & LVS_OWNERDATA)
3501 /* a virtual listview only stores selection and focus */
3502 if (lpLVItem->mask & ~LVIF_STATE)
3503 return FALSE;
3504 lpItem = NULL;
3506 else
3508 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3509 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3510 assert (lpItem);
3513 /* we need to get the lParam and state of the item */
3514 item.iItem = lpLVItem->iItem;
3515 item.iSubItem = lpLVItem->iSubItem;
3516 item.mask = LVIF_STATE | LVIF_PARAM;
3517 item.stateMask = ~0;
3518 item.state = 0;
3519 item.lParam = 0;
3520 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3522 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3523 /* determine what fields will change */
3524 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3525 uChanged |= LVIF_STATE;
3527 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3528 uChanged |= LVIF_IMAGE;
3530 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3531 uChanged |= LVIF_PARAM;
3533 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3534 uChanged |= LVIF_INDENT;
3536 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3537 uChanged |= LVIF_TEXT;
3539 TRACE("uChanged=0x%x\n", uChanged);
3540 if (!uChanged) return TRUE;
3541 *bChanged = TRUE;
3543 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3544 nmlv.iItem = lpLVItem->iItem;
3545 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3546 nmlv.uOldState = item.state;
3547 nmlv.uChanged = uChanged;
3548 nmlv.lParam = item.lParam;
3550 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3551 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3552 /* are enabled */
3553 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3555 HWND hwndSelf = infoPtr->hwndSelf;
3557 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3558 return FALSE;
3559 if (!IsWindow(hwndSelf))
3560 return FALSE;
3563 /* copy information */
3564 if (lpLVItem->mask & LVIF_TEXT)
3565 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3567 if (lpLVItem->mask & LVIF_IMAGE)
3568 lpItem->hdr.iImage = lpLVItem->iImage;
3570 if (lpLVItem->mask & LVIF_PARAM)
3571 lpItem->lParam = lpLVItem->lParam;
3573 if (lpLVItem->mask & LVIF_INDENT)
3574 lpItem->iIndent = lpLVItem->iIndent;
3576 if (uChanged & LVIF_STATE)
3578 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3580 lpItem->state &= ~stateMask;
3581 lpItem->state |= (lpLVItem->state & stateMask);
3583 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3585 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3586 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3588 else if (stateMask & LVIS_SELECTED)
3590 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3592 /* if we are asked to change focus, and we manage it, do it */
3593 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3595 if (lpLVItem->state & LVIS_FOCUSED)
3597 if (infoPtr->nFocusedItem != -1)
3599 /* remove current focus */
3600 item.mask = LVIF_STATE;
3601 item.state = 0;
3602 item.stateMask = LVIS_FOCUSED;
3604 /* recurse with redrawing an item */
3605 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3608 infoPtr->nFocusedItem = lpLVItem->iItem;
3609 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3611 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3613 infoPtr->nFocusedItem = -1;
3618 /* if we're inserting the item, we're done */
3619 if (isNew) return TRUE;
3621 /* send LVN_ITEMCHANGED notification */
3622 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3623 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3625 return TRUE;
3628 /***
3629 * DESCRIPTION:
3630 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3632 * PARAMETER(S):
3633 * [I] infoPtr : valid pointer to the listview structure
3634 * [I] lpLVItem : valid pointer to new subitem attributes
3635 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3636 * [O] bChanged : will be set to TRUE if the item really changed
3638 * RETURN:
3639 * SUCCESS : TRUE
3640 * FAILURE : FALSE
3642 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3644 HDPA hdpaSubItems;
3645 SUBITEM_INFO *lpSubItem;
3647 /* we do not support subitems for virtual listviews */
3648 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3650 /* set subitem only if column is present */
3651 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3653 /* First do some sanity checks */
3654 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3655 particularly useful. We currently do not actually do anything with
3656 the flag on subitems.
3658 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3659 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3661 /* get the subitem structure, and create it if not there */
3662 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3663 assert (hdpaSubItems);
3665 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3666 if (!lpSubItem)
3668 SUBITEM_INFO *tmpSubItem;
3669 INT i;
3671 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3672 if (!lpSubItem) return FALSE;
3673 /* we could binary search here, if need be...*/
3674 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3676 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3677 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3679 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3681 Free(lpSubItem);
3682 return FALSE;
3684 lpSubItem->iSubItem = lpLVItem->iSubItem;
3685 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3686 *bChanged = TRUE;
3689 if (lpLVItem->mask & LVIF_IMAGE)
3690 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3692 lpSubItem->hdr.iImage = lpLVItem->iImage;
3693 *bChanged = TRUE;
3696 if (lpLVItem->mask & LVIF_TEXT)
3697 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3699 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3700 *bChanged = TRUE;
3703 return TRUE;
3706 /***
3707 * DESCRIPTION:
3708 * Sets item attributes.
3710 * PARAMETER(S):
3711 * [I] infoPtr : valid pointer to the listview structure
3712 * [I] lpLVItem : new item attributes
3713 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3715 * RETURN:
3716 * SUCCESS : TRUE
3717 * FAILURE : FALSE
3719 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3721 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3722 HWND hwndSelf = infoPtr->hwndSelf;
3723 LPWSTR pszText = NULL;
3724 BOOL bResult, bChanged = FALSE;
3726 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3728 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3729 return FALSE;
3731 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3732 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3734 pszText = lpLVItem->pszText;
3735 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3738 /* actually set the fields */
3739 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3741 if (lpLVItem->iSubItem)
3742 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3743 else
3744 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3745 if (!IsWindow(hwndSelf))
3746 return FALSE;
3748 /* redraw item, if necessary */
3749 if (bChanged && !infoPtr->bIsDrawing)
3751 /* this little optimization eliminates some nasty flicker */
3752 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3753 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3754 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3755 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3756 else
3757 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3759 /* restore text */
3760 if (pszText)
3762 textfreeT(lpLVItem->pszText, isW);
3763 lpLVItem->pszText = pszText;
3766 return bResult;
3769 /***
3770 * DESCRIPTION:
3771 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3773 * PARAMETER(S):
3774 * [I] infoPtr : valid pointer to the listview structure
3776 * RETURN:
3777 * item index
3779 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3781 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3782 INT nItem = 0;
3783 SCROLLINFO scrollInfo;
3785 scrollInfo.cbSize = sizeof(SCROLLINFO);
3786 scrollInfo.fMask = SIF_POS;
3788 if (uView == LVS_LIST)
3790 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3791 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3793 else if (uView == LVS_REPORT)
3795 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3796 nItem = scrollInfo.nPos;
3798 else
3800 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3801 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3804 TRACE("nItem=%d\n", nItem);
3806 return nItem;
3810 /***
3811 * DESCRIPTION:
3812 * Erases the background of the given rectangle
3814 * PARAMETER(S):
3815 * [I] infoPtr : valid pointer to the listview structure
3816 * [I] hdc : device context handle
3817 * [I] lprcBox : clipping rectangle
3819 * RETURN:
3820 * Success: TRUE
3821 * Failure: FALSE
3823 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3825 if (!infoPtr->hBkBrush) return FALSE;
3827 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3829 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3832 /***
3833 * DESCRIPTION:
3834 * Draws an item.
3836 * PARAMETER(S):
3837 * [I] infoPtr : valid pointer to the listview structure
3838 * [I] hdc : device context handle
3839 * [I] nItem : item index
3840 * [I] nSubItem : subitem index
3841 * [I] pos : item position in client coordinates
3842 * [I] cdmode : custom draw mode
3844 * RETURN:
3845 * Success: TRUE
3846 * Failure: FALSE
3848 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3850 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3851 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3852 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3853 DWORD cdsubitemmode = CDRF_DODEFAULT;
3854 LPRECT lprcFocus;
3855 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3856 NMLVCUSTOMDRAW nmlvcd;
3857 HIMAGELIST himl;
3858 LVITEMW lvItem;
3859 HFONT hOldFont;
3861 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3863 /* get information needed for drawing the item */
3864 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3865 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3866 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3867 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3868 lvItem.iItem = nItem;
3869 lvItem.iSubItem = nSubItem;
3870 lvItem.state = 0;
3871 lvItem.lParam = 0;
3872 lvItem.cchTextMax = DISP_TEXT_SIZE;
3873 lvItem.pszText = szDispText;
3874 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3875 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3876 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3877 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3878 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3880 /* now check if we need to update the focus rectangle */
3881 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3883 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3884 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3885 OffsetRect(&rcBox, pos.x, pos.y);
3886 OffsetRect(&rcSelect, pos.x, pos.y);
3887 OffsetRect(&rcIcon, pos.x, pos.y);
3888 OffsetRect(&rcStateIcon, pos.x, pos.y);
3889 OffsetRect(&rcLabel, pos.x, pos.y);
3890 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3891 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3892 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3894 /* fill in the custom draw structure */
3895 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3897 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3898 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3899 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3900 if (cdmode & CDRF_NOTIFYITEMDRAW)
3901 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3902 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3903 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3904 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3905 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3907 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3908 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3910 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3911 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3912 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3913 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3915 /* in full row select, subitems, will just use main item's colors */
3916 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3917 nmlvcd.clrTextBk = CLR_NONE;
3919 /* state icons */
3920 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3922 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3923 if (uStateImage)
3925 TRACE("uStateImage=%d\n", uStateImage);
3926 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3927 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3931 /* small icons */
3932 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3933 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3935 TRACE("iImage=%d\n", lvItem.iImage);
3936 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3937 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3938 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3941 /* Don't bother painting item being edited */
3942 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3944 /* FIXME: temporary hack */
3945 rcSelect.left = rcLabel.left;
3947 /* draw the selection background, if we're drawing the main item */
3948 if (nSubItem == 0)
3950 /* in icon mode, the label rect is really what we want to draw the
3951 * background for */
3952 if (uView == LVS_ICON)
3953 rcSelect = rcLabel;
3955 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3956 rcSelect.right = rcBox.right;
3958 if (nmlvcd.clrTextBk != CLR_NONE)
3959 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3960 if(lprcFocus) *lprcFocus = rcSelect;
3963 /* figure out the text drawing flags */
3964 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3965 if (uView == LVS_ICON)
3966 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3967 else if (nSubItem)
3969 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3971 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3972 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3973 default: uFormat |= DT_LEFT;
3976 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3978 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3979 else rcLabel.left += LABEL_HOR_PADDING;
3981 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3983 /* for GRIDLINES reduce the bottom so the text formats correctly */
3984 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3985 rcLabel.bottom--;
3987 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3989 postpaint:
3990 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3991 notify_postpaint(infoPtr, &nmlvcd);
3992 if (cdsubitemmode & CDRF_NEWFONT)
3993 SelectObject(hdc, hOldFont);
3994 return TRUE;
3997 /***
3998 * DESCRIPTION:
3999 * Draws listview items when in owner draw mode.
4001 * PARAMETER(S):
4002 * [I] infoPtr : valid pointer to the listview structure
4003 * [I] hdc : device context handle
4005 * RETURN:
4006 * None
4008 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4010 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4011 DWORD cditemmode = CDRF_DODEFAULT;
4012 NMLVCUSTOMDRAW nmlvcd;
4013 POINT Origin, Position;
4014 DRAWITEMSTRUCT dis;
4015 LVITEMW item;
4017 TRACE("()\n");
4019 ZeroMemory(&dis, sizeof(dis));
4021 /* Get scroll info once before loop */
4022 LISTVIEW_GetOrigin(infoPtr, &Origin);
4024 /* iterate through the invalidated rows */
4025 while(iterator_next(i))
4027 item.iItem = i->nItem;
4028 item.iSubItem = 0;
4029 item.mask = LVIF_PARAM | LVIF_STATE;
4030 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4031 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4033 dis.CtlType = ODT_LISTVIEW;
4034 dis.CtlID = uID;
4035 dis.itemID = item.iItem;
4036 dis.itemAction = ODA_DRAWENTIRE;
4037 dis.itemState = 0;
4038 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4039 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4040 dis.hwndItem = infoPtr->hwndSelf;
4041 dis.hDC = hdc;
4042 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4043 dis.rcItem.left = Position.x + Origin.x;
4044 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4045 dis.rcItem.top = Position.y + Origin.y;
4046 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4047 dis.itemData = item.lParam;
4049 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4052 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4053 * structure for the rest. of the paint cycle
4055 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4056 if (cdmode & CDRF_NOTIFYITEMDRAW)
4057 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4059 if (!(cditemmode & CDRF_SKIPDEFAULT))
4061 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4062 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4065 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4066 notify_postpaint(infoPtr, &nmlvcd);
4070 /***
4071 * DESCRIPTION:
4072 * Draws listview items when in report display mode.
4074 * PARAMETER(S):
4075 * [I] infoPtr : valid pointer to the listview structure
4076 * [I] hdc : device context handle
4077 * [I] cdmode : custom draw mode
4079 * RETURN:
4080 * None
4082 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4084 INT rgntype;
4085 RECT rcClip, rcItem;
4086 POINT Origin, Position;
4087 RANGE colRange;
4088 ITERATOR j;
4090 TRACE("()\n");
4092 /* figure out what to draw */
4093 rgntype = GetClipBox(hdc, &rcClip);
4094 if (rgntype == NULLREGION) return;
4096 /* Get scroll info once before loop */
4097 LISTVIEW_GetOrigin(infoPtr, &Origin);
4099 /* narrow down the columns we need to paint */
4100 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4102 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4103 if (rcItem.right + Origin.x >= rcClip.left) break;
4105 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4107 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4108 if (rcItem.left + Origin.x < rcClip.right) break;
4110 iterator_rangeitems(&j, colRange);
4112 /* in full row select, we _have_ to draw the main item */
4113 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4114 j.nSpecial = 0;
4116 /* iterate through the invalidated rows */
4117 while(iterator_next(i))
4119 /* iterate through the invalidated columns */
4120 while(iterator_next(&j))
4122 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4123 Position.x += Origin.x;
4124 Position.y += Origin.y;
4126 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4128 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4129 rcItem.top = 0;
4130 rcItem.bottom = infoPtr->nItemHeight;
4131 OffsetRect(&rcItem, Position.x, Position.y);
4132 if (!RectVisible(hdc, &rcItem)) continue;
4135 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4138 iterator_destroy(&j);
4141 /***
4142 * DESCRIPTION:
4143 * Draws the gridlines if necessary when in report display mode.
4145 * PARAMETER(S):
4146 * [I] infoPtr : valid pointer to the listview structure
4147 * [I] hdc : device context handle
4149 * RETURN:
4150 * None
4152 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4154 INT rgntype;
4155 INT y, itemheight;
4156 HPEN hPen, hOldPen;
4157 RECT rcClip, rcItem = {0};
4158 POINT Origin;
4159 RANGE colRange;
4160 ITERATOR j;
4161 BOOL rmost = FALSE;
4163 TRACE("()\n");
4165 /* figure out what to draw */
4166 rgntype = GetClipBox(hdc, &rcClip);
4167 if (rgntype == NULLREGION) return;
4169 /* Get scroll info once before loop */
4170 LISTVIEW_GetOrigin(infoPtr, &Origin);
4172 /* narrow down the columns we need to paint */
4173 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4175 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4176 if (rcItem.right + Origin.x >= rcClip.left) break;
4178 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4180 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4181 if (rcItem.left + Origin.x < rcClip.right) break;
4183 /* is right most vertical line visible? */
4184 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4186 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcItem);
4187 rmost = (rcItem.right + Origin.x < rcClip.right);
4190 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4192 hOldPen = SelectObject ( hdc, hPen );
4194 /* draw the vertical lines for the columns */
4195 iterator_rangeitems(&j, colRange);
4196 while(iterator_next(&j))
4198 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4199 if (rcItem.left == 0) continue; /* skip first column */
4200 rcItem.left += Origin.x;
4201 rcItem.right += Origin.x;
4202 rcItem.top = infoPtr->rcList.top;
4203 rcItem.bottom = infoPtr->rcList.bottom;
4204 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4205 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4206 LineTo (hdc, rcItem.left, rcItem.bottom);
4208 iterator_destroy(&j);
4209 /* draw rightmost grid line if visible */
4210 if (rmost)
4212 MoveToEx (hdc, rcItem.right, rcItem.top, NULL);
4213 LineTo (hdc, rcItem.right, rcItem.bottom);
4216 /* draw the horizontial lines for the rows */
4217 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4218 rcItem.left = infoPtr->rcList.left;
4219 rcItem.right = infoPtr->rcList.right;
4220 rcItem.bottom = rcItem.top = Origin.y - 1;
4221 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4222 LineTo(hdc, rcItem.right, rcItem.top);
4223 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4225 rcItem.bottom = rcItem.top = y;
4226 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4227 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4228 LineTo (hdc, rcItem.right, rcItem.top);
4231 SelectObject( hdc, hOldPen );
4232 DeleteObject( hPen );
4236 /***
4237 * DESCRIPTION:
4238 * Draws listview items when in list display mode.
4240 * PARAMETER(S):
4241 * [I] infoPtr : valid pointer to the listview structure
4242 * [I] hdc : device context handle
4243 * [I] cdmode : custom draw mode
4245 * RETURN:
4246 * None
4248 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4250 POINT Origin, Position;
4252 /* Get scroll info once before loop */
4253 LISTVIEW_GetOrigin(infoPtr, &Origin);
4255 while(iterator_prev(i))
4257 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4258 Position.x += Origin.x;
4259 Position.y += Origin.y;
4261 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4266 /***
4267 * DESCRIPTION:
4268 * Draws listview items.
4270 * PARAMETER(S):
4271 * [I] infoPtr : valid pointer to the listview structure
4272 * [I] hdc : device context handle
4273 * [I] prcErase : rect to be erased before refresh (may be NULL)
4275 * RETURN:
4276 * NoneX
4278 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4280 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4281 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4282 NMLVCUSTOMDRAW nmlvcd;
4283 HFONT hOldFont = 0;
4284 DWORD cdmode;
4285 INT oldBkMode = 0;
4286 RECT rcClient;
4287 ITERATOR i;
4288 HDC hdcOrig = hdc;
4289 HBITMAP hbmp = NULL;
4291 LISTVIEW_DUMP(infoPtr);
4293 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4294 TRACE("double buffering\n");
4296 hdc = CreateCompatibleDC(hdcOrig);
4297 if (!hdc) {
4298 ERR("Failed to create DC for backbuffer\n");
4299 return;
4301 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4302 infoPtr->rcList.bottom);
4303 if (!hbmp) {
4304 ERR("Failed to create bitmap for backbuffer\n");
4305 DeleteDC(hdc);
4306 return;
4309 SelectObject(hdc, hbmp);
4310 SelectObject(hdc, infoPtr->hFont);
4311 } else {
4312 /* Save dc values we're gonna trash while drawing
4313 * FIXME: Should be done in LISTVIEW_DrawItem() */
4314 hOldFont = SelectObject(hdc, infoPtr->hFont);
4315 oldBkMode = GetBkMode(hdc);
4316 oldBkColor = GetBkColor(hdc);
4317 oldTextColor = GetTextColor(hdc);
4320 infoPtr->bIsDrawing = TRUE;
4322 if (prcErase) {
4323 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4324 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4325 /* If no erasing was done (usually because RedrawWindow was called
4326 * with RDW_INVALIDATE only) we need to copy the old contents into
4327 * the backbuffer before continuing. */
4328 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4329 infoPtr->rcList.right - infoPtr->rcList.left,
4330 infoPtr->rcList.bottom - infoPtr->rcList.top,
4331 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4334 /* FIXME: Shouldn't need to do this */
4335 oldClrTextBk = infoPtr->clrTextBk;
4336 oldClrText = infoPtr->clrText;
4338 infoPtr->cditemmode = CDRF_DODEFAULT;
4340 GetClientRect(infoPtr->hwndSelf, &rcClient);
4341 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4342 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4343 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4344 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4346 /* Use these colors to draw the items */
4347 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4348 infoPtr->clrText = nmlvcd.clrText;
4350 /* nothing to draw */
4351 if(infoPtr->nItemCount == 0) goto enddraw;
4353 /* figure out what we need to draw */
4354 iterator_visibleitems(&i, infoPtr, hdc);
4356 /* send cache hint notification */
4357 if (infoPtr->dwStyle & LVS_OWNERDATA)
4359 RANGE range = iterator_range(&i);
4360 NMLVCACHEHINT nmlv;
4362 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4363 nmlv.iFrom = range.lower;
4364 nmlv.iTo = range.upper - 1;
4365 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4368 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4369 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4370 else
4372 if (uView == LVS_REPORT)
4373 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4374 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4375 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4377 /* if we have a focus rect, draw it */
4378 if (infoPtr->bFocus)
4379 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4381 iterator_destroy(&i);
4383 enddraw:
4384 /* For LVS_EX_GRIDLINES go and draw lines */
4385 /* This includes the case where there were *no* items */
4386 if ((uView == LVS_REPORT) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4387 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4389 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4390 notify_postpaint(infoPtr, &nmlvcd);
4392 infoPtr->clrTextBk = oldClrTextBk;
4393 infoPtr->clrText = oldClrText;
4395 if(hbmp) {
4396 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4397 infoPtr->rcList.right - infoPtr->rcList.left,
4398 infoPtr->rcList.bottom - infoPtr->rcList.top,
4399 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4401 DeleteObject(hbmp);
4402 DeleteDC(hdc);
4403 } else {
4404 SelectObject(hdc, hOldFont);
4405 SetBkMode(hdc, oldBkMode);
4406 SetBkColor(hdc, oldBkColor);
4407 SetTextColor(hdc, oldTextColor);
4410 infoPtr->bIsDrawing = FALSE;
4414 /***
4415 * DESCRIPTION:
4416 * Calculates the approximate width and height of a given number of items.
4418 * PARAMETER(S):
4419 * [I] infoPtr : valid pointer to the listview structure
4420 * [I] nItemCount : number of items
4421 * [I] wWidth : width
4422 * [I] wHeight : height
4424 * RETURN:
4425 * Returns a DWORD. The width in the low word and the height in high word.
4427 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4428 WORD wWidth, WORD wHeight)
4430 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4431 INT nItemCountPerColumn = 1;
4432 INT nColumnCount = 0;
4433 DWORD dwViewRect = 0;
4435 if (nItemCount == -1)
4436 nItemCount = infoPtr->nItemCount;
4438 if (uView == LVS_LIST)
4440 if (wHeight == 0xFFFF)
4442 /* use current height */
4443 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4446 if (wHeight < infoPtr->nItemHeight)
4447 wHeight = infoPtr->nItemHeight;
4449 if (nItemCount > 0)
4451 if (infoPtr->nItemHeight > 0)
4453 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4454 if (nItemCountPerColumn == 0)
4455 nItemCountPerColumn = 1;
4457 if (nItemCount % nItemCountPerColumn != 0)
4458 nColumnCount = nItemCount / nItemCountPerColumn;
4459 else
4460 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4464 /* Microsoft padding magic */
4465 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4466 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4468 dwViewRect = MAKELONG(wWidth, wHeight);
4470 else if (uView == LVS_REPORT)
4472 RECT rcBox;
4474 if (infoPtr->nItemCount > 0)
4476 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4477 wWidth = rcBox.right - rcBox.left;
4478 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4480 else
4482 /* use current height and width */
4483 if (wHeight == 0xffff)
4484 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4485 if (wWidth == 0xffff)
4486 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4489 dwViewRect = MAKELONG(wWidth, wHeight);
4491 else if (uView == LVS_SMALLICON)
4492 FIXME("uView == LVS_SMALLICON: not implemented\n");
4493 else if (uView == LVS_ICON)
4494 FIXME("uView == LVS_ICON: not implemented\n");
4496 return dwViewRect;
4500 /***
4501 * DESCRIPTION:
4502 * Create a drag image list for the specified item.
4504 * PARAMETER(S):
4505 * [I] infoPtr : valid pointer to the listview structure
4506 * [I] iItem : index of item
4507 * [O] lppt : Upper-left corner of the image
4509 * RETURN:
4510 * Returns a handle to the image list if successful, NULL otherwise.
4512 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4514 RECT rcItem;
4515 SIZE size;
4516 POINT pos;
4517 HDC hdc, hdcOrig;
4518 HBITMAP hbmp, hOldbmp;
4519 HIMAGELIST dragList = 0;
4520 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4522 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4523 return 0;
4525 rcItem.left = LVIR_BOUNDS;
4526 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4527 return 0;
4529 lppt->x = rcItem.left;
4530 lppt->y = rcItem.top;
4532 size.cx = rcItem.right - rcItem.left;
4533 size.cy = rcItem.bottom - rcItem.top;
4535 hdcOrig = GetDC(infoPtr->hwndSelf);
4536 hdc = CreateCompatibleDC(hdcOrig);
4537 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4538 hOldbmp = SelectObject(hdc, hbmp);
4540 rcItem.left = rcItem.top = 0;
4541 rcItem.right = size.cx;
4542 rcItem.bottom = size.cy;
4543 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4545 pos.x = pos.y = 0;
4546 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4548 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4549 SelectObject(hdc, hOldbmp);
4550 ImageList_Add(dragList, hbmp, 0);
4552 else
4553 SelectObject(hdc, hOldbmp);
4555 DeleteObject(hbmp);
4556 DeleteDC(hdc);
4557 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4559 TRACE("ret=%p\n", dragList);
4561 return dragList;
4565 /***
4566 * DESCRIPTION:
4567 * Removes all listview items and subitems.
4569 * PARAMETER(S):
4570 * [I] infoPtr : valid pointer to the listview structure
4572 * RETURN:
4573 * SUCCESS : TRUE
4574 * FAILURE : FALSE
4576 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4578 NMLISTVIEW nmlv;
4579 HDPA hdpaSubItems = NULL;
4580 BOOL bSuppress;
4581 ITEMHDR *hdrItem;
4582 INT i, j;
4584 TRACE("()\n");
4586 /* we do it directly, to avoid notifications */
4587 ranges_clear(infoPtr->selectionRanges);
4588 infoPtr->nSelectionMark = -1;
4589 infoPtr->nFocusedItem = -1;
4590 SetRectEmpty(&infoPtr->rcFocus);
4591 /* But we are supposed to leave nHotItem as is! */
4594 /* send LVN_DELETEALLITEMS notification */
4595 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4596 nmlv.iItem = -1;
4597 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4599 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4601 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4603 /* send LVN_DELETEITEM notification, if not suppressed
4604 and if it is not a virtual listview */
4605 if (!bSuppress) notify_deleteitem(infoPtr, i);
4606 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4607 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4609 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4610 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4611 Free(hdrItem);
4613 DPA_Destroy(hdpaSubItems);
4614 DPA_DeletePtr(infoPtr->hdpaItems, i);
4616 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4617 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4618 infoPtr->nItemCount --;
4621 if (!destroy)
4623 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4624 LISTVIEW_UpdateScroll(infoPtr);
4626 LISTVIEW_InvalidateList(infoPtr);
4628 return TRUE;
4631 /***
4632 * DESCRIPTION:
4633 * Scrolls, and updates the columns, when a column is changing width.
4635 * PARAMETER(S):
4636 * [I] infoPtr : valid pointer to the listview structure
4637 * [I] nColumn : column to scroll
4638 * [I] dx : amount of scroll, in pixels
4640 * RETURN:
4641 * None.
4643 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4645 COLUMN_INFO *lpColumnInfo;
4646 RECT rcOld, rcCol;
4647 POINT ptOrigin;
4648 INT nCol;
4650 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4651 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4652 rcCol = lpColumnInfo->rcHeader;
4653 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4654 rcCol.left = rcCol.right;
4656 /* adjust the other columns */
4657 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4659 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4660 lpColumnInfo->rcHeader.left += dx;
4661 lpColumnInfo->rcHeader.right += dx;
4664 /* do not update screen if not in report mode */
4665 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4667 /* if we have a focus, we must first erase the focus rect */
4668 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4670 /* Need to reset the item width when inserting a new column */
4671 infoPtr->nItemWidth += dx;
4673 LISTVIEW_UpdateScroll(infoPtr);
4674 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4676 /* scroll to cover the deleted column, and invalidate for redraw */
4677 rcOld = infoPtr->rcList;
4678 rcOld.left = ptOrigin.x + rcCol.left + dx;
4679 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4681 /* we can restore focus now */
4682 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4685 /***
4686 * DESCRIPTION:
4687 * Removes a column from the listview control.
4689 * PARAMETER(S):
4690 * [I] infoPtr : valid pointer to the listview structure
4691 * [I] nColumn : column index
4693 * RETURN:
4694 * SUCCESS : TRUE
4695 * FAILURE : FALSE
4697 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4699 RECT rcCol;
4701 TRACE("nColumn=%d\n", nColumn);
4703 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4704 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4706 /* While the MSDN specifically says that column zero should not be deleted,
4707 what actually happens is that the column itself is deleted but no items or subitems
4708 are removed.
4711 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4713 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4714 return FALSE;
4716 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4717 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4719 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4721 SUBITEM_INFO *lpSubItem, *lpDelItem;
4722 HDPA hdpaSubItems;
4723 INT nItem, nSubItem, i;
4725 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4727 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4728 nSubItem = 0;
4729 lpDelItem = 0;
4730 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4732 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4733 if (lpSubItem->iSubItem == nColumn)
4735 nSubItem = i;
4736 lpDelItem = lpSubItem;
4738 else if (lpSubItem->iSubItem > nColumn)
4740 lpSubItem->iSubItem--;
4744 /* if we found our subitem, zapp it */
4745 if (nSubItem > 0)
4747 /* free string */
4748 if (is_textW(lpDelItem->hdr.pszText))
4749 Free(lpDelItem->hdr.pszText);
4751 /* free item */
4752 Free(lpDelItem);
4754 /* free dpa memory */
4755 DPA_DeletePtr(hdpaSubItems, nSubItem);
4760 /* update the other column info */
4761 LISTVIEW_UpdateItemSize(infoPtr);
4762 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4763 LISTVIEW_InvalidateList(infoPtr);
4764 else
4765 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4767 return TRUE;
4770 /***
4771 * DESCRIPTION:
4772 * Invalidates the listview after an item's insertion or deletion.
4774 * PARAMETER(S):
4775 * [I] infoPtr : valid pointer to the listview structure
4776 * [I] nItem : item index
4777 * [I] dir : -1 if deleting, 1 if inserting
4779 * RETURN:
4780 * None
4782 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4784 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4785 INT nPerCol, nItemCol, nItemRow;
4786 RECT rcScroll;
4787 POINT Origin;
4789 /* if we don't refresh, what's the point of scrolling? */
4790 if (!is_redrawing(infoPtr)) return;
4792 assert (abs(dir) == 1);
4794 /* arrange icons if autoarrange is on */
4795 if (is_autoarrange(infoPtr))
4797 BOOL arrange = TRUE;
4798 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4799 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4800 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4803 /* scrollbars need updating */
4804 LISTVIEW_UpdateScroll(infoPtr);
4806 /* figure out the item's position */
4807 if (uView == LVS_REPORT)
4808 nPerCol = infoPtr->nItemCount + 1;
4809 else if (uView == LVS_LIST)
4810 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4811 else /* LVS_ICON, or LVS_SMALLICON */
4812 return;
4814 nItemCol = nItem / nPerCol;
4815 nItemRow = nItem % nPerCol;
4816 LISTVIEW_GetOrigin(infoPtr, &Origin);
4818 /* move the items below up a slot */
4819 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4820 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4821 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4822 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4823 OffsetRect(&rcScroll, Origin.x, Origin.y);
4824 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4825 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4827 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4828 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4829 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4832 /* report has only that column, so we're done */
4833 if (uView == LVS_REPORT) return;
4835 /* now for LISTs, we have to deal with the columns to the right */
4836 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4837 rcScroll.top = 0;
4838 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4839 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4840 OffsetRect(&rcScroll, Origin.x, Origin.y);
4841 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4842 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4843 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4846 /***
4847 * DESCRIPTION:
4848 * Removes an item from the listview control.
4850 * PARAMETER(S):
4851 * [I] infoPtr : valid pointer to the listview structure
4852 * [I] nItem : item index
4854 * RETURN:
4855 * SUCCESS : TRUE
4856 * FAILURE : FALSE
4858 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4860 LVITEMW item;
4861 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4862 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4864 TRACE("(nItem=%d)\n", nItem);
4866 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4868 /* remove selection, and focus */
4869 item.state = 0;
4870 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4871 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4873 /* send LVN_DELETEITEM notification. */
4874 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4876 /* we need to do this here, because we'll be deleting stuff */
4877 if (is_icon)
4878 LISTVIEW_InvalidateItem(infoPtr, nItem);
4880 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4882 HDPA hdpaSubItems;
4883 ITEMHDR *hdrItem;
4884 INT i;
4886 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4887 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4889 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4890 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4891 Free(hdrItem);
4893 DPA_Destroy(hdpaSubItems);
4896 if (is_icon)
4898 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4899 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4902 infoPtr->nItemCount--;
4903 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4905 /* now is the invalidation fun */
4906 if (!is_icon)
4907 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4908 return TRUE;
4912 /***
4913 * DESCRIPTION:
4914 * Callback implementation for editlabel control
4916 * PARAMETER(S):
4917 * [I] infoPtr : valid pointer to the listview structure
4918 * [I] pszText : modified text
4919 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4921 * RETURN:
4922 * SUCCESS : TRUE
4923 * FAILURE : FALSE
4925 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4927 HWND hwndSelf = infoPtr->hwndSelf;
4928 NMLVDISPINFOW dispInfo;
4929 INT editedItem = infoPtr->nEditLabelItem;
4930 BOOL bSame;
4932 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4934 infoPtr->nEditLabelItem = -1;
4936 ZeroMemory(&dispInfo, sizeof(dispInfo));
4937 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4938 dispInfo.item.iItem = editedItem;
4939 dispInfo.item.iSubItem = 0;
4940 dispInfo.item.stateMask = ~0;
4941 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4943 if (isW)
4944 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4945 else
4947 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4948 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4949 textfreeT(tmp, FALSE);
4951 if (bSame) return TRUE;
4953 /* add the text from the edit in */
4954 dispInfo.item.mask |= LVIF_TEXT;
4955 dispInfo.item.pszText = pszText;
4956 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4958 /* Do we need to update the Item Text */
4959 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4960 if (!IsWindow(hwndSelf))
4961 return FALSE;
4962 if (!pszText) return TRUE;
4964 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4966 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4967 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4968 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4970 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4971 return TRUE;
4975 ZeroMemory(&dispInfo, sizeof(dispInfo));
4976 dispInfo.item.mask = LVIF_TEXT;
4977 dispInfo.item.iItem = editedItem;
4978 dispInfo.item.iSubItem = 0;
4979 dispInfo.item.pszText = pszText;
4980 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4981 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4984 /***
4985 * DESCRIPTION:
4986 * Begin in place editing of specified list view item
4988 * PARAMETER(S):
4989 * [I] infoPtr : valid pointer to the listview structure
4990 * [I] nItem : item index
4991 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4993 * RETURN:
4994 * SUCCESS : TRUE
4995 * FAILURE : FALSE
4997 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4999 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5000 NMLVDISPINFOW dispInfo;
5001 RECT rect;
5002 HWND hwndSelf = infoPtr->hwndSelf;
5004 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5006 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5007 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5009 infoPtr->nEditLabelItem = nItem;
5011 /* Is the EditBox still there, if so remove it */
5012 if(infoPtr->hwndEdit != 0)
5014 SetFocus(infoPtr->hwndSelf);
5015 infoPtr->hwndEdit = 0;
5018 LISTVIEW_SetSelection(infoPtr, nItem);
5019 LISTVIEW_SetItemFocus(infoPtr, nItem);
5020 LISTVIEW_InvalidateItem(infoPtr, nItem);
5022 rect.left = LVIR_LABEL;
5023 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5025 ZeroMemory(&dispInfo, sizeof(dispInfo));
5026 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5027 dispInfo.item.iItem = nItem;
5028 dispInfo.item.iSubItem = 0;
5029 dispInfo.item.stateMask = ~0;
5030 dispInfo.item.pszText = szDispText;
5031 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5032 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5034 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
5035 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
5036 if (!infoPtr->hwndEdit) return 0;
5038 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5040 if (!IsWindow(hwndSelf))
5041 return 0;
5042 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5043 infoPtr->hwndEdit = 0;
5044 return 0;
5047 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5048 SetFocus(infoPtr->hwndEdit);
5049 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5050 return infoPtr->hwndEdit;
5054 /***
5055 * DESCRIPTION:
5056 * Ensures the specified item is visible, scrolling into view if necessary.
5058 * PARAMETER(S):
5059 * [I] infoPtr : valid pointer to the listview structure
5060 * [I] nItem : item index
5061 * [I] bPartial : partially or entirely visible
5063 * RETURN:
5064 * SUCCESS : TRUE
5065 * FAILURE : FALSE
5067 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5069 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5070 INT nScrollPosHeight = 0;
5071 INT nScrollPosWidth = 0;
5072 INT nHorzAdjust = 0;
5073 INT nVertAdjust = 0;
5074 INT nHorzDiff = 0;
5075 INT nVertDiff = 0;
5076 RECT rcItem, rcTemp;
5078 rcItem.left = LVIR_BOUNDS;
5079 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5081 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5083 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5085 /* scroll left/right, but in LVS_REPORT mode */
5086 if (uView == LVS_LIST)
5087 nScrollPosWidth = infoPtr->nItemWidth;
5088 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5089 nScrollPosWidth = 1;
5091 if (rcItem.left < infoPtr->rcList.left)
5093 nHorzAdjust = -1;
5094 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5096 else
5098 nHorzAdjust = 1;
5099 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5103 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5105 /* scroll up/down, but not in LVS_LIST mode */
5106 if (uView == LVS_REPORT)
5107 nScrollPosHeight = infoPtr->nItemHeight;
5108 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5109 nScrollPosHeight = 1;
5111 if (rcItem.top < infoPtr->rcList.top)
5113 nVertAdjust = -1;
5114 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5116 else
5118 nVertAdjust = 1;
5119 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5123 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5125 if (nScrollPosWidth)
5127 INT diff = nHorzDiff / nScrollPosWidth;
5128 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5129 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5132 if (nScrollPosHeight)
5134 INT diff = nVertDiff / nScrollPosHeight;
5135 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5136 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5139 return TRUE;
5142 /***
5143 * DESCRIPTION:
5144 * Searches for an item with specific characteristics.
5146 * PARAMETER(S):
5147 * [I] hwnd : window handle
5148 * [I] nStart : base item index
5149 * [I] lpFindInfo : item information to look for
5151 * RETURN:
5152 * SUCCESS : index of item
5153 * FAILURE : -1
5155 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5156 const LVFINDINFOW *lpFindInfo)
5158 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5159 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5160 BOOL bWrap = FALSE, bNearest = FALSE;
5161 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5162 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5163 POINT Position, Destination;
5164 LVITEMW lvItem;
5166 /* Search in virtual listviews should be done by application, not by
5167 listview control, so we just send LVN_ODFINDITEMW and return the result */
5168 if (infoPtr->dwStyle & LVS_OWNERDATA)
5170 NMLVFINDITEMW nmlv;
5172 nmlv.iStart = nStart;
5173 nmlv.lvfi = *lpFindInfo;
5174 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5177 if (!lpFindInfo || nItem < 0) return -1;
5179 lvItem.mask = 0;
5180 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5182 lvItem.mask |= LVIF_TEXT;
5183 lvItem.pszText = szDispText;
5184 lvItem.cchTextMax = DISP_TEXT_SIZE;
5187 if (lpFindInfo->flags & LVFI_WRAP)
5188 bWrap = TRUE;
5190 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5191 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5193 POINT Origin;
5194 RECT rcArea;
5196 LISTVIEW_GetOrigin(infoPtr, &Origin);
5197 Destination.x = lpFindInfo->pt.x - Origin.x;
5198 Destination.y = lpFindInfo->pt.y - Origin.y;
5199 switch(lpFindInfo->vkDirection)
5201 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5202 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5203 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5204 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5205 case VK_HOME: Destination.x = Destination.y = 0; break;
5206 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5207 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5208 case VK_END:
5209 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5210 Destination.x = rcArea.right;
5211 Destination.y = rcArea.bottom;
5212 break;
5213 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5215 bNearest = TRUE;
5217 else Destination.x = Destination.y = 0;
5219 /* if LVFI_PARAM is specified, all other flags are ignored */
5220 if (lpFindInfo->flags & LVFI_PARAM)
5222 lvItem.mask |= LVIF_PARAM;
5223 bNearest = FALSE;
5224 lvItem.mask &= ~LVIF_TEXT;
5227 again:
5228 for (; nItem < nLast; nItem++)
5230 lvItem.iItem = nItem;
5231 lvItem.iSubItem = 0;
5232 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5234 if (lvItem.mask & LVIF_PARAM)
5236 if (lpFindInfo->lParam == lvItem.lParam)
5237 return nItem;
5238 else
5239 continue;
5242 if (lvItem.mask & LVIF_TEXT)
5244 if (lpFindInfo->flags & LVFI_PARTIAL)
5246 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5248 else
5250 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5254 if (!bNearest) return nItem;
5256 /* This is very inefficient. To do a good job here,
5257 * we need a sorted array of (x,y) item positions */
5258 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5260 /* compute the distance^2 to the destination */
5261 xdist = Destination.x - Position.x;
5262 ydist = Destination.y - Position.y;
5263 dist = xdist * xdist + ydist * ydist;
5265 /* remember the distance, and item if it's closer */
5266 if (dist < mindist)
5268 mindist = dist;
5269 nNearestItem = nItem;
5273 if (bWrap)
5275 nItem = 0;
5276 nLast = min(nStart + 1, infoPtr->nItemCount);
5277 bWrap = FALSE;
5278 goto again;
5281 return nNearestItem;
5284 /***
5285 * DESCRIPTION:
5286 * Searches for an item with specific characteristics.
5288 * PARAMETER(S):
5289 * [I] hwnd : window handle
5290 * [I] nStart : base item index
5291 * [I] lpFindInfo : item information to look for
5293 * RETURN:
5294 * SUCCESS : index of item
5295 * FAILURE : -1
5297 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5298 const LVFINDINFOA *lpFindInfo)
5300 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5301 LVFINDINFOW fiw;
5302 INT res;
5303 LPWSTR strW = NULL;
5305 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5306 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5307 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5308 textfreeT(strW, FALSE);
5309 return res;
5312 /***
5313 * DESCRIPTION:
5314 * Retrieves the background image of the listview control.
5316 * PARAMETER(S):
5317 * [I] infoPtr : valid pointer to the listview structure
5318 * [O] lpBkImage : background image attributes
5320 * RETURN:
5321 * SUCCESS : TRUE
5322 * FAILURE : FALSE
5324 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5325 /* { */
5326 /* FIXME (listview, "empty stub!\n"); */
5327 /* return FALSE; */
5328 /* } */
5330 /***
5331 * DESCRIPTION:
5332 * Retrieves column attributes.
5334 * PARAMETER(S):
5335 * [I] infoPtr : valid pointer to the listview structure
5336 * [I] nColumn : column index
5337 * [IO] lpColumn : column information
5338 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5339 * otherwise it is in fact a LPLVCOLUMNA
5341 * RETURN:
5342 * SUCCESS : TRUE
5343 * FAILURE : FALSE
5345 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5347 COLUMN_INFO *lpColumnInfo;
5348 HDITEMW hdi;
5350 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5351 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5353 /* initialize memory */
5354 ZeroMemory(&hdi, sizeof(hdi));
5356 if (lpColumn->mask & LVCF_TEXT)
5358 hdi.mask |= HDI_TEXT;
5359 hdi.pszText = lpColumn->pszText;
5360 hdi.cchTextMax = lpColumn->cchTextMax;
5363 if (lpColumn->mask & LVCF_IMAGE)
5364 hdi.mask |= HDI_IMAGE;
5366 if (lpColumn->mask & LVCF_ORDER)
5367 hdi.mask |= HDI_ORDER;
5369 if (lpColumn->mask & LVCF_SUBITEM)
5370 hdi.mask |= HDI_LPARAM;
5372 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5374 if (lpColumn->mask & LVCF_FMT)
5375 lpColumn->fmt = lpColumnInfo->fmt;
5377 if (lpColumn->mask & LVCF_WIDTH)
5378 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5380 if (lpColumn->mask & LVCF_IMAGE)
5381 lpColumn->iImage = hdi.iImage;
5383 if (lpColumn->mask & LVCF_ORDER)
5384 lpColumn->iOrder = hdi.iOrder;
5386 if (lpColumn->mask & LVCF_SUBITEM)
5387 lpColumn->iSubItem = hdi.lParam;
5389 return TRUE;
5393 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5395 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5397 if (!lpiArray)
5398 return FALSE;
5400 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5403 /***
5404 * DESCRIPTION:
5405 * Retrieves the column width.
5407 * PARAMETER(S):
5408 * [I] infoPtr : valid pointer to the listview structure
5409 * [I] int : column index
5411 * RETURN:
5412 * SUCCESS : column width
5413 * FAILURE : zero
5415 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5417 INT nColumnWidth = 0;
5418 HDITEMW hdItem;
5420 TRACE("nColumn=%d\n", nColumn);
5422 /* we have a 'column' in LIST and REPORT mode only */
5423 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5425 case LVS_LIST:
5426 nColumnWidth = infoPtr->nItemWidth;
5427 break;
5428 case LVS_REPORT:
5429 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5430 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5431 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5433 * TODO: should we do the same in LVM_GETCOLUMN?
5435 hdItem.mask = HDI_WIDTH;
5436 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5438 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5439 return 0;
5441 nColumnWidth = hdItem.cxy;
5442 break;
5445 TRACE("nColumnWidth=%d\n", nColumnWidth);
5446 return nColumnWidth;
5449 /***
5450 * DESCRIPTION:
5451 * In list or report display mode, retrieves the number of items that can fit
5452 * vertically in the visible area. In icon or small icon display mode,
5453 * retrieves the total number of visible items.
5455 * PARAMETER(S):
5456 * [I] infoPtr : valid pointer to the listview structure
5458 * RETURN:
5459 * Number of fully visible items.
5461 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5463 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5465 case LVS_ICON:
5466 case LVS_SMALLICON:
5467 return infoPtr->nItemCount;
5468 case LVS_REPORT:
5469 return LISTVIEW_GetCountPerColumn(infoPtr);
5470 case LVS_LIST:
5471 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5473 assert(FALSE);
5474 return 0;
5477 /***
5478 * DESCRIPTION:
5479 * Retrieves an image list handle.
5481 * PARAMETER(S):
5482 * [I] infoPtr : valid pointer to the listview structure
5483 * [I] nImageList : image list identifier
5485 * RETURN:
5486 * SUCCESS : image list handle
5487 * FAILURE : NULL
5489 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5491 switch (nImageList)
5493 case LVSIL_NORMAL: return infoPtr->himlNormal;
5494 case LVSIL_SMALL: return infoPtr->himlSmall;
5495 case LVSIL_STATE: return infoPtr->himlState;
5497 return NULL;
5500 /* LISTVIEW_GetISearchString */
5502 /***
5503 * DESCRIPTION:
5504 * Retrieves item attributes.
5506 * PARAMETER(S):
5507 * [I] hwnd : window handle
5508 * [IO] lpLVItem : item info
5509 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5510 * if FALSE, then lpLVItem is a LPLVITEMA.
5512 * NOTE:
5513 * This is the internal 'GetItem' interface -- it tries to
5514 * be smart and avoid text copies, if possible, by modifying
5515 * lpLVItem->pszText to point to the text string. Please note
5516 * that this is not always possible (e.g. OWNERDATA), so on
5517 * entry you *must* supply valid values for pszText, and cchTextMax.
5518 * The only difference to the documented interface is that upon
5519 * return, you should use *only* the lpLVItem->pszText, rather than
5520 * the buffer pointer you provided on input. Most code already does
5521 * that, so it's not a problem.
5522 * For the two cases when the text must be copied (that is,
5523 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5525 * RETURN:
5526 * SUCCESS : TRUE
5527 * FAILURE : FALSE
5529 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5531 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5532 NMLVDISPINFOW dispInfo;
5533 ITEM_INFO *lpItem;
5534 ITEMHDR* pItemHdr;
5535 HDPA hdpaSubItems;
5536 INT isubitem;
5538 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5540 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5541 return FALSE;
5543 if (lpLVItem->mask == 0) return TRUE;
5545 /* make a local copy */
5546 isubitem = lpLVItem->iSubItem;
5548 /* a quick optimization if all we're asked is the focus state
5549 * these queries are worth optimising since they are common,
5550 * and can be answered in constant time, without the heavy accesses */
5551 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5552 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5554 lpLVItem->state = 0;
5555 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5556 lpLVItem->state |= LVIS_FOCUSED;
5557 return TRUE;
5560 ZeroMemory(&dispInfo, sizeof(dispInfo));
5562 /* if the app stores all the data, handle it separately */
5563 if (infoPtr->dwStyle & LVS_OWNERDATA)
5565 dispInfo.item.state = 0;
5567 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5568 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5570 UINT mask = lpLVItem->mask;
5572 /* NOTE: copy only fields which we _know_ are initialized, some apps
5573 * depend on the uninitialized fields being 0 */
5574 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5575 dispInfo.item.iItem = lpLVItem->iItem;
5576 dispInfo.item.iSubItem = isubitem;
5577 if (lpLVItem->mask & LVIF_TEXT)
5579 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5580 /* reset mask */
5581 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5582 else
5584 dispInfo.item.pszText = lpLVItem->pszText;
5585 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5588 if (lpLVItem->mask & LVIF_STATE)
5589 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5590 /* could be zeroed on LVIF_NORECOMPUTE case */
5591 if (dispInfo.item.mask != 0)
5593 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5594 dispInfo.item.stateMask = lpLVItem->stateMask;
5595 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5597 /* full size structure expected - _WIN32IE >= 0x560 */
5598 *lpLVItem = dispInfo.item;
5600 else if (lpLVItem->mask & LVIF_INDENT)
5602 /* indent member expected - _WIN32IE >= 0x300 */
5603 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5605 else
5607 /* minimal structure expected */
5608 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5610 lpLVItem->mask = mask;
5611 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5615 /* make sure lParam is zeroed out */
5616 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5618 /* callback marked pointer required here */
5619 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5620 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5622 /* we store only a little state, so if we're not asked, we're done */
5623 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5625 /* if focus is handled by us, report it */
5626 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5628 lpLVItem->state &= ~LVIS_FOCUSED;
5629 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5630 lpLVItem->state |= LVIS_FOCUSED;
5633 /* and do the same for selection, if we handle it */
5634 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5636 lpLVItem->state &= ~LVIS_SELECTED;
5637 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5638 lpLVItem->state |= LVIS_SELECTED;
5641 return TRUE;
5644 /* find the item and subitem structures before we proceed */
5645 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5646 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5647 assert (lpItem);
5649 if (isubitem)
5651 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5652 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5653 if (!lpSubItem)
5655 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5656 isubitem = 0;
5659 else
5660 pItemHdr = &lpItem->hdr;
5662 /* Do we need to query the state from the app? */
5663 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5665 dispInfo.item.mask |= LVIF_STATE;
5666 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5669 /* Do we need to enquire about the image? */
5670 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5671 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5673 dispInfo.item.mask |= LVIF_IMAGE;
5674 dispInfo.item.iImage = I_IMAGECALLBACK;
5677 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5678 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
5679 !is_textW(pItemHdr->pszText))
5681 dispInfo.item.mask |= LVIF_TEXT;
5682 dispInfo.item.pszText = lpLVItem->pszText;
5683 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5684 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5685 *dispInfo.item.pszText = '\0';
5688 /* If we don't have all the requested info, query the application */
5689 if (dispInfo.item.mask != 0)
5691 dispInfo.item.iItem = lpLVItem->iItem;
5692 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5693 dispInfo.item.lParam = lpItem->lParam;
5694 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5695 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5698 /* we should not store values for subitems */
5699 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5701 /* Now, handle the iImage field */
5702 if (dispInfo.item.mask & LVIF_IMAGE)
5704 lpLVItem->iImage = dispInfo.item.iImage;
5705 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5706 pItemHdr->iImage = dispInfo.item.iImage;
5708 else if (lpLVItem->mask & LVIF_IMAGE)
5710 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5711 lpLVItem->iImage = pItemHdr->iImage;
5712 else
5713 lpLVItem->iImage = 0;
5716 /* The pszText field */
5717 if (dispInfo.item.mask & LVIF_TEXT)
5719 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5720 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5722 lpLVItem->pszText = dispInfo.item.pszText;
5724 else if (lpLVItem->mask & LVIF_TEXT)
5726 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
5727 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
5728 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5731 /* Next is the lParam field */
5732 if (dispInfo.item.mask & LVIF_PARAM)
5734 lpLVItem->lParam = dispInfo.item.lParam;
5735 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5736 lpItem->lParam = dispInfo.item.lParam;
5738 else if (lpLVItem->mask & LVIF_PARAM)
5739 lpLVItem->lParam = lpItem->lParam;
5741 /* if this is a subitem, we're done */
5742 if (isubitem) return TRUE;
5744 /* ... the state field (this one is different due to uCallbackmask) */
5745 if (lpLVItem->mask & LVIF_STATE)
5747 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5748 if (dispInfo.item.mask & LVIF_STATE)
5750 lpLVItem->state &= ~dispInfo.item.stateMask;
5751 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5753 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5755 lpLVItem->state &= ~LVIS_FOCUSED;
5756 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5757 lpLVItem->state |= LVIS_FOCUSED;
5759 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5761 lpLVItem->state &= ~LVIS_SELECTED;
5762 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5763 lpLVItem->state |= LVIS_SELECTED;
5767 /* and last, but not least, the indent field */
5768 if (lpLVItem->mask & LVIF_INDENT)
5769 lpLVItem->iIndent = lpItem->iIndent;
5771 return TRUE;
5774 /***
5775 * DESCRIPTION:
5776 * Retrieves item attributes.
5778 * PARAMETER(S):
5779 * [I] hwnd : window handle
5780 * [IO] lpLVItem : item info
5781 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5782 * if FALSE, then lpLVItem is a LPLVITEMA.
5784 * NOTE:
5785 * This is the external 'GetItem' interface -- it properly copies
5786 * the text in the provided buffer.
5788 * RETURN:
5789 * SUCCESS : TRUE
5790 * FAILURE : FALSE
5792 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5794 LPWSTR pszText;
5795 BOOL bResult;
5797 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5798 return FALSE;
5800 pszText = lpLVItem->pszText;
5801 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5802 if (bResult && lpLVItem->pszText != pszText)
5804 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
5805 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5806 else
5807 pszText = LPSTR_TEXTCALLBACKW;
5809 lpLVItem->pszText = pszText;
5811 return bResult;
5815 /***
5816 * DESCRIPTION:
5817 * Retrieves the position (upper-left) of the listview control item.
5818 * Note that for LVS_ICON style, the upper-left is that of the icon
5819 * and not the bounding box.
5821 * PARAMETER(S):
5822 * [I] infoPtr : valid pointer to the listview structure
5823 * [I] nItem : item index
5824 * [O] lpptPosition : coordinate information
5826 * RETURN:
5827 * SUCCESS : TRUE
5828 * FAILURE : FALSE
5830 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5832 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5833 POINT Origin;
5835 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5837 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5839 LISTVIEW_GetOrigin(infoPtr, &Origin);
5840 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5842 if (uView == LVS_ICON)
5844 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5845 lpptPosition->y += ICON_TOP_PADDING;
5847 lpptPosition->x += Origin.x;
5848 lpptPosition->y += Origin.y;
5850 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5851 return TRUE;
5855 /***
5856 * DESCRIPTION:
5857 * Retrieves the bounding rectangle for a listview control item.
5859 * PARAMETER(S):
5860 * [I] infoPtr : valid pointer to the listview structure
5861 * [I] nItem : item index
5862 * [IO] lprc : bounding rectangle coordinates
5863 * lprc->left specifies the portion of the item for which the bounding
5864 * rectangle will be retrieved.
5866 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5867 * including the icon and label.
5869 * * For LVS_ICON
5870 * * Experiment shows that native control returns:
5871 * * width = min (48, length of text line)
5872 * * .left = position.x - (width - iconsize.cx)/2
5873 * * .right = .left + width
5874 * * height = #lines of text * ntmHeight + icon height + 8
5875 * * .top = position.y - 2
5876 * * .bottom = .top + height
5877 * * separation between items .y = itemSpacing.cy - height
5878 * * .x = itemSpacing.cx - width
5879 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5881 * * For LVS_ICON
5882 * * Experiment shows that native control returns:
5883 * * width = iconSize.cx + 16
5884 * * .left = position.x - (width - iconsize.cx)/2
5885 * * .right = .left + width
5886 * * height = iconSize.cy + 4
5887 * * .top = position.y - 2
5888 * * .bottom = .top + height
5889 * * separation between items .y = itemSpacing.cy - height
5890 * * .x = itemSpacing.cx - width
5891 * LVIR_LABEL Returns the bounding rectangle of the item text.
5893 * * For LVS_ICON
5894 * * Experiment shows that native control returns:
5895 * * width = text length
5896 * * .left = position.x - width/2
5897 * * .right = .left + width
5898 * * height = ntmH * linecount + 2
5899 * * .top = position.y + iconSize.cy + 6
5900 * * .bottom = .top + height
5901 * * separation between items .y = itemSpacing.cy - height
5902 * * .x = itemSpacing.cx - width
5903 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5904 * rectangles, but excludes columns in report view.
5906 * RETURN:
5907 * SUCCESS : TRUE
5908 * FAILURE : FALSE
5910 * NOTES
5911 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5912 * upon whether the window has the focus currently and on whether the item
5913 * is the one with the focus. Ensure that the control's record of which
5914 * item has the focus agrees with the items' records.
5916 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5918 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5919 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5920 BOOL doLabel = TRUE, oversizedBox = FALSE;
5921 POINT Position, Origin;
5922 LVITEMW lvItem;
5924 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5926 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5928 LISTVIEW_GetOrigin(infoPtr, &Origin);
5929 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5931 /* Be smart and try to figure out the minimum we have to do */
5932 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5933 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5934 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5935 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5936 oversizedBox = TRUE;
5938 /* get what we need from the item before hand, so we make
5939 * only one request. This can speed up things, if data
5940 * is stored on the app side */
5941 lvItem.mask = 0;
5942 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5943 if (doLabel) lvItem.mask |= LVIF_TEXT;
5944 lvItem.iItem = nItem;
5945 lvItem.iSubItem = 0;
5946 lvItem.pszText = szDispText;
5947 lvItem.cchTextMax = DISP_TEXT_SIZE;
5948 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5949 /* we got the state already up, simulate it here, to avoid a reget */
5950 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5952 lvItem.mask |= LVIF_STATE;
5953 lvItem.stateMask = LVIS_FOCUSED;
5954 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5957 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5958 lprc->left = LVIR_BOUNDS;
5959 switch(lprc->left)
5961 case LVIR_ICON:
5962 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5963 break;
5965 case LVIR_LABEL:
5966 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5967 break;
5969 case LVIR_BOUNDS:
5970 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5971 break;
5973 case LVIR_SELECTBOUNDS:
5974 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5975 break;
5977 default:
5978 WARN("Unknown value: %d\n", lprc->left);
5979 return FALSE;
5982 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5984 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5986 return TRUE;
5989 /***
5990 * DESCRIPTION:
5991 * Retrieves the spacing between listview control items.
5993 * PARAMETER(S):
5994 * [I] infoPtr : valid pointer to the listview structure
5995 * [IO] lprc : rectangle to receive the output
5996 * on input, lprc->top = nSubItem
5997 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5999 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6000 * not only those of the first column.
6001 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6003 * RETURN:
6004 * TRUE: success
6005 * FALSE: failure
6007 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6009 POINT Position;
6010 LVITEMW lvItem;
6011 INT nColumn;
6013 if (!lprc) return FALSE;
6015 nColumn = lprc->top;
6017 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6018 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6019 if (lprc->top == 0)
6020 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6022 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
6024 /* special case for header items */
6025 if (nItem == -1)
6027 if (lprc->left != LVIR_BOUNDS)
6029 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6030 return FALSE;
6033 if (infoPtr->hwndHeader)
6034 return Header_GetItemRect(infoPtr->hwndHeader, lprc->top, lprc);
6035 else
6037 memset(lprc, 0, sizeof(RECT));
6038 return TRUE;
6042 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6044 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6046 lvItem.mask = 0;
6047 lvItem.iItem = nItem;
6048 lvItem.iSubItem = nColumn;
6050 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6051 switch(lprc->left)
6053 case LVIR_ICON:
6054 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6055 break;
6057 case LVIR_LABEL:
6058 case LVIR_BOUNDS:
6059 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6060 break;
6062 default:
6063 ERR("Unknown bounds=%d\n", lprc->left);
6064 return FALSE;
6067 OffsetRect(lprc, Position.x, Position.y);
6068 return TRUE;
6072 /***
6073 * DESCRIPTION:
6074 * Retrieves the width of a label.
6076 * PARAMETER(S):
6077 * [I] infoPtr : valid pointer to the listview structure
6079 * RETURN:
6080 * SUCCESS : string width (in pixels)
6081 * FAILURE : zero
6083 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6085 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6086 LVITEMW lvItem;
6088 TRACE("(nItem=%d)\n", nItem);
6090 lvItem.mask = LVIF_TEXT;
6091 lvItem.iItem = nItem;
6092 lvItem.iSubItem = 0;
6093 lvItem.pszText = szDispText;
6094 lvItem.cchTextMax = DISP_TEXT_SIZE;
6095 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6097 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6100 /***
6101 * DESCRIPTION:
6102 * Retrieves the spacing between listview control items.
6104 * PARAMETER(S):
6105 * [I] infoPtr : valid pointer to the listview structure
6106 * [I] bSmall : flag for small or large icon
6108 * RETURN:
6109 * Horizontal + vertical spacing
6111 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6113 LONG lResult;
6115 if (!bSmall)
6117 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6119 else
6121 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6122 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6123 else
6124 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6126 return lResult;
6129 /***
6130 * DESCRIPTION:
6131 * Retrieves the state of a listview control item.
6133 * PARAMETER(S):
6134 * [I] infoPtr : valid pointer to the listview structure
6135 * [I] nItem : item index
6136 * [I] uMask : state mask
6138 * RETURN:
6139 * State specified by the mask.
6141 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6143 LVITEMW lvItem;
6145 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6147 lvItem.iItem = nItem;
6148 lvItem.iSubItem = 0;
6149 lvItem.mask = LVIF_STATE;
6150 lvItem.stateMask = uMask;
6151 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6153 return lvItem.state & uMask;
6156 /***
6157 * DESCRIPTION:
6158 * Retrieves the text of a listview control item or subitem.
6160 * PARAMETER(S):
6161 * [I] hwnd : window handle
6162 * [I] nItem : item index
6163 * [IO] lpLVItem : item information
6164 * [I] isW : TRUE if lpLVItem is Unicode
6166 * RETURN:
6167 * SUCCESS : string length
6168 * FAILURE : 0
6170 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6172 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6174 lpLVItem->mask = LVIF_TEXT;
6175 lpLVItem->iItem = nItem;
6176 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6178 return textlenT(lpLVItem->pszText, isW);
6181 /***
6182 * DESCRIPTION:
6183 * Searches for an item based on properties + relationships.
6185 * PARAMETER(S):
6186 * [I] infoPtr : valid pointer to the listview structure
6187 * [I] nItem : item index
6188 * [I] uFlags : relationship flag
6190 * RETURN:
6191 * SUCCESS : item index
6192 * FAILURE : -1
6194 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6196 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6197 UINT uMask = 0;
6198 LVFINDINFOW lvFindInfo;
6199 INT nCountPerColumn;
6200 INT nCountPerRow;
6201 INT i;
6203 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6204 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6206 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6208 if (uFlags & LVNI_CUT)
6209 uMask |= LVIS_CUT;
6211 if (uFlags & LVNI_DROPHILITED)
6212 uMask |= LVIS_DROPHILITED;
6214 if (uFlags & LVNI_FOCUSED)
6215 uMask |= LVIS_FOCUSED;
6217 if (uFlags & LVNI_SELECTED)
6218 uMask |= LVIS_SELECTED;
6220 /* if we're asked for the focused item, that's only one,
6221 * so it's worth optimizing */
6222 if (uFlags & LVNI_FOCUSED)
6224 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6225 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6228 if (uFlags & LVNI_ABOVE)
6230 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6232 while (nItem >= 0)
6234 nItem--;
6235 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6236 return nItem;
6239 else
6241 /* Special case for autoarrange - move 'til the top of a list */
6242 if (is_autoarrange(infoPtr))
6244 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6245 while (nItem - nCountPerRow >= 0)
6247 nItem -= nCountPerRow;
6248 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6249 return nItem;
6251 return -1;
6253 lvFindInfo.flags = LVFI_NEARESTXY;
6254 lvFindInfo.vkDirection = VK_UP;
6255 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6256 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6258 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6259 return nItem;
6263 else if (uFlags & LVNI_BELOW)
6265 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6267 while (nItem < infoPtr->nItemCount)
6269 nItem++;
6270 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6271 return nItem;
6274 else
6276 /* Special case for autoarrange - move 'til the bottom of a list */
6277 if (is_autoarrange(infoPtr))
6279 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6280 while (nItem + nCountPerRow < infoPtr->nItemCount )
6282 nItem += nCountPerRow;
6283 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6284 return nItem;
6286 return -1;
6288 lvFindInfo.flags = LVFI_NEARESTXY;
6289 lvFindInfo.vkDirection = VK_DOWN;
6290 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6291 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6293 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6294 return nItem;
6298 else if (uFlags & LVNI_TOLEFT)
6300 if (uView == LVS_LIST)
6302 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6303 while (nItem - nCountPerColumn >= 0)
6305 nItem -= nCountPerColumn;
6306 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6307 return nItem;
6310 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6312 /* Special case for autoarrange - move 'til the beginning of a row */
6313 if (is_autoarrange(infoPtr))
6315 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6316 while (nItem % nCountPerRow > 0)
6318 nItem --;
6319 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6320 return nItem;
6322 return -1;
6324 lvFindInfo.flags = LVFI_NEARESTXY;
6325 lvFindInfo.vkDirection = VK_LEFT;
6326 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6327 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6329 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6330 return nItem;
6334 else if (uFlags & LVNI_TORIGHT)
6336 if (uView == LVS_LIST)
6338 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6339 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6341 nItem += nCountPerColumn;
6342 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6343 return nItem;
6346 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6348 /* Special case for autoarrange - move 'til the end of a row */
6349 if (is_autoarrange(infoPtr))
6351 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6352 while (nItem % nCountPerRow < nCountPerRow - 1 )
6354 nItem ++;
6355 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6356 return nItem;
6358 return -1;
6360 lvFindInfo.flags = LVFI_NEARESTXY;
6361 lvFindInfo.vkDirection = VK_RIGHT;
6362 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6363 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6365 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6366 return nItem;
6370 else
6372 nItem++;
6374 /* search by index */
6375 for (i = nItem; i < infoPtr->nItemCount; i++)
6377 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6378 return i;
6382 return -1;
6385 /* LISTVIEW_GetNumberOfWorkAreas */
6387 /***
6388 * DESCRIPTION:
6389 * Retrieves the origin coordinates when in icon or small icon display mode.
6391 * PARAMETER(S):
6392 * [I] infoPtr : valid pointer to the listview structure
6393 * [O] lpptOrigin : coordinate information
6395 * RETURN:
6396 * None.
6398 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6400 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6401 INT nHorzPos = 0, nVertPos = 0;
6402 SCROLLINFO scrollInfo;
6404 scrollInfo.cbSize = sizeof(SCROLLINFO);
6405 scrollInfo.fMask = SIF_POS;
6407 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6408 nHorzPos = scrollInfo.nPos;
6409 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6410 nVertPos = scrollInfo.nPos;
6412 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6414 lpptOrigin->x = infoPtr->rcList.left;
6415 lpptOrigin->y = infoPtr->rcList.top;
6416 if (uView == LVS_LIST)
6417 nHorzPos *= infoPtr->nItemWidth;
6418 else if (uView == LVS_REPORT)
6419 nVertPos *= infoPtr->nItemHeight;
6421 lpptOrigin->x -= nHorzPos;
6422 lpptOrigin->y -= nVertPos;
6424 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6427 /***
6428 * DESCRIPTION:
6429 * Retrieves the width of a string.
6431 * PARAMETER(S):
6432 * [I] hwnd : window handle
6433 * [I] lpszText : text string to process
6434 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6436 * RETURN:
6437 * SUCCESS : string width (in pixels)
6438 * FAILURE : zero
6440 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6442 SIZE stringSize;
6444 stringSize.cx = 0;
6445 if (is_textT(lpszText, isW))
6447 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6448 HDC hdc = GetDC(infoPtr->hwndSelf);
6449 HFONT hOldFont = SelectObject(hdc, hFont);
6451 if (isW)
6452 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6453 else
6454 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6455 SelectObject(hdc, hOldFont);
6456 ReleaseDC(infoPtr->hwndSelf, hdc);
6458 return stringSize.cx;
6461 /***
6462 * DESCRIPTION:
6463 * Determines which listview item is located at the specified position.
6465 * PARAMETER(S):
6466 * [I] infoPtr : valid pointer to the listview structure
6467 * [IO] lpht : hit test information
6468 * [I] subitem : fill out iSubItem.
6469 * [I] select : return the index only if the hit selects the item
6471 * NOTE:
6472 * (mm 20001022): We must not allow iSubItem to be touched, for
6473 * an app might pass only a structure with space up to iItem!
6474 * (MS Office 97 does that for instance in the file open dialog)
6476 * RETURN:
6477 * SUCCESS : item index
6478 * FAILURE : -1
6480 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6482 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6483 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6484 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6485 POINT Origin, Position, opt;
6486 LVITEMW lvItem;
6487 ITERATOR i;
6488 INT iItem;
6490 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6492 lpht->flags = 0;
6493 lpht->iItem = -1;
6494 if (subitem) lpht->iSubItem = 0;
6496 if (infoPtr->rcList.left > lpht->pt.x)
6497 lpht->flags |= LVHT_TOLEFT;
6498 else if (infoPtr->rcList.right < lpht->pt.x)
6499 lpht->flags |= LVHT_TORIGHT;
6501 if (infoPtr->rcList.top > lpht->pt.y)
6502 lpht->flags |= LVHT_ABOVE;
6503 else if (infoPtr->rcList.bottom < lpht->pt.y)
6504 lpht->flags |= LVHT_BELOW;
6506 TRACE("lpht->flags=0x%x\n", lpht->flags);
6507 if (lpht->flags) return -1;
6509 lpht->flags |= LVHT_NOWHERE;
6511 LISTVIEW_GetOrigin(infoPtr, &Origin);
6513 /* first deal with the large items */
6514 rcSearch.left = lpht->pt.x;
6515 rcSearch.top = lpht->pt.y;
6516 rcSearch.right = rcSearch.left + 1;
6517 rcSearch.bottom = rcSearch.top + 1;
6519 iterator_frameditems(&i, infoPtr, &rcSearch);
6520 iterator_next(&i); /* go to first item in the sequence */
6521 iItem = i.nItem;
6522 iterator_destroy(&i);
6524 TRACE("lpht->iItem=%d\n", iItem);
6525 if (iItem == -1) return -1;
6527 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6528 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6529 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6530 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6531 lvItem.iItem = iItem;
6532 lvItem.iSubItem = 0;
6533 lvItem.pszText = szDispText;
6534 lvItem.cchTextMax = DISP_TEXT_SIZE;
6535 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6536 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6538 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6539 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6540 opt.x = lpht->pt.x - Position.x - Origin.x;
6541 opt.y = lpht->pt.y - Position.y - Origin.y;
6543 if (uView == LVS_REPORT)
6544 rcBounds = rcBox;
6545 else
6547 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6548 UnionRect(&rcBounds, &rcBounds, &rcState);
6550 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6551 if (!PtInRect(&rcBounds, opt)) return -1;
6553 if (PtInRect(&rcIcon, opt))
6554 lpht->flags |= LVHT_ONITEMICON;
6555 else if (PtInRect(&rcLabel, opt))
6556 lpht->flags |= LVHT_ONITEMLABEL;
6557 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6558 lpht->flags |= LVHT_ONITEMSTATEICON;
6559 if (lpht->flags & LVHT_ONITEM)
6560 lpht->flags &= ~LVHT_NOWHERE;
6562 TRACE("lpht->flags=0x%x\n", lpht->flags);
6563 if (uView == LVS_REPORT && subitem)
6565 INT j;
6567 rcBounds.right = rcBounds.left;
6568 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6570 rcBounds.left = rcBounds.right;
6571 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6572 if (PtInRect(&rcBounds, opt))
6574 lpht->iSubItem = j;
6575 break;
6580 if (select && !(uView == LVS_REPORT &&
6581 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6582 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6584 if (uView == LVS_REPORT)
6586 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6587 UnionRect(&rcBounds, &rcBounds, &rcState);
6589 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6591 return lpht->iItem = iItem;
6594 /***
6595 * DESCRIPTION:
6596 * Inserts a new item in the listview control.
6598 * PARAMETER(S):
6599 * [I] infoPtr : valid pointer to the listview structure
6600 * [I] lpLVItem : item information
6601 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6603 * RETURN:
6604 * SUCCESS : new item index
6605 * FAILURE : -1
6607 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6609 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6610 INT nItem;
6611 HDPA hdpaSubItems;
6612 NMLISTVIEW nmlv;
6613 ITEM_INFO *lpItem;
6614 BOOL is_sorted, has_changed;
6615 LVITEMW item;
6616 HWND hwndSelf = infoPtr->hwndSelf;
6618 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6620 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6622 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6623 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6625 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6627 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6629 /* insert item in listview control data structure */
6630 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6631 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6633 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6634 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6636 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6638 /* calculate new item index */
6639 if (is_sorted)
6641 HDPA hItem;
6642 ITEM_INFO *item_s;
6643 INT i = 0, cmpv;
6645 while (i < infoPtr->nItemCount)
6647 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
6648 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
6650 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
6651 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
6653 if (cmpv >= 0) break;
6654 i++;
6656 nItem = i;
6658 else
6659 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
6661 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6662 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6663 if (nItem == -1) goto fail;
6664 infoPtr->nItemCount++;
6666 /* shift indices first so they don't get tangled */
6667 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6669 /* set the item attributes */
6670 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6672 /* full size structure expected - _WIN32IE >= 0x560 */
6673 item = *lpLVItem;
6675 else if (lpLVItem->mask & LVIF_INDENT)
6677 /* indent member expected - _WIN32IE >= 0x300 */
6678 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6680 else
6682 /* minimal structure expected */
6683 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6685 item.iItem = nItem;
6686 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6688 item.mask |= LVIF_STATE;
6689 item.stateMask |= LVIS_STATEIMAGEMASK;
6690 item.state &= ~LVIS_STATEIMAGEMASK;
6691 item.state |= INDEXTOSTATEIMAGEMASK(1);
6693 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6695 /* make room for the position, if we are in the right mode */
6696 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6698 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6699 goto undo;
6700 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6702 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6703 goto undo;
6707 /* send LVN_INSERTITEM notification */
6708 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6709 nmlv.iItem = nItem;
6710 nmlv.lParam = lpItem->lParam;
6711 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6712 if (!IsWindow(hwndSelf))
6713 return -1;
6715 /* align items (set position of each item) */
6716 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6718 POINT pt;
6720 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6721 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6722 else
6723 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6725 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6728 /* now is the invalidation fun */
6729 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6730 return nItem;
6732 undo:
6733 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6734 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6735 infoPtr->nItemCount--;
6736 fail:
6737 DPA_DeletePtr(hdpaSubItems, 0);
6738 DPA_Destroy (hdpaSubItems);
6739 Free (lpItem);
6740 return -1;
6743 /***
6744 * DESCRIPTION:
6745 * Redraws a range of items.
6747 * PARAMETER(S):
6748 * [I] infoPtr : valid pointer to the listview structure
6749 * [I] nFirst : first item
6750 * [I] nLast : last item
6752 * RETURN:
6753 * SUCCESS : TRUE
6754 * FAILURE : FALSE
6756 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6758 INT i;
6760 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6761 max(nFirst, nLast) >= infoPtr->nItemCount)
6762 return FALSE;
6764 for (i = nFirst; i <= nLast; i++)
6765 LISTVIEW_InvalidateItem(infoPtr, i);
6767 return TRUE;
6770 /***
6771 * DESCRIPTION:
6772 * Scroll the content of a listview.
6774 * PARAMETER(S):
6775 * [I] infoPtr : valid pointer to the listview structure
6776 * [I] dx : horizontal scroll amount in pixels
6777 * [I] dy : vertical scroll amount in pixels
6779 * RETURN:
6780 * SUCCESS : TRUE
6781 * FAILURE : FALSE
6783 * COMMENTS:
6784 * If the control is in report mode (LVS_REPORT) the control can
6785 * be scrolled only in line increments. "dy" will be rounded to the
6786 * nearest number of pixels that are a whole line. Ex: if line height
6787 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6788 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6790 * For: (per experimentation with native control and CSpy ListView)
6791 * LVS_ICON dy=1 = 1 pixel (vertical only)
6792 * dx ignored
6793 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6794 * dx ignored
6795 * LVS_LIST dx=1 = 1 column (horizontal only)
6796 * but will only scroll 1 column per message
6797 * no matter what the value.
6798 * dy must be 0 or FALSE returned.
6799 * LVS_REPORT dx=1 = 1 pixel
6800 * dy= see above
6803 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6805 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6806 case LVS_REPORT:
6807 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6808 dy /= infoPtr->nItemHeight;
6809 break;
6810 case LVS_LIST:
6811 if (dy != 0) return FALSE;
6812 break;
6813 default: /* icon */
6814 dx = 0;
6815 break;
6818 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6819 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6821 return TRUE;
6824 /***
6825 * DESCRIPTION:
6826 * Sets the background color.
6828 * PARAMETER(S):
6829 * [I] infoPtr : valid pointer to the listview structure
6830 * [I] clrBk : background color
6832 * RETURN:
6833 * SUCCESS : TRUE
6834 * FAILURE : FALSE
6836 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6838 TRACE("(clrBk=%x)\n", clrBk);
6840 if(infoPtr->clrBk != clrBk) {
6841 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6842 infoPtr->clrBk = clrBk;
6843 if (clrBk == CLR_NONE)
6844 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6845 else
6846 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6847 LISTVIEW_InvalidateList(infoPtr);
6850 return TRUE;
6853 /* LISTVIEW_SetBkImage */
6855 /*** Helper for {Insert,Set}ColumnT *only* */
6856 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6857 const LVCOLUMNW *lpColumn, BOOL isW)
6859 if (lpColumn->mask & LVCF_FMT)
6861 /* format member is valid */
6862 lphdi->mask |= HDI_FORMAT;
6864 /* set text alignment (leftmost column must be left-aligned) */
6865 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6866 lphdi->fmt |= HDF_LEFT;
6867 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6868 lphdi->fmt |= HDF_RIGHT;
6869 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6870 lphdi->fmt |= HDF_CENTER;
6872 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6873 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6875 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6877 lphdi->fmt |= HDF_IMAGE;
6878 lphdi->iImage = I_IMAGECALLBACK;
6882 if (lpColumn->mask & LVCF_WIDTH)
6884 lphdi->mask |= HDI_WIDTH;
6885 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6887 /* make it fill the remainder of the controls width */
6888 RECT rcHeader;
6889 INT item_index;
6891 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6893 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6894 lphdi->cxy += rcHeader.right - rcHeader.left;
6897 /* retrieve the layout of the header */
6898 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6899 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6901 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6903 else
6904 lphdi->cxy = lpColumn->cx;
6907 if (lpColumn->mask & LVCF_TEXT)
6909 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6910 lphdi->fmt |= HDF_STRING;
6911 lphdi->pszText = lpColumn->pszText;
6912 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6915 if (lpColumn->mask & LVCF_IMAGE)
6917 lphdi->mask |= HDI_IMAGE;
6918 lphdi->iImage = lpColumn->iImage;
6921 if (lpColumn->mask & LVCF_ORDER)
6923 lphdi->mask |= HDI_ORDER;
6924 lphdi->iOrder = lpColumn->iOrder;
6929 /***
6930 * DESCRIPTION:
6931 * Inserts a new column.
6933 * PARAMETER(S):
6934 * [I] infoPtr : valid pointer to the listview structure
6935 * [I] nColumn : column index
6936 * [I] lpColumn : column information
6937 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6939 * RETURN:
6940 * SUCCESS : new column index
6941 * FAILURE : -1
6943 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6944 const LVCOLUMNW *lpColumn, BOOL isW)
6946 COLUMN_INFO *lpColumnInfo;
6947 INT nNewColumn;
6948 HDITEMW hdi;
6949 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6951 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6953 if (!lpColumn || nColumn < 0) return -1;
6954 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6956 ZeroMemory(&hdi, sizeof(HDITEMW));
6957 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6960 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6961 * (can be seen in SPY) otherwise column never gets added.
6963 if (!(lpColumn->mask & LVCF_WIDTH)) {
6964 hdi.mask |= HDI_WIDTH;
6965 hdi.cxy = 10;
6969 * when the iSubItem is available Windows copies it to the header lParam. It seems
6970 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6972 if (lpColumn->mask & LVCF_SUBITEM)
6974 hdi.mask |= HDI_LPARAM;
6975 hdi.lParam = lpColumn->iSubItem;
6978 /* create header if not present */
6979 LISTVIEW_CreateHeader(infoPtr);
6980 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
6981 (LVS_REPORT == uView) && (WS_VISIBLE & infoPtr->dwStyle))
6983 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
6986 /* insert item in header control */
6987 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6988 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6989 (WPARAM)nColumn, (LPARAM)&hdi);
6990 if (nNewColumn == -1) return -1;
6991 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6993 /* create our own column info */
6994 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6995 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6997 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6998 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
7000 /* now we have to actually adjust the data */
7001 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7003 SUBITEM_INFO *lpSubItem;
7004 HDPA hdpaSubItems;
7005 INT nItem, i;
7007 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7009 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7010 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7012 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7013 if (lpSubItem->iSubItem >= nNewColumn)
7014 lpSubItem->iSubItem++;
7019 /* make space for the new column */
7020 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7021 LISTVIEW_UpdateItemSize(infoPtr);
7023 return nNewColumn;
7025 fail:
7026 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7027 if (lpColumnInfo)
7029 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7030 Free(lpColumnInfo);
7032 return -1;
7035 /***
7036 * DESCRIPTION:
7037 * Sets the attributes of a header item.
7039 * PARAMETER(S):
7040 * [I] infoPtr : valid pointer to the listview structure
7041 * [I] nColumn : column index
7042 * [I] lpColumn : column attributes
7043 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7045 * RETURN:
7046 * SUCCESS : TRUE
7047 * FAILURE : FALSE
7049 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7050 const LVCOLUMNW *lpColumn, BOOL isW)
7052 HDITEMW hdi, hdiget;
7053 BOOL bResult;
7055 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7057 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7059 ZeroMemory(&hdi, sizeof(HDITEMW));
7060 if (lpColumn->mask & LVCF_FMT)
7062 hdi.mask |= HDI_FORMAT;
7063 hdiget.mask = HDI_FORMAT;
7064 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7065 hdi.fmt = hdiget.fmt & HDF_STRING;
7067 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7069 /* set header item attributes */
7070 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7071 if (!bResult) return FALSE;
7073 if (lpColumn->mask & LVCF_FMT)
7075 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7076 int oldFmt = lpColumnInfo->fmt;
7078 lpColumnInfo->fmt = lpColumn->fmt;
7079 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7081 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7082 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7086 return TRUE;
7089 /***
7090 * DESCRIPTION:
7091 * Sets the column order array
7093 * PARAMETERS:
7094 * [I] infoPtr : valid pointer to the listview structure
7095 * [I] iCount : number of elements in column order array
7096 * [I] lpiArray : pointer to column order array
7098 * RETURN:
7099 * SUCCESS : TRUE
7100 * FAILURE : FALSE
7102 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7104 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7106 if (!lpiArray)
7107 return FALSE;
7109 return TRUE;
7113 /***
7114 * DESCRIPTION:
7115 * Sets the width of a column
7117 * PARAMETERS:
7118 * [I] infoPtr : valid pointer to the listview structure
7119 * [I] nColumn : column index
7120 * [I] cx : column width
7122 * RETURN:
7123 * SUCCESS : TRUE
7124 * FAILURE : FALSE
7126 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7128 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7129 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7130 INT max_cx = 0;
7131 HDITEMW hdi;
7133 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7135 /* set column width only if in report or list mode */
7136 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7138 /* take care of invalid cx values */
7139 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7140 else if (uView == LVS_LIST && cx < 1) return FALSE;
7142 /* resize all columns if in LVS_LIST mode */
7143 if(uView == LVS_LIST)
7145 infoPtr->nItemWidth = cx;
7146 LISTVIEW_InvalidateList(infoPtr);
7147 return TRUE;
7150 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7152 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7154 INT nLabelWidth;
7155 LVITEMW lvItem;
7157 lvItem.mask = LVIF_TEXT;
7158 lvItem.iItem = 0;
7159 lvItem.iSubItem = nColumn;
7160 lvItem.pszText = szDispText;
7161 lvItem.cchTextMax = DISP_TEXT_SIZE;
7162 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7164 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7165 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7166 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7168 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7169 max_cx += infoPtr->iconSize.cx;
7170 max_cx += TRAILING_LABEL_PADDING;
7173 /* autosize based on listview items width */
7174 if(cx == LVSCW_AUTOSIZE)
7175 cx = max_cx;
7176 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7178 /* if iCol is the last column make it fill the remainder of the controls width */
7179 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7181 RECT rcHeader;
7182 POINT Origin;
7184 LISTVIEW_GetOrigin(infoPtr, &Origin);
7185 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7187 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7189 else
7191 /* Despite what the MS docs say, if this is not the last
7192 column, then MS resizes the column to the width of the
7193 largest text string in the column, including headers
7194 and items. This is different from LVSCW_AUTOSIZE in that
7195 LVSCW_AUTOSIZE ignores the header string length. */
7196 cx = 0;
7198 /* retrieve header text */
7199 hdi.mask = HDI_TEXT;
7200 hdi.cchTextMax = DISP_TEXT_SIZE;
7201 hdi.pszText = szDispText;
7202 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7204 HDC hdc = GetDC(infoPtr->hwndSelf);
7205 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7206 SIZE size;
7208 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7209 cx = size.cx + TRAILING_HEADER_PADDING;
7210 /* FIXME: Take into account the header image, if one is present */
7211 SelectObject(hdc, old_font);
7212 ReleaseDC(infoPtr->hwndSelf, hdc);
7214 cx = max (cx, max_cx);
7218 if (cx < 0) return FALSE;
7220 /* call header to update the column change */
7221 hdi.mask = HDI_WIDTH;
7222 hdi.cxy = cx;
7223 TRACE("hdi.cxy=%d\n", hdi.cxy);
7224 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7227 /***
7228 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7231 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7233 HDC hdc_wnd, hdc;
7234 HBITMAP hbm_im, hbm_mask, hbm_orig;
7235 RECT rc;
7236 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7237 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7238 HIMAGELIST himl;
7240 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7241 ILC_COLOR | ILC_MASK, 2, 2);
7242 hdc_wnd = GetDC(infoPtr->hwndSelf);
7243 hdc = CreateCompatibleDC(hdc_wnd);
7244 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7245 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7246 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7248 rc.left = rc.top = 0;
7249 rc.right = GetSystemMetrics(SM_CXSMICON);
7250 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7252 hbm_orig = SelectObject(hdc, hbm_mask);
7253 FillRect(hdc, &rc, hbr_white);
7254 InflateRect(&rc, -2, -2);
7255 FillRect(hdc, &rc, hbr_black);
7257 SelectObject(hdc, hbm_im);
7258 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7259 SelectObject(hdc, hbm_orig);
7260 ImageList_Add(himl, hbm_im, hbm_mask);
7262 SelectObject(hdc, hbm_im);
7263 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7264 SelectObject(hdc, hbm_orig);
7265 ImageList_Add(himl, hbm_im, hbm_mask);
7267 DeleteObject(hbm_mask);
7268 DeleteObject(hbm_im);
7269 DeleteDC(hdc);
7271 return himl;
7274 /***
7275 * DESCRIPTION:
7276 * Sets the extended listview style.
7278 * PARAMETERS:
7279 * [I] infoPtr : valid pointer to the listview structure
7280 * [I] dwMask : mask
7281 * [I] dwStyle : style
7283 * RETURN:
7284 * SUCCESS : previous style
7285 * FAILURE : 0
7287 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7289 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7291 /* set new style */
7292 if (dwMask)
7293 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7294 else
7295 infoPtr->dwLvExStyle = dwExStyle;
7297 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7299 HIMAGELIST himl = 0;
7300 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7302 LVITEMW item;
7303 item.mask = LVIF_STATE;
7304 item.stateMask = LVIS_STATEIMAGEMASK;
7305 item.state = INDEXTOSTATEIMAGEMASK(1);
7306 LISTVIEW_SetItemState(infoPtr, -1, &item);
7308 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7310 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7313 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7315 DWORD dwStyle;
7317 /* if not already created */
7318 LISTVIEW_CreateHeader(infoPtr);
7320 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7321 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7322 dwStyle |= HDS_DRAGDROP;
7323 else
7324 dwStyle &= ~HDS_DRAGDROP;
7325 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7328 /* GRIDLINES adds decoration at top so changes sizes */
7329 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7331 LISTVIEW_UpdateSize(infoPtr);
7335 LISTVIEW_InvalidateList(infoPtr);
7336 return dwOldExStyle;
7339 /***
7340 * DESCRIPTION:
7341 * Sets the new hot cursor used during hot tracking and hover selection.
7343 * PARAMETER(S):
7344 * [I] infoPtr : valid pointer to the listview structure
7345 * [I] hCursor : the new hot cursor handle
7347 * RETURN:
7348 * Returns the previous hot cursor
7350 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7352 HCURSOR oldCursor = infoPtr->hHotCursor;
7354 infoPtr->hHotCursor = hCursor;
7356 return oldCursor;
7360 /***
7361 * DESCRIPTION:
7362 * Sets the hot item index.
7364 * PARAMETERS:
7365 * [I] infoPtr : valid pointer to the listview structure
7366 * [I] iIndex : index
7368 * RETURN:
7369 * SUCCESS : previous hot item index
7370 * FAILURE : -1 (no hot item)
7372 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7374 INT iOldIndex = infoPtr->nHotItem;
7376 infoPtr->nHotItem = iIndex;
7378 return iOldIndex;
7382 /***
7383 * DESCRIPTION:
7384 * Sets the amount of time the cursor must hover over an item before it is selected.
7386 * PARAMETER(S):
7387 * [I] infoPtr : valid pointer to the listview structure
7388 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7390 * RETURN:
7391 * Returns the previous hover time
7393 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7395 DWORD oldHoverTime = infoPtr->dwHoverTime;
7397 infoPtr->dwHoverTime = dwHoverTime;
7399 return oldHoverTime;
7402 /***
7403 * DESCRIPTION:
7404 * Sets spacing for icons of LVS_ICON style.
7406 * PARAMETER(S):
7407 * [I] infoPtr : valid pointer to the listview structure
7408 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7409 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7411 * RETURN:
7412 * MAKELONG(oldcx, oldcy)
7414 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7416 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7417 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7419 TRACE("requested=(%d,%d)\n", cx, cy);
7421 /* this is supported only for LVS_ICON style */
7422 if (uView != LVS_ICON) return oldspacing;
7424 /* set to defaults, if instructed to */
7425 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7426 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7428 /* if 0 then compute width
7429 * FIXME: Should scan each item and determine max width of
7430 * icon or label, then make that the width */
7431 if (cx == 0)
7432 cx = infoPtr->iconSpacing.cx;
7434 /* if 0 then compute height */
7435 if (cy == 0)
7436 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7437 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7440 infoPtr->iconSpacing.cx = cx;
7441 infoPtr->iconSpacing.cy = cy;
7443 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7444 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7445 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7446 infoPtr->ntmHeight);
7448 /* these depend on the iconSpacing */
7449 LISTVIEW_UpdateItemSize(infoPtr);
7451 return oldspacing;
7454 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7456 INT cx, cy;
7458 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7460 size->cx = cx;
7461 size->cy = cy;
7463 else
7465 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7466 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7470 /***
7471 * DESCRIPTION:
7472 * Sets image lists.
7474 * PARAMETER(S):
7475 * [I] infoPtr : valid pointer to the listview structure
7476 * [I] nType : image list type
7477 * [I] himl : image list handle
7479 * RETURN:
7480 * SUCCESS : old image list
7481 * FAILURE : NULL
7483 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7485 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7486 INT oldHeight = infoPtr->nItemHeight;
7487 HIMAGELIST himlOld = 0;
7489 TRACE("(nType=%d, himl=%p\n", nType, himl);
7491 switch (nType)
7493 case LVSIL_NORMAL:
7494 himlOld = infoPtr->himlNormal;
7495 infoPtr->himlNormal = himl;
7496 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7497 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7498 break;
7500 case LVSIL_SMALL:
7501 himlOld = infoPtr->himlSmall;
7502 infoPtr->himlSmall = himl;
7503 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7504 break;
7506 case LVSIL_STATE:
7507 himlOld = infoPtr->himlState;
7508 infoPtr->himlState = himl;
7509 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7510 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7511 break;
7513 default:
7514 ERR("Unknown icon type=%d\n", nType);
7515 return NULL;
7518 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7519 if (infoPtr->nItemHeight != oldHeight)
7520 LISTVIEW_UpdateScroll(infoPtr);
7522 return himlOld;
7525 /***
7526 * DESCRIPTION:
7527 * Preallocates memory (does *not* set the actual count of items !)
7529 * PARAMETER(S):
7530 * [I] infoPtr : valid pointer to the listview structure
7531 * [I] nItems : item count (projected number of items to allocate)
7532 * [I] dwFlags : update flags
7534 * RETURN:
7535 * SUCCESS : TRUE
7536 * FAILURE : FALSE
7538 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7540 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7542 if (infoPtr->dwStyle & LVS_OWNERDATA)
7544 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7545 INT nOldCount = infoPtr->nItemCount;
7547 if (nItems < nOldCount)
7549 RANGE range = { nItems, nOldCount };
7550 ranges_del(infoPtr->selectionRanges, range);
7551 if (infoPtr->nFocusedItem >= nItems)
7553 LISTVIEW_SetItemFocus(infoPtr, -1);
7554 SetRectEmpty(&infoPtr->rcFocus);
7558 infoPtr->nItemCount = nItems;
7559 LISTVIEW_UpdateScroll(infoPtr);
7561 /* the flags are valid only in ownerdata report and list modes */
7562 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7564 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7565 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7567 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7568 LISTVIEW_InvalidateList(infoPtr);
7569 else
7571 INT nFrom, nTo;
7572 POINT Origin;
7573 RECT rcErase;
7575 LISTVIEW_GetOrigin(infoPtr, &Origin);
7576 nFrom = min(nOldCount, nItems);
7577 nTo = max(nOldCount, nItems);
7579 if (uView == LVS_REPORT)
7581 rcErase.left = 0;
7582 rcErase.top = nFrom * infoPtr->nItemHeight;
7583 rcErase.right = infoPtr->nItemWidth;
7584 rcErase.bottom = nTo * infoPtr->nItemHeight;
7585 OffsetRect(&rcErase, Origin.x, Origin.y);
7586 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7587 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7589 else /* LVS_LIST */
7591 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7593 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7594 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7595 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7596 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7597 OffsetRect(&rcErase, Origin.x, Origin.y);
7598 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7599 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7601 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7602 rcErase.top = 0;
7603 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7604 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7605 OffsetRect(&rcErase, Origin.x, Origin.y);
7606 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7607 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7611 else
7613 /* According to MSDN for non-LVS_OWNERDATA this is just
7614 * a performance issue. The control allocates its internal
7615 * data structures for the number of items specified. It
7616 * cuts down on the number of memory allocations. Therefore
7617 * we will just issue a WARN here
7619 WARN("for non-ownerdata performance option not implemented.\n");
7622 return TRUE;
7625 /***
7626 * DESCRIPTION:
7627 * Sets the position of an item.
7629 * PARAMETER(S):
7630 * [I] infoPtr : valid pointer to the listview structure
7631 * [I] nItem : item index
7632 * [I] pt : coordinate
7634 * RETURN:
7635 * SUCCESS : TRUE
7636 * FAILURE : FALSE
7638 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7640 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7641 POINT Origin;
7643 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7645 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7646 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7648 LISTVIEW_GetOrigin(infoPtr, &Origin);
7650 /* This point value seems to be an undocumented feature.
7651 * The best guess is that it means either at the origin,
7652 * or at true beginning of the list. I will assume the origin. */
7653 if ((pt.x == -1) && (pt.y == -1))
7654 pt = Origin;
7656 if (uView == LVS_ICON)
7658 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7659 pt.y -= ICON_TOP_PADDING;
7661 pt.x -= Origin.x;
7662 pt.y -= Origin.y;
7664 infoPtr->bAutoarrange = FALSE;
7666 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7669 /***
7670 * DESCRIPTION:
7671 * Sets the state of one or many items.
7673 * PARAMETER(S):
7674 * [I] infoPtr : valid pointer to the listview structure
7675 * [I] nItem : item index
7676 * [I] lpLVItem : item or subitem info
7678 * RETURN:
7679 * SUCCESS : TRUE
7680 * FAILURE : FALSE
7682 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7684 BOOL bResult = TRUE;
7685 LVITEMW lvItem;
7687 lvItem.iItem = nItem;
7688 lvItem.iSubItem = 0;
7689 lvItem.mask = LVIF_STATE;
7690 lvItem.state = lpLVItem->state;
7691 lvItem.stateMask = lpLVItem->stateMask;
7692 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7694 if (nItem == -1)
7696 /* apply to all items */
7697 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7698 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7700 else
7701 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7704 * Update selection mark
7706 * Investigation on windows 2k showed that selection mark was updated
7707 * whenever a new selection was made, but if the selected item was
7708 * unselected it was not updated.
7710 * we are probably still not 100% accurate, but this at least sets the
7711 * proper selection mark when it is needed
7714 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7715 (infoPtr->nSelectionMark == -1))
7717 int i;
7718 for (i = 0; i < infoPtr->nItemCount; i++)
7720 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7722 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7724 infoPtr->nSelectionMark = i;
7725 break;
7728 else if (ranges_contain(infoPtr->selectionRanges, i))
7730 infoPtr->nSelectionMark = i;
7731 break;
7736 return bResult;
7739 /***
7740 * DESCRIPTION:
7741 * Sets the text of an item or subitem.
7743 * PARAMETER(S):
7744 * [I] hwnd : window handle
7745 * [I] nItem : item index
7746 * [I] lpLVItem : item or subitem info
7747 * [I] isW : TRUE if input is Unicode
7749 * RETURN:
7750 * SUCCESS : TRUE
7751 * FAILURE : FALSE
7753 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7755 LVITEMW lvItem;
7757 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7759 lvItem.iItem = nItem;
7760 lvItem.iSubItem = lpLVItem->iSubItem;
7761 lvItem.mask = LVIF_TEXT;
7762 lvItem.pszText = lpLVItem->pszText;
7763 lvItem.cchTextMax = lpLVItem->cchTextMax;
7765 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7767 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7770 /***
7771 * DESCRIPTION:
7772 * Set item index that marks the start of a multiple selection.
7774 * PARAMETER(S):
7775 * [I] infoPtr : valid pointer to the listview structure
7776 * [I] nIndex : index
7778 * RETURN:
7779 * Index number or -1 if there is no selection mark.
7781 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7783 INT nOldIndex = infoPtr->nSelectionMark;
7785 TRACE("(nIndex=%d)\n", nIndex);
7787 infoPtr->nSelectionMark = nIndex;
7789 return nOldIndex;
7792 /***
7793 * DESCRIPTION:
7794 * Sets the text background color.
7796 * PARAMETER(S):
7797 * [I] infoPtr : valid pointer to the listview structure
7798 * [I] clrTextBk : text background color
7800 * RETURN:
7801 * SUCCESS : TRUE
7802 * FAILURE : FALSE
7804 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7806 TRACE("(clrTextBk=%x)\n", clrTextBk);
7808 if (infoPtr->clrTextBk != clrTextBk)
7810 infoPtr->clrTextBk = clrTextBk;
7811 LISTVIEW_InvalidateList(infoPtr);
7814 return TRUE;
7817 /***
7818 * DESCRIPTION:
7819 * Sets the text foreground color.
7821 * PARAMETER(S):
7822 * [I] infoPtr : valid pointer to the listview structure
7823 * [I] clrText : text color
7825 * RETURN:
7826 * SUCCESS : TRUE
7827 * FAILURE : FALSE
7829 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7831 TRACE("(clrText=%x)\n", clrText);
7833 if (infoPtr->clrText != clrText)
7835 infoPtr->clrText = clrText;
7836 LISTVIEW_InvalidateList(infoPtr);
7839 return TRUE;
7842 /***
7843 * DESCRIPTION:
7844 * Sets new ToolTip window to ListView control.
7846 * PARAMETER(S):
7847 * [I] infoPtr : valid pointer to the listview structure
7848 * [I] hwndNewToolTip : handle to new ToolTip
7850 * RETURN:
7851 * old tool tip
7853 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7855 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7856 infoPtr->hwndToolTip = hwndNewToolTip;
7857 return hwndOldToolTip;
7861 * DESCRIPTION:
7862 * sets the Unicode character format flag for the control
7863 * PARAMETER(S):
7864 * [I] infoPtr :valid pointer to the listview structure
7865 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7867 * RETURN:
7868 * Old Unicode Format
7870 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7872 BOOL rc = infoPtr->notifyFormat;
7873 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7874 return rc;
7877 /* LISTVIEW_SetWorkAreas */
7879 /***
7880 * DESCRIPTION:
7881 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
7883 * PARAMETER(S):
7884 * [I] first : pointer to first ITEM_INFO to compare
7885 * [I] second : pointer to second ITEM_INFO to compare
7886 * [I] lParam : HWND of control
7888 * RETURN:
7889 * if first comes before second : negative
7890 * if first comes after second : positive
7891 * if first and second are equivalent : zero
7893 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7895 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7896 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7897 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7899 /* Forward the call to the client defined callback */
7900 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7903 /***
7904 * DESCRIPTION:
7905 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
7907 * PARAMETER(S):
7908 * [I] first : pointer to first ITEM_INFO to compare
7909 * [I] second : pointer to second ITEM_INFO to compare
7910 * [I] lParam : HWND of control
7912 * RETURN:
7913 * if first comes before second : negative
7914 * if first comes after second : positive
7915 * if first and second are equivalent : zero
7917 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
7919 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7920 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
7921 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
7923 /* Forward the call to the client defined callback */
7924 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
7927 /***
7928 * DESCRIPTION:
7929 * Sorts the listview items.
7931 * PARAMETER(S):
7932 * [I] infoPtr : valid pointer to the listview structure
7933 * [I] pfnCompare : application-defined value
7934 * [I] lParamSort : pointer to comparison callback
7935 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
7937 * RETURN:
7938 * SUCCESS : TRUE
7939 * FAILURE : FALSE
7941 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
7942 LPARAM lParamSort, BOOL IsEx)
7944 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7945 HDPA hdpaSubItems;
7946 ITEM_INFO *lpItem;
7947 LPVOID selectionMarkItem = NULL;
7948 LPVOID focusedItem = NULL;
7949 int i;
7951 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7953 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7955 if (!pfnCompare) return FALSE;
7956 if (!infoPtr->hdpaItems) return FALSE;
7958 /* if there are 0 or 1 items, there is no need to sort */
7959 if (infoPtr->nItemCount < 2) return TRUE;
7961 /* clear selection */
7962 ranges_clear(infoPtr->selectionRanges);
7964 /* save selection mark and focused item */
7965 if (infoPtr->nSelectionMark >= 0)
7966 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
7967 if (infoPtr->nFocusedItem >= 0)
7968 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7970 infoPtr->pfnCompare = pfnCompare;
7971 infoPtr->lParamSort = lParamSort;
7972 if (IsEx)
7973 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
7974 else
7975 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7977 /* restore selection ranges */
7978 for (i=0; i < infoPtr->nItemCount; i++)
7980 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7981 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7983 if (lpItem->state & LVIS_SELECTED)
7984 ranges_additem(infoPtr->selectionRanges, i);
7986 /* restore selection mark and focused item */
7987 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7988 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
7990 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7992 /* refresh the display */
7993 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7994 LISTVIEW_InvalidateList(infoPtr);
7996 return TRUE;
7999 /***
8000 * DESCRIPTION:
8001 * Update theme handle after a theme change.
8003 * PARAMETER(S):
8004 * [I] infoPtr : valid pointer to the listview structure
8006 * RETURN:
8007 * SUCCESS : 0
8008 * FAILURE : something else
8010 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8012 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8013 CloseThemeData(theme);
8014 OpenThemeData(infoPtr->hwndSelf, themeClass);
8015 return 0;
8018 /***
8019 * DESCRIPTION:
8020 * Updates an items or rearranges the listview control.
8022 * PARAMETER(S):
8023 * [I] infoPtr : valid pointer to the listview structure
8024 * [I] nItem : item index
8026 * RETURN:
8027 * SUCCESS : TRUE
8028 * FAILURE : FALSE
8030 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8032 TRACE("(nItem=%d)\n", nItem);
8034 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8036 /* rearrange with default alignment style */
8037 if (is_autoarrange(infoPtr))
8038 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8039 else
8040 LISTVIEW_InvalidateItem(infoPtr, nItem);
8042 return TRUE;
8045 /***
8046 * DESCRIPTION:
8047 * Draw the track line at the place defined in the infoPtr structure.
8048 * The line is drawn with a XOR pen so drawing the line for the second time
8049 * in the same place erases the line.
8051 * PARAMETER(S):
8052 * [I] infoPtr : valid pointer to the listview structure
8054 * RETURN:
8055 * SUCCESS : TRUE
8056 * FAILURE : FALSE
8058 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8060 HPEN hOldPen;
8061 HDC hdc;
8062 INT oldROP;
8064 if (infoPtr->xTrackLine == -1)
8065 return FALSE;
8067 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8068 return FALSE;
8069 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8070 oldROP = SetROP2(hdc, R2_XORPEN);
8071 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8072 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8073 SetROP2(hdc, oldROP);
8074 SelectObject(hdc, hOldPen);
8075 ReleaseDC(infoPtr->hwndSelf, hdc);
8076 return TRUE;
8079 /***
8080 * DESCRIPTION:
8081 * Called when an edit control should be displayed. This function is called after
8082 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8084 * PARAMETER(S):
8085 * [I] hwnd : Handle to the listview
8086 * [I] uMsg : WM_TIMER (ignored)
8087 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8088 * [I] dwTimer : The elapsed time (ignored)
8090 * RETURN:
8091 * None.
8093 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8095 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8096 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8098 KillTimer(hwnd, idEvent);
8099 editItem->fEnabled = FALSE;
8100 /* check if the item is still selected */
8101 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8102 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8105 /***
8106 * DESCRIPTION:
8107 * Creates the listview control - the WM_NCCREATE phase.
8109 * PARAMETER(S):
8110 * [I] hwnd : window handle
8111 * [I] lpcs : the create parameters
8113 * RETURN:
8114 * Success: TRUE
8115 * Failure: FALSE
8117 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8119 LISTVIEW_INFO *infoPtr;
8120 LOGFONTW logFont;
8122 TRACE("(lpcs=%p)\n", lpcs);
8124 /* initialize info pointer */
8125 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8126 if (!infoPtr) return FALSE;
8128 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8130 infoPtr->hwndSelf = hwnd;
8131 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8132 /* determine the type of structures to use */
8133 infoPtr->hwndNotify = lpcs->hwndParent;
8134 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8136 /* initialize color information */
8137 infoPtr->clrBk = CLR_NONE;
8138 infoPtr->clrText = CLR_DEFAULT;
8139 infoPtr->clrTextBk = CLR_DEFAULT;
8140 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8142 /* set default values */
8143 infoPtr->nFocusedItem = -1;
8144 infoPtr->nSelectionMark = -1;
8145 infoPtr->nHotItem = -1;
8146 infoPtr->bRedraw = TRUE;
8147 infoPtr->bNoItemMetrics = TRUE;
8148 infoPtr->bDoChangeNotify = TRUE;
8149 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8150 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8151 infoPtr->nEditLabelItem = -1;
8152 infoPtr->dwHoverTime = -1; /* default system hover time */
8153 infoPtr->nMeasureItemHeight = 0;
8154 infoPtr->xTrackLine = -1; /* no track line */
8155 infoPtr->itemEdit.fEnabled = FALSE;
8156 infoPtr->iVersion = COMCTL32_VERSION;
8158 /* get default font (icon title) */
8159 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8160 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8161 infoPtr->hFont = infoPtr->hDefaultFont;
8162 LISTVIEW_SaveTextMetrics(infoPtr);
8164 /* allocate memory for the data structure */
8165 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8166 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8167 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8168 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8169 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8170 return TRUE;
8172 fail:
8173 DestroyWindow(infoPtr->hwndHeader);
8174 ranges_destroy(infoPtr->selectionRanges);
8175 DPA_Destroy(infoPtr->hdpaItems);
8176 DPA_Destroy(infoPtr->hdpaPosX);
8177 DPA_Destroy(infoPtr->hdpaPosY);
8178 DPA_Destroy(infoPtr->hdpaColumns);
8179 Free(infoPtr);
8180 return FALSE;
8183 /***
8184 * DESCRIPTION:
8185 * Creates the listview control - the WM_CREATE phase. Most of the data is
8186 * already set up in LISTVIEW_NCCreate
8188 * PARAMETER(S):
8189 * [I] hwnd : window handle
8190 * [I] lpcs : the create parameters
8192 * RETURN:
8193 * Success: 0
8194 * Failure: -1
8196 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8198 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8199 UINT uView = lpcs->style & LVS_TYPEMASK;
8201 TRACE("(lpcs=%p)\n", lpcs);
8203 infoPtr->dwStyle = lpcs->style;
8204 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8205 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8207 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8209 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8211 else
8212 infoPtr->hwndHeader = 0;
8214 /* init item size to avoid division by 0 */
8215 LISTVIEW_UpdateItemSize (infoPtr);
8217 if (uView == LVS_REPORT)
8219 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8221 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8223 LISTVIEW_UpdateScroll(infoPtr);
8226 OpenThemeData(hwnd, themeClass);
8228 /* initialize the icon sizes */
8229 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8230 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8231 return 0;
8234 /***
8235 * DESCRIPTION:
8236 * Destroys the listview control.
8238 * PARAMETER(S):
8239 * [I] infoPtr : valid pointer to the listview structure
8241 * RETURN:
8242 * Success: 0
8243 * Failure: -1
8245 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8247 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8248 CloseThemeData(theme);
8249 return 0;
8252 /***
8253 * DESCRIPTION:
8254 * Enables the listview control.
8256 * PARAMETER(S):
8257 * [I] infoPtr : valid pointer to the listview structure
8258 * [I] bEnable : specifies whether to enable or disable the window
8260 * RETURN:
8261 * SUCCESS : TRUE
8262 * FAILURE : FALSE
8264 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8266 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8267 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8268 return TRUE;
8271 /***
8272 * DESCRIPTION:
8273 * Erases the background of the listview control.
8275 * PARAMETER(S):
8276 * [I] infoPtr : valid pointer to the listview structure
8277 * [I] hdc : device context handle
8279 * RETURN:
8280 * SUCCESS : TRUE
8281 * FAILURE : FALSE
8283 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8285 RECT rc;
8287 TRACE("(hdc=%p)\n", hdc);
8289 if (!GetClipBox(hdc, &rc)) return FALSE;
8291 if (infoPtr->clrBk == CLR_NONE)
8292 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8294 /* for double buffered controls we need to do this during refresh */
8295 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8297 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8301 /***
8302 * DESCRIPTION:
8303 * Helper function for LISTVIEW_[HV]Scroll *only*.
8304 * Performs vertical/horizontal scrolling by a give amount.
8306 * PARAMETER(S):
8307 * [I] infoPtr : valid pointer to the listview structure
8308 * [I] dx : amount of horizontal scroll
8309 * [I] dy : amount of vertical scroll
8311 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8313 /* now we can scroll the list */
8314 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8315 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8316 /* if we have focus, adjust rect */
8317 OffsetRect(&infoPtr->rcFocus, dx, dy);
8318 UpdateWindow(infoPtr->hwndSelf);
8321 /***
8322 * DESCRIPTION:
8323 * Performs vertical scrolling.
8325 * PARAMETER(S):
8326 * [I] infoPtr : valid pointer to the listview structure
8327 * [I] nScrollCode : scroll code
8328 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8329 * [I] hScrollWnd : scrollbar control window handle
8331 * RETURN:
8332 * Zero
8334 * NOTES:
8335 * SB_LINEUP/SB_LINEDOWN:
8336 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8337 * for LVS_REPORT is 1 line
8338 * for LVS_LIST cannot occur
8341 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8342 INT nScrollDiff, HWND hScrollWnd)
8344 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8345 INT nOldScrollPos, nNewScrollPos;
8346 SCROLLINFO scrollInfo;
8347 BOOL is_an_icon;
8349 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8350 debugscrollcode(nScrollCode), nScrollDiff);
8352 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8354 scrollInfo.cbSize = sizeof(SCROLLINFO);
8355 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8357 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8359 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8361 nOldScrollPos = scrollInfo.nPos;
8362 switch (nScrollCode)
8364 case SB_INTERNAL:
8365 break;
8367 case SB_LINEUP:
8368 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8369 break;
8371 case SB_LINEDOWN:
8372 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8373 break;
8375 case SB_PAGEUP:
8376 nScrollDiff = -scrollInfo.nPage;
8377 break;
8379 case SB_PAGEDOWN:
8380 nScrollDiff = scrollInfo.nPage;
8381 break;
8383 case SB_THUMBPOSITION:
8384 case SB_THUMBTRACK:
8385 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8386 break;
8388 default:
8389 nScrollDiff = 0;
8392 /* quit right away if pos isn't changing */
8393 if (nScrollDiff == 0) return 0;
8395 /* calculate new position, and handle overflows */
8396 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8397 if (nScrollDiff > 0) {
8398 if (nNewScrollPos < nOldScrollPos ||
8399 nNewScrollPos > scrollInfo.nMax)
8400 nNewScrollPos = scrollInfo.nMax;
8401 } else {
8402 if (nNewScrollPos > nOldScrollPos ||
8403 nNewScrollPos < scrollInfo.nMin)
8404 nNewScrollPos = scrollInfo.nMin;
8407 /* set the new position, and reread in case it changed */
8408 scrollInfo.fMask = SIF_POS;
8409 scrollInfo.nPos = nNewScrollPos;
8410 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8412 /* carry on only if it really changed */
8413 if (nNewScrollPos == nOldScrollPos) return 0;
8415 /* now adjust to client coordinates */
8416 nScrollDiff = nOldScrollPos - nNewScrollPos;
8417 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8419 /* and scroll the window */
8420 scroll_list(infoPtr, 0, nScrollDiff);
8422 return 0;
8425 /***
8426 * DESCRIPTION:
8427 * Performs horizontal scrolling.
8429 * PARAMETER(S):
8430 * [I] infoPtr : valid pointer to the listview structure
8431 * [I] nScrollCode : scroll code
8432 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8433 * [I] hScrollWnd : scrollbar control window handle
8435 * RETURN:
8436 * Zero
8438 * NOTES:
8439 * SB_LINELEFT/SB_LINERIGHT:
8440 * for LVS_ICON, LVS_SMALLICON 1 pixel
8441 * for LVS_REPORT is 1 pixel
8442 * for LVS_LIST is 1 column --> which is a 1 because the
8443 * scroll is based on columns not pixels
8446 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8447 INT nScrollDiff, HWND hScrollWnd)
8449 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8450 INT nOldScrollPos, nNewScrollPos;
8451 SCROLLINFO scrollInfo;
8453 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8454 debugscrollcode(nScrollCode), nScrollDiff);
8456 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8458 scrollInfo.cbSize = sizeof(SCROLLINFO);
8459 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8461 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8463 nOldScrollPos = scrollInfo.nPos;
8465 switch (nScrollCode)
8467 case SB_INTERNAL:
8468 break;
8470 case SB_LINELEFT:
8471 nScrollDiff = -1;
8472 break;
8474 case SB_LINERIGHT:
8475 nScrollDiff = 1;
8476 break;
8478 case SB_PAGELEFT:
8479 nScrollDiff = -scrollInfo.nPage;
8480 break;
8482 case SB_PAGERIGHT:
8483 nScrollDiff = scrollInfo.nPage;
8484 break;
8486 case SB_THUMBPOSITION:
8487 case SB_THUMBTRACK:
8488 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8489 break;
8491 default:
8492 nScrollDiff = 0;
8495 /* quit right away if pos isn't changing */
8496 if (nScrollDiff == 0) return 0;
8498 /* calculate new position, and handle overflows */
8499 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8500 if (nScrollDiff > 0) {
8501 if (nNewScrollPos < nOldScrollPos ||
8502 nNewScrollPos > scrollInfo.nMax)
8503 nNewScrollPos = scrollInfo.nMax;
8504 } else {
8505 if (nNewScrollPos > nOldScrollPos ||
8506 nNewScrollPos < scrollInfo.nMin)
8507 nNewScrollPos = scrollInfo.nMin;
8510 /* set the new position, and reread in case it changed */
8511 scrollInfo.fMask = SIF_POS;
8512 scrollInfo.nPos = nNewScrollPos;
8513 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8515 /* carry on only if it really changed */
8516 if (nNewScrollPos == nOldScrollPos) return 0;
8518 if(uView == LVS_REPORT)
8519 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8521 /* now adjust to client coordinates */
8522 nScrollDiff = nOldScrollPos - nNewScrollPos;
8523 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8525 /* and scroll the window */
8526 scroll_list(infoPtr, nScrollDiff, 0);
8528 return 0;
8531 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8533 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8534 INT gcWheelDelta = 0;
8535 INT pulScrollLines = 3;
8536 SCROLLINFO scrollInfo;
8538 TRACE("(wheelDelta=%d)\n", wheelDelta);
8540 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8541 gcWheelDelta -= wheelDelta;
8543 scrollInfo.cbSize = sizeof(SCROLLINFO);
8544 scrollInfo.fMask = SIF_POS;
8546 switch(uView)
8548 case LVS_ICON:
8549 case LVS_SMALLICON:
8551 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8552 * should be fixed in the future.
8554 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8555 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8556 break;
8558 case LVS_REPORT:
8559 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8561 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8562 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8563 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8565 break;
8567 case LVS_LIST:
8568 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8569 break;
8571 return 0;
8574 /***
8575 * DESCRIPTION:
8576 * ???
8578 * PARAMETER(S):
8579 * [I] infoPtr : valid pointer to the listview structure
8580 * [I] nVirtualKey : virtual key
8581 * [I] lKeyData : key data
8583 * RETURN:
8584 * Zero
8586 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8588 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8589 HWND hwndSelf = infoPtr->hwndSelf;
8590 INT nItem = -1;
8591 NMLVKEYDOWN nmKeyDown;
8593 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8595 /* send LVN_KEYDOWN notification */
8596 nmKeyDown.wVKey = nVirtualKey;
8597 nmKeyDown.flags = 0;
8598 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8599 if (!IsWindow(hwndSelf))
8600 return 0;
8602 switch (nVirtualKey)
8604 case VK_SPACE:
8605 nItem = infoPtr->nFocusedItem;
8606 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8607 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8608 break;
8610 case VK_RETURN:
8611 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8613 if (!notify(infoPtr, NM_RETURN)) return 0;
8614 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8616 break;
8618 case VK_HOME:
8619 if (infoPtr->nItemCount > 0)
8620 nItem = 0;
8621 break;
8623 case VK_END:
8624 if (infoPtr->nItemCount > 0)
8625 nItem = infoPtr->nItemCount - 1;
8626 break;
8628 case VK_LEFT:
8629 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
8630 break;
8632 case VK_UP:
8633 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
8634 break;
8636 case VK_RIGHT:
8637 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
8638 break;
8640 case VK_DOWN:
8641 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
8642 break;
8644 case VK_PRIOR:
8645 if (uView == LVS_REPORT)
8647 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8648 if (infoPtr->nFocusedItem == topidx)
8649 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8650 else
8651 nItem = topidx;
8653 else
8654 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8655 * LISTVIEW_GetCountPerRow(infoPtr);
8656 if(nItem < 0) nItem = 0;
8657 break;
8659 case VK_NEXT:
8660 if (uView == LVS_REPORT)
8662 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8663 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8664 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8665 nItem = infoPtr->nFocusedItem + cnt - 1;
8666 else
8667 nItem = topidx + cnt - 1;
8669 else
8670 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8671 * LISTVIEW_GetCountPerRow(infoPtr);
8672 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8673 break;
8676 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8677 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8679 return 0;
8682 /***
8683 * DESCRIPTION:
8684 * Kills the focus.
8686 * PARAMETER(S):
8687 * [I] infoPtr : valid pointer to the listview structure
8689 * RETURN:
8690 * Zero
8692 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8694 TRACE("()\n");
8696 /* if we did not have the focus, there's nothing to do */
8697 if (!infoPtr->bFocus) return 0;
8699 /* send NM_KILLFOCUS notification */
8700 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8702 /* if we have a focus rectangle, get rid of it */
8703 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8705 /* set window focus flag */
8706 infoPtr->bFocus = FALSE;
8708 /* invalidate the selected items before resetting focus flag */
8709 LISTVIEW_InvalidateSelectedItems(infoPtr);
8711 return 0;
8714 /***
8715 * DESCRIPTION:
8716 * Processes double click messages (left mouse button).
8718 * PARAMETER(S):
8719 * [I] infoPtr : valid pointer to the listview structure
8720 * [I] wKey : key flag
8721 * [I] x,y : mouse coordinate
8723 * RETURN:
8724 * Zero
8726 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8728 LVHITTESTINFO htInfo;
8730 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8732 /* Cancel the item edition if any */
8733 if (infoPtr->itemEdit.fEnabled)
8735 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8736 infoPtr->itemEdit.fEnabled = FALSE;
8739 /* send NM_RELEASEDCAPTURE notification */
8740 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8742 htInfo.pt.x = x;
8743 htInfo.pt.y = y;
8745 /* send NM_DBLCLK notification */
8746 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8747 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8749 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8750 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8752 return 0;
8755 /***
8756 * DESCRIPTION:
8757 * Processes mouse down messages (left mouse button).
8759 * PARAMETERS:
8760 * infoPtr [I ] valid pointer to the listview structure
8761 * wKey [I ] key flag
8762 * x,y [I ] mouse coordinate
8764 * RETURN:
8765 * Zero
8767 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8769 LVHITTESTINFO lvHitTestInfo;
8770 static BOOL bGroupSelect = TRUE;
8771 POINT pt = { x, y };
8772 INT nItem;
8774 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8776 /* send NM_RELEASEDCAPTURE notification */
8777 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8779 /* set left button down flag and record the click position */
8780 infoPtr->bLButtonDown = TRUE;
8781 infoPtr->ptClickPos = pt;
8782 infoPtr->bDragging = FALSE;
8784 lvHitTestInfo.pt.x = x;
8785 lvHitTestInfo.pt.y = y;
8787 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8788 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8789 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8791 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8793 toggle_checkbox_state(infoPtr, nItem);
8794 return 0;
8797 if (infoPtr->dwStyle & LVS_SINGLESEL)
8799 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8800 infoPtr->nEditLabelItem = nItem;
8801 else
8802 LISTVIEW_SetSelection(infoPtr, nItem);
8804 else
8806 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8808 if (bGroupSelect)
8810 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8811 LISTVIEW_SetItemFocus(infoPtr, nItem);
8812 infoPtr->nSelectionMark = nItem;
8814 else
8816 LVITEMW item;
8818 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8819 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8821 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8822 infoPtr->nSelectionMark = nItem;
8825 else if (wKey & MK_CONTROL)
8827 LVITEMW item;
8829 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8831 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8832 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8833 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8834 infoPtr->nSelectionMark = nItem;
8836 else if (wKey & MK_SHIFT)
8838 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8840 else
8842 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8843 infoPtr->nEditLabelItem = nItem;
8845 /* set selection (clears other pre-existing selections) */
8846 LISTVIEW_SetSelection(infoPtr, nItem);
8850 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8851 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8853 else
8855 /* remove all selections */
8856 LISTVIEW_DeselectAll(infoPtr);
8857 ReleaseCapture();
8860 return 0;
8863 /***
8864 * DESCRIPTION:
8865 * Processes mouse up messages (left mouse button).
8867 * PARAMETERS:
8868 * infoPtr [I ] valid pointer to the listview structure
8869 * wKey [I ] key flag
8870 * x,y [I ] mouse coordinate
8872 * RETURN:
8873 * Zero
8875 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8877 LVHITTESTINFO lvHitTestInfo;
8879 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8881 if (!infoPtr->bLButtonDown) return 0;
8883 lvHitTestInfo.pt.x = x;
8884 lvHitTestInfo.pt.y = y;
8886 /* send NM_CLICK notification */
8887 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8888 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8890 /* set left button flag */
8891 infoPtr->bLButtonDown = FALSE;
8893 if (infoPtr->bDragging)
8895 infoPtr->bDragging = FALSE;
8896 return 0;
8899 /* if we clicked on a selected item, edit the label */
8900 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8902 /* we want to make sure the user doesn't want to do a double click. So we will
8903 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8905 infoPtr->itemEdit.fEnabled = TRUE;
8906 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8907 SetTimer(infoPtr->hwndSelf,
8908 (UINT_PTR)&infoPtr->itemEdit,
8909 GetDoubleClickTime(),
8910 LISTVIEW_DelayedEditItem);
8913 if (!infoPtr->bFocus)
8914 SetFocus(infoPtr->hwndSelf);
8916 return 0;
8919 /***
8920 * DESCRIPTION:
8921 * Destroys the listview control (called after WM_DESTROY).
8923 * PARAMETER(S):
8924 * [I] infoPtr : valid pointer to the listview structure
8926 * RETURN:
8927 * Zero
8929 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8931 TRACE("()\n");
8933 /* delete all items */
8934 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8936 /* destroy data structure */
8937 DPA_Destroy(infoPtr->hdpaItems);
8938 DPA_Destroy(infoPtr->hdpaPosX);
8939 DPA_Destroy(infoPtr->hdpaPosY);
8940 DPA_Destroy(infoPtr->hdpaColumns);
8941 ranges_destroy(infoPtr->selectionRanges);
8943 /* destroy image lists */
8944 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8946 if (infoPtr->himlNormal)
8947 ImageList_Destroy(infoPtr->himlNormal);
8948 if (infoPtr->himlSmall)
8949 ImageList_Destroy(infoPtr->himlSmall);
8950 if (infoPtr->himlState)
8951 ImageList_Destroy(infoPtr->himlState);
8954 /* destroy font, bkgnd brush */
8955 infoPtr->hFont = 0;
8956 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8957 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8959 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8961 /* free listview info pointer*/
8962 Free(infoPtr);
8964 return 0;
8967 /***
8968 * DESCRIPTION:
8969 * Handles notifications from header.
8971 * PARAMETER(S):
8972 * [I] infoPtr : valid pointer to the listview structure
8973 * [I] nCtrlId : control identifier
8974 * [I] lpnmh : notification information
8976 * RETURN:
8977 * Zero
8979 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8981 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8982 HWND hwndSelf = infoPtr->hwndSelf;
8984 TRACE("(lpnmh=%p)\n", lpnmh);
8986 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8988 switch (lpnmh->hdr.code)
8990 case HDN_TRACKW:
8991 case HDN_TRACKA:
8993 COLUMN_INFO *lpColumnInfo;
8994 POINT ptOrigin;
8995 INT x;
8997 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8998 break;
9000 /* remove the old line (if any) */
9001 LISTVIEW_DrawTrackLine(infoPtr);
9003 /* compute & draw the new line */
9004 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9005 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9006 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9007 infoPtr->xTrackLine = x + ptOrigin.x;
9008 LISTVIEW_DrawTrackLine(infoPtr);
9009 break;
9012 case HDN_ENDTRACKA:
9013 case HDN_ENDTRACKW:
9014 /* remove the track line (if any) */
9015 LISTVIEW_DrawTrackLine(infoPtr);
9016 infoPtr->xTrackLine = -1;
9017 break;
9019 case HDN_ENDDRAG:
9020 FIXME("Changing column order not implemented\n");
9021 return TRUE;
9023 case HDN_ITEMCHANGINGW:
9024 case HDN_ITEMCHANGINGA:
9025 return notify_forward_header(infoPtr, lpnmh);
9027 case HDN_ITEMCHANGEDW:
9028 case HDN_ITEMCHANGEDA:
9030 COLUMN_INFO *lpColumnInfo;
9031 INT dx, cxy;
9033 notify_forward_header(infoPtr, lpnmh);
9034 if (!IsWindow(hwndSelf))
9035 break;
9037 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9039 HDITEMW hdi;
9041 hdi.mask = HDI_WIDTH;
9042 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
9043 cxy = hdi.cxy;
9045 else
9046 cxy = lpnmh->pitem->cxy;
9048 /* determine how much we change since the last know position */
9049 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9050 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9051 if (dx != 0)
9053 lpColumnInfo->rcHeader.right += dx;
9054 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9055 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9056 else
9058 /* only needs to update the scrolls */
9059 infoPtr->nItemWidth += dx;
9060 LISTVIEW_UpdateScroll(infoPtr);
9062 LISTVIEW_UpdateItemSize(infoPtr);
9063 if (uView == LVS_REPORT && is_redrawing(infoPtr))
9065 POINT ptOrigin;
9066 RECT rcCol = lpColumnInfo->rcHeader;
9068 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9069 OffsetRect(&rcCol, ptOrigin.x, 0);
9071 rcCol.top = infoPtr->rcList.top;
9072 rcCol.bottom = infoPtr->rcList.bottom;
9074 /* resizing left-aligned columns leaves most of the left side untouched */
9075 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9077 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9078 if (dx > 0)
9079 nMaxDirty += dx;
9080 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9083 /* when shrinking the last column clear the now unused field */
9084 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9086 RECT right;
9088 rcCol.right -= dx;
9090 /* deal with right from rightmost column area */
9091 right.left = rcCol.right;
9092 right.top = rcCol.top;
9093 right.bottom = rcCol.bottom;
9094 right.right = infoPtr->rcList.right;
9096 LISTVIEW_InvalidateRect(infoPtr, &right);
9099 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9103 break;
9105 case HDN_ITEMCLICKW:
9106 case HDN_ITEMCLICKA:
9108 /* Handle sorting by Header Column */
9109 NMLISTVIEW nmlv;
9111 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9112 nmlv.iItem = -1;
9113 nmlv.iSubItem = lpnmh->iItem;
9114 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9115 notify_forward_header(infoPtr, lpnmh);
9117 break;
9119 case HDN_DIVIDERDBLCLICKW:
9120 case HDN_DIVIDERDBLCLICKA:
9121 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9122 break;
9125 return 0;
9128 /***
9129 * DESCRIPTION:
9130 * Paint non-client area of control.
9132 * PARAMETER(S):
9133 * [I] infoPtr : valid pointer to the listview structureof the sender
9134 * [I] region : update region
9136 * RETURN:
9137 * TRUE - frame was painted
9138 * FALSE - call default window proc
9140 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9142 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9143 HDC dc;
9144 RECT r;
9145 HRGN cliprgn;
9146 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9147 cyEdge = GetSystemMetrics (SM_CYEDGE);
9149 if (!theme) return FALSE;
9151 GetWindowRect(infoPtr->hwndSelf, &r);
9153 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9154 r.right - cxEdge, r.bottom - cyEdge);
9155 if (region != (HRGN)1)
9156 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9157 OffsetRect(&r, -r.left, -r.top);
9159 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9160 OffsetRect(&r, -r.left, -r.top);
9162 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9163 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9164 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9165 ReleaseDC(infoPtr->hwndSelf, dc);
9167 /* Call default proc to get the scrollbars etc. painted */
9168 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9170 return TRUE;
9173 /***
9174 * DESCRIPTION:
9175 * Determines the type of structure to use.
9177 * PARAMETER(S):
9178 * [I] infoPtr : valid pointer to the listview structureof the sender
9179 * [I] hwndFrom : listview window handle
9180 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9182 * RETURN:
9183 * Zero
9185 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9187 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9189 if (nCommand == NF_REQUERY)
9190 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9192 return infoPtr->notifyFormat;
9195 /***
9196 * DESCRIPTION:
9197 * Paints/Repaints the listview control.
9199 * PARAMETER(S):
9200 * [I] infoPtr : valid pointer to the listview structure
9201 * [I] hdc : device context handle
9203 * RETURN:
9204 * Zero
9206 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9208 TRACE("(hdc=%p)\n", hdc);
9210 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9212 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9214 infoPtr->bNoItemMetrics = FALSE;
9215 LISTVIEW_UpdateItemSize(infoPtr);
9216 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9217 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9218 LISTVIEW_UpdateScroll(infoPtr);
9221 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9223 if (hdc)
9224 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9225 else
9227 PAINTSTRUCT ps;
9229 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9230 if (!hdc) return 1;
9231 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9232 EndPaint(infoPtr->hwndSelf, &ps);
9235 return 0;
9239 /***
9240 * DESCRIPTION:
9241 * Paints/Repaints the listview control.
9243 * PARAMETER(S):
9244 * [I] infoPtr : valid pointer to the listview structure
9245 * [I] hdc : device context handle
9246 * [I] options : drawing options
9248 * RETURN:
9249 * Zero
9251 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9253 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9255 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9256 return 0;
9258 if (options & PRF_ERASEBKGND)
9259 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9261 if (options & PRF_CLIENT)
9262 LISTVIEW_Paint(infoPtr, hdc);
9264 return 0;
9268 /***
9269 * DESCRIPTION:
9270 * Processes double click messages (right mouse button).
9272 * PARAMETER(S):
9273 * [I] infoPtr : valid pointer to the listview structure
9274 * [I] wKey : key flag
9275 * [I] x,y : mouse coordinate
9277 * RETURN:
9278 * Zero
9280 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9282 LVHITTESTINFO lvHitTestInfo;
9284 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9286 /* send NM_RELEASEDCAPTURE notification */
9287 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9289 /* send NM_RDBLCLK notification */
9290 lvHitTestInfo.pt.x = x;
9291 lvHitTestInfo.pt.y = y;
9292 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9293 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9295 return 0;
9298 /***
9299 * DESCRIPTION:
9300 * Processes mouse down messages (right mouse button).
9302 * PARAMETER(S):
9303 * [I] infoPtr : valid pointer to the listview structure
9304 * [I] wKey : key flag
9305 * [I] x,y : mouse coordinate
9307 * RETURN:
9308 * Zero
9310 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9312 LVHITTESTINFO lvHitTestInfo;
9313 INT nItem;
9315 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9317 /* send NM_RELEASEDCAPTURE notification */
9318 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9320 /* make sure the listview control window has the focus */
9321 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9323 /* set right button down flag */
9324 infoPtr->bRButtonDown = TRUE;
9326 /* determine the index of the selected item */
9327 lvHitTestInfo.pt.x = x;
9328 lvHitTestInfo.pt.y = y;
9329 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9331 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9333 LISTVIEW_SetItemFocus(infoPtr, nItem);
9334 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9335 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9336 LISTVIEW_SetSelection(infoPtr, nItem);
9338 else
9340 LISTVIEW_DeselectAll(infoPtr);
9343 return 0;
9346 /***
9347 * DESCRIPTION:
9348 * Processes mouse up messages (right mouse button).
9350 * PARAMETER(S):
9351 * [I] infoPtr : valid pointer to the listview structure
9352 * [I] wKey : key flag
9353 * [I] x,y : mouse coordinate
9355 * RETURN:
9356 * Zero
9358 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9360 LVHITTESTINFO lvHitTestInfo;
9361 POINT pt;
9363 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9365 if (!infoPtr->bRButtonDown) return 0;
9367 /* set button flag */
9368 infoPtr->bRButtonDown = FALSE;
9370 /* Send NM_RCLICK notification */
9371 lvHitTestInfo.pt.x = x;
9372 lvHitTestInfo.pt.y = y;
9373 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9374 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9376 /* Change to screen coordinate for WM_CONTEXTMENU */
9377 pt = lvHitTestInfo.pt;
9378 ClientToScreen(infoPtr->hwndSelf, &pt);
9380 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9381 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9382 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9384 return 0;
9388 /***
9389 * DESCRIPTION:
9390 * Sets the cursor.
9392 * PARAMETER(S):
9393 * [I] infoPtr : valid pointer to the listview structure
9394 * [I] hwnd : window handle of window containing the cursor
9395 * [I] nHittest : hit-test code
9396 * [I] wMouseMsg : ideintifier of the mouse message
9398 * RETURN:
9399 * TRUE if cursor is set
9400 * FALSE otherwise
9402 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9404 LVHITTESTINFO lvHitTestInfo;
9406 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9408 if(!infoPtr->hHotCursor) return FALSE;
9410 GetCursorPos(&lvHitTestInfo.pt);
9411 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9413 SetCursor(infoPtr->hHotCursor);
9415 return TRUE;
9418 /***
9419 * DESCRIPTION:
9420 * Sets the focus.
9422 * PARAMETER(S):
9423 * [I] infoPtr : valid pointer to the listview structure
9424 * [I] hwndLoseFocus : handle of previously focused window
9426 * RETURN:
9427 * Zero
9429 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9431 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9433 /* if we have the focus already, there's nothing to do */
9434 if (infoPtr->bFocus) return 0;
9436 /* send NM_SETFOCUS notification */
9437 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9439 /* set window focus flag */
9440 infoPtr->bFocus = TRUE;
9442 /* put the focus rect back on */
9443 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9445 /* redraw all visible selected items */
9446 LISTVIEW_InvalidateSelectedItems(infoPtr);
9448 return 0;
9451 /***
9452 * DESCRIPTION:
9453 * Sets the font.
9455 * PARAMETER(S):
9456 * [I] infoPtr : valid pointer to the listview structure
9457 * [I] fRedraw : font handle
9458 * [I] fRedraw : redraw flag
9460 * RETURN:
9461 * Zero
9463 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9465 HFONT oldFont = infoPtr->hFont;
9467 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9469 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9470 if (infoPtr->hFont == oldFont) return 0;
9472 LISTVIEW_SaveTextMetrics(infoPtr);
9474 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9476 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9477 LISTVIEW_UpdateSize(infoPtr);
9478 LISTVIEW_UpdateScroll(infoPtr);
9481 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9483 return 0;
9486 /***
9487 * DESCRIPTION:
9488 * Message handling for WM_SETREDRAW.
9489 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9491 * PARAMETER(S):
9492 * [I] infoPtr : valid pointer to the listview structure
9493 * [I] bRedraw: state of redraw flag
9495 * RETURN:
9496 * DefWinProc return value
9498 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9500 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9502 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9503 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9505 infoPtr->bRedraw = bRedraw;
9507 if(!bRedraw) return 0;
9509 if (is_autoarrange(infoPtr))
9510 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9511 LISTVIEW_UpdateScroll(infoPtr);
9513 /* despite what the WM_SETREDRAW docs says, apps expect us
9514 * to invalidate the listview here... stupid! */
9515 LISTVIEW_InvalidateList(infoPtr);
9517 return 0;
9520 /***
9521 * DESCRIPTION:
9522 * Resizes the listview control. This function processes WM_SIZE
9523 * messages. At this time, the width and height are not used.
9525 * PARAMETER(S):
9526 * [I] infoPtr : valid pointer to the listview structure
9527 * [I] Width : new width
9528 * [I] Height : new height
9530 * RETURN:
9531 * Zero
9533 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9535 RECT rcOld = infoPtr->rcList;
9537 TRACE("(width=%d, height=%d)\n", Width, Height);
9539 LISTVIEW_UpdateSize(infoPtr);
9540 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9542 /* do not bother with display related stuff if we're not redrawing */
9543 if (!is_redrawing(infoPtr)) return 0;
9545 if (is_autoarrange(infoPtr))
9546 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9548 LISTVIEW_UpdateScroll(infoPtr);
9550 /* refresh all only for lists whose height changed significantly */
9551 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9552 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9553 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9554 LISTVIEW_InvalidateList(infoPtr);
9556 return 0;
9559 /***
9560 * DESCRIPTION:
9561 * Sets the size information.
9563 * PARAMETER(S):
9564 * [I] infoPtr : valid pointer to the listview structure
9566 * RETURN:
9567 * None
9569 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9571 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9573 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9575 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9577 if (uView == LVS_LIST)
9579 /* Apparently the "LIST" style is supposed to have the same
9580 * number of items in a column even if there is no scroll bar.
9581 * Since if a scroll bar already exists then the bottom is already
9582 * reduced, only reduce if the scroll bar does not currently exist.
9583 * The "2" is there to mimic the native control. I think it may be
9584 * related to either padding or edges. (GLA 7/2002)
9586 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9587 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9588 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9590 else if (uView == LVS_REPORT)
9592 HDLAYOUT hl;
9593 WINDOWPOS wp;
9595 hl.prc = &infoPtr->rcList;
9596 hl.pwpos = &wp;
9597 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9598 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9599 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9600 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9601 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9602 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9604 infoPtr->rcList.top = max(wp.cy, 0);
9605 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9608 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9611 /***
9612 * DESCRIPTION:
9613 * Processes WM_STYLECHANGED messages.
9615 * PARAMETER(S):
9616 * [I] infoPtr : valid pointer to the listview structure
9617 * [I] wStyleType : window style type (normal or extended)
9618 * [I] lpss : window style information
9620 * RETURN:
9621 * Zero
9623 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9624 const STYLESTRUCT *lpss)
9626 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9627 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9628 UINT style;
9630 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9631 wStyleType, lpss->styleOld, lpss->styleNew);
9633 if (wStyleType != GWL_STYLE) return 0;
9635 infoPtr->dwStyle = lpss->styleNew;
9637 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9638 ((lpss->styleNew & WS_HSCROLL) == 0))
9639 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9641 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9642 ((lpss->styleNew & WS_VSCROLL) == 0))
9643 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9645 if (uNewView != uOldView)
9647 SIZE oldIconSize = infoPtr->iconSize;
9648 HIMAGELIST himl;
9650 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9651 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9653 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9654 SetRectEmpty(&infoPtr->rcFocus);
9656 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9657 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9659 if (uNewView == LVS_ICON)
9661 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9663 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9664 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9665 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9668 else if (uNewView == LVS_REPORT)
9670 HDLAYOUT hl;
9671 WINDOWPOS wp;
9673 LISTVIEW_CreateHeader( infoPtr );
9675 hl.prc = &infoPtr->rcList;
9676 hl.pwpos = &wp;
9677 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9678 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9679 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9680 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9683 LISTVIEW_UpdateItemSize(infoPtr);
9686 if (uNewView == LVS_REPORT)
9688 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9690 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9692 /* Turn off the header control */
9693 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9694 TRACE("Hide header control, was 0x%08x\n", style);
9695 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9696 } else {
9697 /* Turn on the header control */
9698 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9700 TRACE("Show header control, was 0x%08x\n", style);
9701 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9707 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9708 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9709 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9711 /* update the size of the client area */
9712 LISTVIEW_UpdateSize(infoPtr);
9714 /* add scrollbars if needed */
9715 LISTVIEW_UpdateScroll(infoPtr);
9717 /* invalidate client area + erase background */
9718 LISTVIEW_InvalidateList(infoPtr);
9720 return 0;
9723 /***
9724 * DESCRIPTION:
9725 * Processes WM_STYLECHANGING messages.
9727 * PARAMETER(S):
9728 * [I] infoPtr : valid pointer to the listview structure
9729 * [I] wStyleType : window style type (normal or extended)
9730 * [I0] lpss : window style information
9732 * RETURN:
9733 * Zero
9735 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9736 STYLESTRUCT *lpss)
9738 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9739 wStyleType, lpss->styleOld, lpss->styleNew);
9741 /* don't forward LVS_OWNERDATA only if not already set to */
9742 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9744 if (lpss->styleOld & LVS_OWNERDATA)
9745 lpss->styleNew |= LVS_OWNERDATA;
9746 else
9747 lpss->styleNew &= ~LVS_OWNERDATA;
9750 return 0;
9753 /***
9754 * DESCRIPTION:
9755 * Processes WM_SHOWWINDOW messages.
9757 * PARAMETER(S):
9758 * [I] infoPtr : valid pointer to the listview structure
9759 * [I] bShown : window is being shown (FALSE when hidden)
9760 * [I] iStatus : window show status
9762 * RETURN:
9763 * Zero
9765 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9767 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9769 /* header delayed creation */
9770 if ((uView == LVS_REPORT) && bShown)
9772 LISTVIEW_CreateHeader(infoPtr);
9774 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9775 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9778 return 0;
9781 /***
9782 * DESCRIPTION:
9783 * Processes CCM_GETVERSION messages.
9785 * PARAMETER(S):
9786 * [I] infoPtr : valid pointer to the listview structure
9788 * RETURN:
9789 * Current version
9791 static inline LRESULT LISTVIEW_GetVersion(LISTVIEW_INFO *infoPtr)
9793 return infoPtr->iVersion;
9796 /***
9797 * DESCRIPTION:
9798 * Processes CCM_SETVERSION messages.
9800 * PARAMETER(S):
9801 * [I] infoPtr : valid pointer to the listview structure
9802 * [I] iVersion : version to be set
9804 * RETURN:
9805 * -1 when requested version is greater than DLL version;
9806 * previous version otherwise
9808 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
9810 INT iOldVersion = infoPtr->iVersion;
9812 if (iVersion > COMCTL32_VERSION)
9813 return -1;
9815 infoPtr->iVersion = iVersion;
9817 TRACE("new version %d\n", iVersion);
9819 return iOldVersion;
9822 /***
9823 * DESCRIPTION:
9824 * Window procedure of the listview control.
9827 static LRESULT WINAPI
9828 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9830 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9832 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9834 if (!infoPtr && (uMsg != WM_NCCREATE))
9835 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9837 switch (uMsg)
9839 case LVM_APPROXIMATEVIEWRECT:
9840 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9841 LOWORD(lParam), HIWORD(lParam));
9842 case LVM_ARRANGE:
9843 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9845 /* case LVM_CANCELEDITLABEL: */
9847 case LVM_CREATEDRAGIMAGE:
9848 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9850 case LVM_DELETEALLITEMS:
9851 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9853 case LVM_DELETECOLUMN:
9854 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9856 case LVM_DELETEITEM:
9857 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9859 case LVM_EDITLABELW:
9860 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9862 case LVM_EDITLABELA:
9863 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9865 /* case LVM_ENABLEGROUPVIEW: */
9867 case LVM_ENSUREVISIBLE:
9868 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9870 case LVM_FINDITEMW:
9871 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9873 case LVM_FINDITEMA:
9874 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9876 case LVM_GETBKCOLOR:
9877 return infoPtr->clrBk;
9879 /* case LVM_GETBKIMAGE: */
9881 case LVM_GETCALLBACKMASK:
9882 return infoPtr->uCallbackMask;
9884 case LVM_GETCOLUMNA:
9885 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9887 case LVM_GETCOLUMNW:
9888 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9890 case LVM_GETCOLUMNORDERARRAY:
9891 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9893 case LVM_GETCOLUMNWIDTH:
9894 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9896 case LVM_GETCOUNTPERPAGE:
9897 return LISTVIEW_GetCountPerPage(infoPtr);
9899 case LVM_GETEDITCONTROL:
9900 return (LRESULT)infoPtr->hwndEdit;
9902 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9903 return infoPtr->dwLvExStyle;
9905 /* case LVM_GETGROUPINFO: */
9907 /* case LVM_GETGROUPMETRICS: */
9909 case LVM_GETHEADER:
9910 return (LRESULT)infoPtr->hwndHeader;
9912 case LVM_GETHOTCURSOR:
9913 return (LRESULT)infoPtr->hHotCursor;
9915 case LVM_GETHOTITEM:
9916 return infoPtr->nHotItem;
9918 case LVM_GETHOVERTIME:
9919 return infoPtr->dwHoverTime;
9921 case LVM_GETIMAGELIST:
9922 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9924 /* case LVM_GETINSERTMARK: */
9926 /* case LVM_GETINSERTMARKCOLOR: */
9928 /* case LVM_GETINSERTMARKRECT: */
9930 case LVM_GETISEARCHSTRINGA:
9931 case LVM_GETISEARCHSTRINGW:
9932 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9933 return FALSE;
9935 case LVM_GETITEMA:
9936 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9938 case LVM_GETITEMW:
9939 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9941 case LVM_GETITEMCOUNT:
9942 return infoPtr->nItemCount;
9944 case LVM_GETITEMPOSITION:
9945 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9947 case LVM_GETITEMRECT:
9948 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9950 case LVM_GETITEMSPACING:
9951 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9953 case LVM_GETITEMSTATE:
9954 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9956 case LVM_GETITEMTEXTA:
9957 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9959 case LVM_GETITEMTEXTW:
9960 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9962 case LVM_GETNEXTITEM:
9963 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9965 case LVM_GETNUMBEROFWORKAREAS:
9966 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9967 return 1;
9969 case LVM_GETORIGIN:
9970 if (!lParam) return FALSE;
9971 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9972 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9973 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9974 return TRUE;
9976 /* case LVM_GETOUTLINECOLOR: */
9978 /* case LVM_GETSELECTEDCOLUMN: */
9980 case LVM_GETSELECTEDCOUNT:
9981 return LISTVIEW_GetSelectedCount(infoPtr);
9983 case LVM_GETSELECTIONMARK:
9984 return infoPtr->nSelectionMark;
9986 case LVM_GETSTRINGWIDTHA:
9987 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9989 case LVM_GETSTRINGWIDTHW:
9990 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9992 case LVM_GETSUBITEMRECT:
9993 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9995 case LVM_GETTEXTBKCOLOR:
9996 return infoPtr->clrTextBk;
9998 case LVM_GETTEXTCOLOR:
9999 return infoPtr->clrText;
10001 /* case LVM_GETTILEINFO: */
10003 /* case LVM_GETTILEVIEWINFO: */
10005 case LVM_GETTOOLTIPS:
10006 if( !infoPtr->hwndToolTip )
10007 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10008 return (LRESULT)infoPtr->hwndToolTip;
10010 case LVM_GETTOPINDEX:
10011 return LISTVIEW_GetTopIndex(infoPtr);
10013 case LVM_GETUNICODEFORMAT:
10014 return (infoPtr->notifyFormat == NFR_UNICODE);
10016 /* case LVM_GETVIEW: */
10018 case LVM_GETVIEWRECT:
10019 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10021 case LVM_GETWORKAREAS:
10022 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10023 return FALSE;
10025 /* case LVM_HASGROUP: */
10027 case LVM_HITTEST:
10028 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
10030 case LVM_INSERTCOLUMNA:
10031 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10033 case LVM_INSERTCOLUMNW:
10034 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10036 /* case LVM_INSERTGROUP: */
10038 /* case LVM_INSERTGROUPSORTED: */
10040 case LVM_INSERTITEMA:
10041 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10043 case LVM_INSERTITEMW:
10044 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10046 /* case LVM_INSERTMARKHITTEST: */
10048 /* case LVM_ISGROUPVIEWENABLED: */
10050 /* case LVM_MAPIDTOINDEX: */
10052 /* case LVM_MAPINDEXTOID: */
10054 /* case LVM_MOVEGROUP: */
10056 /* case LVM_MOVEITEMTOGROUP: */
10058 case LVM_REDRAWITEMS:
10059 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10061 /* case LVM_REMOVEALLGROUPS: */
10063 /* case LVM_REMOVEGROUP: */
10065 case LVM_SCROLL:
10066 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10068 case LVM_SETBKCOLOR:
10069 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10071 /* case LVM_SETBKIMAGE: */
10073 case LVM_SETCALLBACKMASK:
10074 infoPtr->uCallbackMask = (UINT)wParam;
10075 return TRUE;
10077 case LVM_SETCOLUMNA:
10078 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10080 case LVM_SETCOLUMNW:
10081 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10083 case LVM_SETCOLUMNORDERARRAY:
10084 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10086 case LVM_SETCOLUMNWIDTH:
10087 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10089 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10090 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10092 /* case LVM_SETGROUPINFO: */
10094 /* case LVM_SETGROUPMETRICS: */
10096 case LVM_SETHOTCURSOR:
10097 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10099 case LVM_SETHOTITEM:
10100 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10102 case LVM_SETHOVERTIME:
10103 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10105 case LVM_SETICONSPACING:
10106 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10108 case LVM_SETIMAGELIST:
10109 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10111 /* case LVM_SETINFOTIP: */
10113 /* case LVM_SETINSERTMARK: */
10115 /* case LVM_SETINSERTMARKCOLOR: */
10117 case LVM_SETITEMA:
10118 case LVM_SETITEMW:
10120 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10121 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10124 case LVM_SETITEMCOUNT:
10125 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10127 case LVM_SETITEMPOSITION:
10129 POINT pt;
10130 pt.x = (short)LOWORD(lParam);
10131 pt.y = (short)HIWORD(lParam);
10132 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10135 case LVM_SETITEMPOSITION32:
10136 if (lParam == 0) return FALSE;
10137 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10139 case LVM_SETITEMSTATE:
10140 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10142 case LVM_SETITEMTEXTA:
10143 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10145 case LVM_SETITEMTEXTW:
10146 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10148 /* case LVM_SETOUTLINECOLOR: */
10150 /* case LVM_SETSELECTEDCOLUMN: */
10152 case LVM_SETSELECTIONMARK:
10153 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10155 case LVM_SETTEXTBKCOLOR:
10156 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10158 case LVM_SETTEXTCOLOR:
10159 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10161 /* case LVM_SETTILEINFO: */
10163 /* case LVM_SETTILEVIEWINFO: */
10165 /* case LVM_SETTILEWIDTH: */
10167 case LVM_SETTOOLTIPS:
10168 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10170 case LVM_SETUNICODEFORMAT:
10171 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10173 /* case LVM_SETVIEW: */
10175 /* case LVM_SETWORKAREAS: */
10177 /* case LVM_SORTGROUPS: */
10179 case LVM_SORTITEMS:
10180 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10182 case LVM_SORTITEMSEX:
10183 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10185 case LVM_SUBITEMHITTEST:
10186 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10188 case LVM_UPDATE:
10189 return LISTVIEW_Update(infoPtr, (INT)wParam);
10191 case CCM_GETVERSION:
10192 return LISTVIEW_GetVersion(infoPtr);
10194 case CCM_SETVERSION:
10195 return LISTVIEW_SetVersion(infoPtr, wParam);
10197 case WM_CHAR:
10198 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10200 case WM_COMMAND:
10201 return LISTVIEW_Command(infoPtr, wParam, lParam);
10203 case WM_NCCREATE:
10204 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10206 case WM_CREATE:
10207 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10209 case WM_DESTROY:
10210 return LISTVIEW_Destroy(infoPtr);
10212 case WM_ENABLE:
10213 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10215 case WM_ERASEBKGND:
10216 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10218 case WM_GETDLGCODE:
10219 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10221 case WM_GETFONT:
10222 return (LRESULT)infoPtr->hFont;
10224 case WM_HSCROLL:
10225 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10227 case WM_KEYDOWN:
10228 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10230 case WM_KILLFOCUS:
10231 return LISTVIEW_KillFocus(infoPtr);
10233 case WM_LBUTTONDBLCLK:
10234 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10236 case WM_LBUTTONDOWN:
10237 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10239 case WM_LBUTTONUP:
10240 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10242 case WM_MOUSEMOVE:
10243 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10245 case WM_MOUSEHOVER:
10246 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10248 case WM_NCDESTROY:
10249 return LISTVIEW_NCDestroy(infoPtr);
10251 case WM_NCPAINT:
10252 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10253 return 0;
10254 goto fwd_msg;
10256 case WM_NOTIFY:
10257 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10258 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10259 else return 0;
10261 case WM_NOTIFYFORMAT:
10262 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10264 case WM_PRINTCLIENT:
10265 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10267 case WM_PAINT:
10268 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10270 case WM_RBUTTONDBLCLK:
10271 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10273 case WM_RBUTTONDOWN:
10274 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10276 case WM_RBUTTONUP:
10277 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10279 case WM_SETCURSOR:
10280 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10281 return TRUE;
10282 goto fwd_msg;
10284 case WM_SETFOCUS:
10285 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10287 case WM_SETFONT:
10288 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10290 case WM_SETREDRAW:
10291 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10293 case WM_SHOWWINDOW:
10294 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10295 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10297 case WM_SIZE:
10298 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10300 case WM_STYLECHANGED:
10301 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10303 case WM_STYLECHANGING:
10304 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10306 case WM_SYSCOLORCHANGE:
10307 COMCTL32_RefreshSysColors();
10308 return 0;
10310 /* case WM_TIMER: */
10311 case WM_THEMECHANGED:
10312 return LISTVIEW_ThemeChanged(infoPtr);
10314 case WM_VSCROLL:
10315 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10317 case WM_MOUSEWHEEL:
10318 if (wParam & (MK_SHIFT | MK_CONTROL))
10319 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10320 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10322 case WM_WINDOWPOSCHANGED:
10323 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10325 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10326 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10327 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10329 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10331 MEASUREITEMSTRUCT mis;
10332 mis.CtlType = ODT_LISTVIEW;
10333 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10334 mis.itemID = -1;
10335 mis.itemWidth = 0;
10336 mis.itemData = 0;
10337 mis.itemHeight= infoPtr->nItemHeight;
10338 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10339 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10340 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10343 LISTVIEW_UpdateSize(infoPtr);
10344 LISTVIEW_UpdateScroll(infoPtr);
10346 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10348 /* case WM_WININICHANGE: */
10350 default:
10351 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10352 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10354 fwd_msg:
10355 /* call default window procedure */
10356 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10361 /***
10362 * DESCRIPTION:
10363 * Registers the window class.
10365 * PARAMETER(S):
10366 * None
10368 * RETURN:
10369 * None
10371 void LISTVIEW_Register(void)
10373 WNDCLASSW wndClass;
10375 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10376 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10377 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10378 wndClass.cbClsExtra = 0;
10379 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10380 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10381 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10382 wndClass.lpszClassName = WC_LISTVIEWW;
10383 RegisterClassW(&wndClass);
10386 /***
10387 * DESCRIPTION:
10388 * Unregisters the window class.
10390 * PARAMETER(S):
10391 * None
10393 * RETURN:
10394 * None
10396 void LISTVIEW_Unregister(void)
10398 UnregisterClassW(WC_LISTVIEWW, NULL);
10401 /***
10402 * DESCRIPTION:
10403 * Handle any WM_COMMAND messages
10405 * PARAMETER(S):
10406 * [I] infoPtr : valid pointer to the listview structure
10407 * [I] wParam : the first message parameter
10408 * [I] lParam : the second message parameter
10410 * RETURN:
10411 * Zero.
10413 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10415 switch (HIWORD(wParam))
10417 case EN_UPDATE:
10420 * Adjust the edit window size
10422 WCHAR buffer[1024];
10423 HDC hdc = GetDC(infoPtr->hwndEdit);
10424 HFONT hFont, hOldFont = 0;
10425 RECT rect;
10426 SIZE sz;
10428 if (!infoPtr->hwndEdit || !hdc) return 0;
10429 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10430 GetWindowRect(infoPtr->hwndEdit, &rect);
10432 /* Select font to get the right dimension of the string */
10433 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10434 if(hFont != 0)
10436 hOldFont = SelectObject(hdc, hFont);
10439 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10441 TEXTMETRICW textMetric;
10443 /* Add Extra spacing for the next character */
10444 GetTextMetricsW(hdc, &textMetric);
10445 sz.cx += (textMetric.tmMaxCharWidth * 2);
10447 SetWindowPos (
10448 infoPtr->hwndEdit,
10449 HWND_TOP,
10452 sz.cx,
10453 rect.bottom - rect.top,
10454 SWP_DRAWFRAME|SWP_NOMOVE);
10456 if(hFont != 0)
10457 SelectObject(hdc, hOldFont);
10459 ReleaseDC(infoPtr->hwndEdit, hdc);
10461 break;
10464 default:
10465 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10468 return 0;
10472 /***
10473 * DESCRIPTION:
10474 * Subclassed edit control windproc function
10476 * PARAMETER(S):
10477 * [I] hwnd : the edit window handle
10478 * [I] uMsg : the message that is to be processed
10479 * [I] wParam : first message parameter
10480 * [I] lParam : second message parameter
10481 * [I] isW : TRUE if input is Unicode
10483 * RETURN:
10484 * Zero.
10486 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10488 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10489 BOOL cancel = FALSE;
10491 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10492 hwnd, uMsg, wParam, lParam, isW);
10494 switch (uMsg)
10496 case WM_GETDLGCODE:
10497 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10499 case WM_KILLFOCUS:
10500 break;
10502 case WM_DESTROY:
10504 WNDPROC editProc = infoPtr->EditWndProc;
10505 infoPtr->EditWndProc = 0;
10506 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10507 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10510 case WM_KEYDOWN:
10511 if (VK_ESCAPE == (INT)wParam)
10513 cancel = TRUE;
10514 break;
10516 else if (VK_RETURN == (INT)wParam)
10517 break;
10519 default:
10520 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10523 /* kill the edit */
10524 if (infoPtr->hwndEdit)
10526 LPWSTR buffer = NULL;
10528 infoPtr->hwndEdit = 0;
10529 if (!cancel)
10531 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10533 if (len)
10535 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10537 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10538 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10542 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10544 Free(buffer);
10547 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10548 return 0;
10551 /***
10552 * DESCRIPTION:
10553 * Subclassed edit control Unicode windproc function
10555 * PARAMETER(S):
10556 * [I] hwnd : the edit window handle
10557 * [I] uMsg : the message that is to be processed
10558 * [I] wParam : first message parameter
10559 * [I] lParam : second message parameter
10561 * RETURN:
10563 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10565 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10568 /***
10569 * DESCRIPTION:
10570 * Subclassed edit control ANSI windproc function
10572 * PARAMETER(S):
10573 * [I] hwnd : the edit window handle
10574 * [I] uMsg : the message that is to be processed
10575 * [I] wParam : first message parameter
10576 * [I] lParam : second message parameter
10578 * RETURN:
10580 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10582 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10585 /***
10586 * DESCRIPTION:
10587 * Creates a subclassed edit control
10589 * PARAMETER(S):
10590 * [I] infoPtr : valid pointer to the listview structure
10591 * [I] text : initial text for the edit
10592 * [I] style : the window style
10593 * [I] isW : TRUE if input is Unicode
10595 * RETURN:
10597 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10598 INT x, INT y, INT width, INT height, BOOL isW)
10600 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10601 HWND hedit;
10602 SIZE sz;
10603 HDC hdc;
10604 HDC hOldFont=0;
10605 TEXTMETRICW textMetric;
10606 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10608 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10610 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10611 hdc = GetDC(infoPtr->hwndSelf);
10613 /* Select the font to get appropriate metric dimensions */
10614 if(infoPtr->hFont != 0)
10615 hOldFont = SelectObject(hdc, infoPtr->hFont);
10617 /*Get String Length in pixels */
10618 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10620 /*Add Extra spacing for the next character */
10621 GetTextMetricsW(hdc, &textMetric);
10622 sz.cx += (textMetric.tmMaxCharWidth * 2);
10624 if(infoPtr->hFont != 0)
10625 SelectObject(hdc, hOldFont);
10627 ReleaseDC(infoPtr->hwndSelf, hdc);
10628 if (isW)
10629 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10630 else
10631 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10633 if (!hedit) return 0;
10635 infoPtr->EditWndProc = (WNDPROC)
10636 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10637 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10639 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10641 return hedit;