push c6aba86b6d7f7dbe994528d67faed47594362b60
[wine/hacks.git] / dlls / comctl32 / listview.c
blobe571f330caba3bf5a658dfb0d3ad0e2e5c4590ab
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
63 * Speedups
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
73 * Flags
74 * -- LVIF_COLUMNS
75 * -- LVIF_GROUPID
76 * -- LVIF_NORECOMPUTE
78 * States
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVIS_CUT
81 * -- LVIS_DROPHILITED
82 * -- LVIS_OVERLAYMASK
84 * Styles
85 * -- LVS_NOLABELWRAP
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
88 * -- LVS_ALIGNTOP
89 * -- LVS_TYPESTYLEMASK
91 * Extended Styles
92 * -- LVS_EX_BORDERSELECT
93 * -- LVS_EX_FLATSB
94 * -- LVS_EX_HEADERDRAGDROP
95 * -- LVS_EX_INFOTIP
96 * -- LVS_EX_LABELTIP
97 * -- LVS_EX_MULTIWORKAREAS
98 * -- LVS_EX_REGIONAL
99 * -- LVS_EX_SIMPLESELECT
100 * -- LVS_EX_TWOCLICKACTIVATE
101 * -- LVS_EX_UNDERLINECOLD
102 * -- LVS_EX_UNDERLINEHOT
104 * Notifications:
105 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
106 * -- LVN_GETINFOTIP
107 * -- LVN_HOTTRACK
108 * -- LVN_MARQUEEBEGIN
109 * -- LVN_SETDISPINFO
110 * -- NM_HOVER
111 * -- LVN_BEGINRDRAG
113 * Messages:
114 * -- LVM_CANCELEDITLABEL
115 * -- LVM_ENABLEGROUPVIEW
116 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
117 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
118 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
119 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
120 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
121 * -- LVM_GETINSERTMARKRECT
122 * -- LVM_GETNUMBEROFWORKAREAS
123 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
124 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
125 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
126 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
127 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
128 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
129 * -- LVM_GETVIEW, LVM_SETVIEW
130 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
131 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
132 * -- LVM_INSERTGROUPSORTED
133 * -- LVM_INSERTMARKHITTEST
134 * -- LVM_ISGROUPVIEWENABLED
135 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
136 * -- LVM_MOVEGROUP
137 * -- LVM_MOVEITEMTOGROUP
138 * -- LVM_SETINFOTIP
139 * -- LVM_SETTILEWIDTH
140 * -- LVM_SORTGROUPS
141 * -- LVM_SORTITEMSEX
143 * Macros:
144 * -- ListView_GetCheckSate, ListView_SetCheckState
145 * -- ListView_GetHoverTime, ListView_SetHoverTime
146 * -- ListView_GetISearchString
147 * -- ListView_GetNumberOfWorkAreas
148 * -- ListView_GetOrigin
149 * -- ListView_GetTextBkColor
150 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
151 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
152 * -- ListView_SortItemsEx
154 * Functions:
155 * -- LVGroupComparE
157 * Known differences in message stream from native control (not known if
158 * these differences cause problems):
159 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
160 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
161 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
162 * processing for "USEDOUBLECLICKTIME".
165 #include "config.h"
166 #include "wine/port.h"
168 #include <assert.h>
169 #include <ctype.h>
170 #include <string.h>
171 #include <stdlib.h>
172 #include <stdarg.h>
173 #include <stdio.h>
175 #include "windef.h"
176 #include "winbase.h"
177 #include "winnt.h"
178 #include "wingdi.h"
179 #include "winuser.h"
180 #include "winnls.h"
181 #include "commctrl.h"
182 #include "comctl32.h"
183 #include "uxtheme.h"
185 #include "wine/debug.h"
186 #include "wine/unicode.h"
188 WINE_DEFAULT_DEBUG_CHANNEL(listview);
190 /* make sure you set this to 0 for production use! */
191 #define DEBUG_RANGES 1
193 typedef struct tagCOLUMN_INFO
195 RECT rcHeader; /* tracks the header's rectangle */
196 int fmt; /* same as LVCOLUMN.fmt */
197 } COLUMN_INFO;
199 typedef struct tagITEMHDR
201 LPWSTR pszText;
202 INT iImage;
203 } ITEMHDR, *LPITEMHDR;
205 typedef struct tagSUBITEM_INFO
207 ITEMHDR hdr;
208 INT iSubItem;
209 } SUBITEM_INFO;
211 typedef struct tagITEM_INFO
213 ITEMHDR hdr;
214 UINT state;
215 LPARAM lParam;
216 INT iIndent;
217 } ITEM_INFO;
219 typedef struct tagRANGE
221 INT lower;
222 INT upper;
223 } RANGE;
225 typedef struct tagRANGES
227 HDPA hdpa;
228 } *RANGES;
230 typedef struct tagITERATOR
232 INT nItem;
233 INT nSpecial;
234 RANGE range;
235 RANGES ranges;
236 INT index;
237 } ITERATOR;
239 typedef struct tagDELAYED_ITEM_EDIT
241 BOOL fEnabled;
242 INT iItem;
243 } DELAYED_ITEM_EDIT;
245 typedef struct tagLISTVIEW_INFO
247 HWND hwndSelf;
248 HBRUSH hBkBrush;
249 COLORREF clrBk;
250 COLORREF clrText;
251 COLORREF clrTextBk;
252 HIMAGELIST himlNormal;
253 HIMAGELIST himlSmall;
254 HIMAGELIST himlState;
255 BOOL bLButtonDown;
256 BOOL bRButtonDown;
257 BOOL bDragging;
258 POINT ptClickPos; /* point where the user clicked */
259 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
260 INT nItemHeight;
261 INT nItemWidth;
262 RANGES selectionRanges;
263 INT nSelectionMark;
264 INT nHotItem;
265 SHORT notifyFormat;
266 HWND hwndNotify;
267 RECT rcList; /* This rectangle is really the window
268 * client rectangle possibly reduced by the
269 * horizontal scroll bar and/or header - see
270 * LISTVIEW_UpdateSize. This rectangle offset
271 * by the LISTVIEW_GetOrigin value is in
272 * client coordinates */
273 SIZE iconSize;
274 SIZE iconSpacing;
275 SIZE iconStateSize;
276 UINT uCallbackMask;
277 HWND hwndHeader;
278 HCURSOR hHotCursor;
279 HFONT hDefaultFont;
280 HFONT hFont;
281 INT ntmHeight; /* Some cached metrics of the font used */
282 INT ntmMaxCharWidth; /* by the listview to draw items */
283 INT nEllipsisWidth;
284 BOOL bRedraw; /* Turns on/off repaints & invalidations */
285 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
286 BOOL bFocus;
287 BOOL bDoChangeNotify; /* send change notification messages? */
288 INT nFocusedItem;
289 RECT rcFocus;
290 DWORD dwStyle; /* the cached window GWL_STYLE */
291 DWORD dwLvExStyle; /* extended listview style */
292 INT nItemCount; /* the number of items in the list */
293 HDPA hdpaItems; /* array ITEM_INFO pointers */
294 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
295 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
296 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
297 POINT currIconPos; /* this is the position next icon will be placed */
298 PFNLVCOMPARE pfnCompare;
299 LPARAM lParamSort;
300 HWND hwndEdit;
301 WNDPROC EditWndProc;
302 INT nEditLabelItem;
303 DWORD dwHoverTime;
304 HWND hwndToolTip;
306 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
308 DWORD lastKeyPressTimestamp;
309 WPARAM charCode;
310 INT nSearchParamLength;
311 WCHAR szSearchParam[ MAX_PATH ];
312 BOOL bIsDrawing;
313 INT nMeasureItemHeight;
314 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
315 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
316 } LISTVIEW_INFO;
319 * constants
321 /* How many we debug buffer to allocate */
322 #define DEBUG_BUFFERS 20
323 /* The size of a single debug bbuffer */
324 #define DEBUG_BUFFER_SIZE 256
326 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
327 #define SB_INTERNAL -1
329 /* maximum size of a label */
330 #define DISP_TEXT_SIZE 512
332 /* padding for items in list and small icon display modes */
333 #define WIDTH_PADDING 12
335 /* padding for items in list, report and small icon display modes */
336 #define HEIGHT_PADDING 1
338 /* offset of items in report display mode */
339 #define REPORT_MARGINX 2
341 /* padding for icon in large icon display mode
342 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
343 * that HITTEST will see.
344 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
345 * ICON_TOP_PADDING - sum of the two above.
346 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
347 * LABEL_HOR_PADDING - between text and sides of box
348 * LABEL_VERT_PADDING - between bottom of text and end of box
350 * ICON_LR_PADDING - additional width above icon size.
351 * ICON_LR_HALF - half of the above value
353 #define ICON_TOP_PADDING_NOTHITABLE 2
354 #define ICON_TOP_PADDING_HITABLE 2
355 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
356 #define ICON_BOTTOM_PADDING 4
357 #define LABEL_HOR_PADDING 5
358 #define LABEL_VERT_PADDING 7
359 #define ICON_LR_PADDING 16
360 #define ICON_LR_HALF (ICON_LR_PADDING/2)
362 /* default label width for items in list and small icon display modes */
363 #define DEFAULT_LABEL_WIDTH 40
365 /* default column width for items in list display mode */
366 #define DEFAULT_COLUMN_WIDTH 128
368 /* Size of "line" scroll for V & H scrolls */
369 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
371 /* Padding between image and label */
372 #define IMAGE_PADDING 2
374 /* Padding behind the label */
375 #define TRAILING_LABEL_PADDING 12
376 #define TRAILING_HEADER_PADDING 11
378 /* Border for the icon caption */
379 #define CAPTION_BORDER 2
381 /* Standard DrawText flags */
382 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
383 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
384 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 /* Image index from state */
387 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
389 /* The time in milliseconds to reset the search in the list */
390 #define KEY_DELAY 450
392 /* Dump the LISTVIEW_INFO structure to the debug channel */
393 #define LISTVIEW_DUMP(iP) do { \
394 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
395 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
396 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
397 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
398 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
399 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
400 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
401 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
402 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
403 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
404 } while(0)
406 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
409 * forward declarations
411 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
412 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
413 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
414 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
415 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
416 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
417 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
418 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
419 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
420 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
421 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
422 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
423 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
424 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
425 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
426 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
427 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
428 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
429 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
430 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
431 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
432 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
433 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
434 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
435 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
436 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
437 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
439 /******** Text handling functions *************************************/
441 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
442 * text string. The string may be ANSI or Unicode, in which case
443 * the boolean isW tells us the type of the string.
445 * The name of the function tell what type of strings it expects:
446 * W: Unicode, T: ANSI/Unicode - function of isW
449 static inline BOOL is_textW(LPCWSTR text)
451 return text != NULL && text != LPSTR_TEXTCALLBACKW;
454 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
456 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
457 return is_textW(text);
460 static inline int textlenT(LPCWSTR text, BOOL isW)
462 return !is_textT(text, isW) ? 0 :
463 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
466 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
468 if (isDestW)
469 if (isSrcW) lstrcpynW(dest, src, max);
470 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
471 else
472 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
473 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
476 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
478 LPWSTR wstr = (LPWSTR)text;
480 if (!isW && is_textT(text, isW))
482 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
483 wstr = Alloc(len * sizeof(WCHAR));
484 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
486 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
487 return wstr;
490 static inline void textfreeT(LPWSTR wstr, BOOL isW)
492 if (!isW && is_textT(wstr, isW)) Free (wstr);
496 * dest is a pointer to a Unicode string
497 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
499 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
501 BOOL bResult = TRUE;
503 if (src == LPSTR_TEXTCALLBACKW)
505 if (is_textW(*dest)) Free(*dest);
506 *dest = LPSTR_TEXTCALLBACKW;
508 else
510 LPWSTR pszText = textdupTtoW(src, isW);
511 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
512 bResult = Str_SetPtrW(dest, pszText);
513 textfreeT(pszText, isW);
515 return bResult;
519 * compares a Unicode to a Unicode/ANSI text string
521 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
523 if (!aw) return bt ? -1 : 0;
524 if (!bt) return aw ? 1 : 0;
525 if (aw == LPSTR_TEXTCALLBACKW)
526 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
527 if (bt != LPSTR_TEXTCALLBACKW)
529 LPWSTR bw = textdupTtoW(bt, isW);
530 int r = bw ? lstrcmpW(aw, bw) : 1;
531 textfreeT(bw, isW);
532 return r;
535 return 1;
538 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
540 int res;
542 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
543 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
544 return res ? res - sizeof(WCHAR) : res;
547 /******** Debugging functions *****************************************/
549 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
551 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
552 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
555 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
557 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
558 n = min(textlenT(text, isW), n);
559 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
562 static char* debug_getbuf(void)
564 static int index = 0;
565 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
566 return buffers[index++ % DEBUG_BUFFERS];
569 static inline const char* debugrange(const RANGE *lprng)
571 if (!lprng) return "(null)";
572 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
575 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
577 char* buf = debug_getbuf(), *text = buf;
578 int len, size = DEBUG_BUFFER_SIZE;
580 if (pScrollInfo == NULL) return "(null)";
581 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
582 if (len == -1) goto end; buf += len; size -= len;
583 if (pScrollInfo->fMask & SIF_RANGE)
584 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
585 else len = 0;
586 if (len == -1) goto end; buf += len; size -= len;
587 if (pScrollInfo->fMask & SIF_PAGE)
588 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
589 else len = 0;
590 if (len == -1) goto end; buf += len; size -= len;
591 if (pScrollInfo->fMask & SIF_POS)
592 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
593 else len = 0;
594 if (len == -1) goto end; buf += len; size -= len;
595 if (pScrollInfo->fMask & SIF_TRACKPOS)
596 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
597 else len = 0;
598 if (len == -1) goto end; buf += len; size -= len;
599 goto undo;
600 end:
601 buf = text + strlen(text);
602 undo:
603 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
604 return text;
607 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
609 if (!plvnm) return "(null)";
610 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
611 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
612 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
613 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
616 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
618 char* buf = debug_getbuf(), *text = buf;
619 int len, size = DEBUG_BUFFER_SIZE;
621 if (lpLVItem == NULL) return "(null)";
622 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
623 if (len == -1) goto end; buf += len; size -= len;
624 if (lpLVItem->mask & LVIF_STATE)
625 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
626 else len = 0;
627 if (len == -1) goto end; buf += len; size -= len;
628 if (lpLVItem->mask & LVIF_TEXT)
629 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
630 else len = 0;
631 if (len == -1) goto end; buf += len; size -= len;
632 if (lpLVItem->mask & LVIF_IMAGE)
633 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
634 else len = 0;
635 if (len == -1) goto end; buf += len; size -= len;
636 if (lpLVItem->mask & LVIF_PARAM)
637 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
638 else len = 0;
639 if (len == -1) goto end; buf += len; size -= len;
640 if (lpLVItem->mask & LVIF_INDENT)
641 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
642 else len = 0;
643 if (len == -1) goto end; buf += len; size -= len;
644 goto undo;
645 end:
646 buf = text + strlen(text);
647 undo:
648 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
649 return text;
652 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
654 char* buf = debug_getbuf(), *text = buf;
655 int len, size = DEBUG_BUFFER_SIZE;
657 if (lpColumn == NULL) return "(null)";
658 len = snprintf(buf, size, "{");
659 if (len == -1) goto end; buf += len; size -= len;
660 if (lpColumn->mask & LVCF_SUBITEM)
661 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
662 else len = 0;
663 if (len == -1) goto end; buf += len; size -= len;
664 if (lpColumn->mask & LVCF_FMT)
665 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
666 else len = 0;
667 if (len == -1) goto end; buf += len; size -= len;
668 if (lpColumn->mask & LVCF_WIDTH)
669 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
670 else len = 0;
671 if (len == -1) goto end; buf += len; size -= len;
672 if (lpColumn->mask & LVCF_TEXT)
673 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
674 else len = 0;
675 if (len == -1) goto end; buf += len; size -= len;
676 if (lpColumn->mask & LVCF_IMAGE)
677 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
678 else len = 0;
679 if (len == -1) goto end; buf += len; size -= len;
680 if (lpColumn->mask & LVCF_ORDER)
681 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
682 else len = 0;
683 if (len == -1) goto end; buf += len; size -= len;
684 goto undo;
685 end:
686 buf = text + strlen(text);
687 undo:
688 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
689 return text;
692 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
694 if (!lpht) return "(null)";
696 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
697 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
700 /* Return the corresponding text for a given scroll value */
701 static inline LPCSTR debugscrollcode(int nScrollCode)
703 switch(nScrollCode)
705 case SB_LINELEFT: return "SB_LINELEFT";
706 case SB_LINERIGHT: return "SB_LINERIGHT";
707 case SB_PAGELEFT: return "SB_PAGELEFT";
708 case SB_PAGERIGHT: return "SB_PAGERIGHT";
709 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
710 case SB_THUMBTRACK: return "SB_THUMBTRACK";
711 case SB_ENDSCROLL: return "SB_ENDSCROLL";
712 case SB_INTERNAL: return "SB_INTERNAL";
713 default: return "unknown";
718 /******** Notification functions i************************************/
720 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
722 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
723 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
726 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
728 LRESULT result;
730 TRACE("(code=%d)\n", code);
732 pnmh->hwndFrom = infoPtr->hwndSelf;
733 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
734 pnmh->code = code;
735 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
737 TRACE(" <= %ld\n", result);
739 return result;
742 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
744 NMHDR nmh;
745 HWND hwnd = infoPtr->hwndSelf;
746 notify_hdr(infoPtr, code, &nmh);
747 return IsWindow(hwnd);
750 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
752 NMITEMACTIVATE nmia;
753 LVITEMW item;
755 if (htInfo) {
756 nmia.uNewState = 0;
757 nmia.uOldState = 0;
758 nmia.uChanged = 0;
759 nmia.uKeyFlags = 0;
761 item.mask = LVIF_PARAM|LVIF_STATE;
762 item.iItem = htInfo->iItem;
763 item.iSubItem = 0;
764 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
765 nmia.lParam = item.lParam;
766 nmia.uOldState = item.state;
767 nmia.uNewState = item.state | LVIS_ACTIVATING;
768 nmia.uChanged = LVIF_STATE;
771 nmia.iItem = htInfo->iItem;
772 nmia.iSubItem = htInfo->iSubItem;
773 nmia.ptAction = htInfo->pt;
775 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
776 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
777 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
779 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
782 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
784 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
785 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
788 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
790 NMLISTVIEW nmlv;
791 LVITEMW item;
792 HWND hwnd = infoPtr->hwndSelf;
794 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
795 ZeroMemory(&nmlv, sizeof(nmlv));
796 nmlv.iItem = lvht->iItem;
797 nmlv.iSubItem = lvht->iSubItem;
798 nmlv.ptAction = lvht->pt;
799 item.mask = LVIF_PARAM;
800 item.iItem = lvht->iItem;
801 item.iSubItem = 0;
802 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
803 notify_listview(infoPtr, code, &nmlv);
804 return IsWindow(hwnd);
807 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
809 NMLISTVIEW nmlv;
810 LVITEMW item;
811 HWND hwnd = infoPtr->hwndSelf;
813 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
814 nmlv.iItem = nItem;
815 item.mask = LVIF_PARAM;
816 item.iItem = nItem;
817 item.iSubItem = 0;
818 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
819 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
820 return IsWindow(hwnd);
823 static int get_ansi_notification(UINT unicodeNotificationCode)
825 switch (unicodeNotificationCode)
827 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
828 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
829 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
830 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
831 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
832 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
834 ERR("unknown notification %x\n", unicodeNotificationCode);
835 assert(FALSE);
836 return 0;
840 Send notification. depends on dispinfoW having same
841 structure as dispinfoA.
842 infoPtr : listview struct
843 notificationCode : *Unicode* notification code
844 pdi : dispinfo structure (can be unicode or ansi)
845 isW : TRUE if dispinfo is Unicode
847 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
849 BOOL bResult = FALSE;
850 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
851 INT cchTempBufMax = 0, savCchTextMax = 0;
852 UINT realNotifCode;
853 LPWSTR pszTempBuf = NULL, savPszText = NULL;
855 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
857 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
858 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
861 if (convertToAnsi || convertToUnicode)
863 if (notificationCode != LVN_GETDISPINFOW)
865 cchTempBufMax = convertToUnicode ?
866 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
867 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
869 else
871 cchTempBufMax = pdi->item.cchTextMax;
872 *pdi->item.pszText = 0; /* make sure we don't process garbage */
875 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
876 if (!pszTempBuf) return FALSE;
878 if (convertToUnicode)
879 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
880 pszTempBuf, cchTempBufMax);
881 else
882 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
883 cchTempBufMax, NULL, NULL);
885 savCchTextMax = pdi->item.cchTextMax;
886 savPszText = pdi->item.pszText;
887 pdi->item.pszText = pszTempBuf;
888 pdi->item.cchTextMax = cchTempBufMax;
891 if (infoPtr->notifyFormat == NFR_ANSI)
892 realNotifCode = get_ansi_notification(notificationCode);
893 else
894 realNotifCode = notificationCode;
895 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
896 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
898 if (convertToUnicode || convertToAnsi)
900 if (convertToUnicode) /* note : pointer can be changed by app ! */
901 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
902 savCchTextMax, NULL, NULL);
903 else
904 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
905 savPszText, savCchTextMax);
906 pdi->item.pszText = savPszText; /* restores our buffer */
907 pdi->item.cchTextMax = savCchTextMax;
908 Free (pszTempBuf);
910 return bResult;
913 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
914 const RECT *rcBounds, const LVITEMW *lplvItem)
916 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
917 lpnmlvcd->nmcd.hdc = hdc;
918 lpnmlvcd->nmcd.rc = *rcBounds;
919 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
920 lpnmlvcd->clrText = infoPtr->clrText;
921 if (!lplvItem) return;
922 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
923 lpnmlvcd->iSubItem = lplvItem->iSubItem;
924 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
925 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
926 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
927 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
930 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
932 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
933 DWORD result;
935 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
936 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
937 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
938 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
939 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
940 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
941 return result;
944 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
946 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
947 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
948 if (lpnmlvcd->clrText == CLR_DEFAULT)
949 lpnmlvcd->clrText = comctl32_color.clrWindowText;
951 /* apparently, for selected items, we have to override the returned values */
952 if (!SubItem)
954 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
956 if (infoPtr->bFocus)
958 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
959 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
961 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
963 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
964 lpnmlvcd->clrText = comctl32_color.clrBtnText;
969 /* Set the text attributes */
970 if (lpnmlvcd->clrTextBk != CLR_NONE)
972 SetBkMode(hdc, OPAQUE);
973 SetBkColor(hdc,lpnmlvcd->clrTextBk);
975 else
976 SetBkMode(hdc, TRANSPARENT);
977 SetTextColor(hdc, lpnmlvcd->clrText);
980 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
982 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
985 /******** Item iterator functions **********************************/
987 static RANGES ranges_create(int count);
988 static void ranges_destroy(RANGES ranges);
989 static BOOL ranges_add(RANGES ranges, RANGE range);
990 static BOOL ranges_del(RANGES ranges, RANGE range);
991 static void ranges_dump(RANGES ranges);
993 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
995 RANGE range = { nItem, nItem + 1 };
997 return ranges_add(ranges, range);
1000 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1002 RANGE range = { nItem, nItem + 1 };
1004 return ranges_del(ranges, range);
1007 /***
1008 * ITERATOR DOCUMENTATION
1010 * The iterator functions allow for easy, and convenient iteration
1011 * over items of interest in the list. Typically, you create a
1012 * iterator, use it, and destroy it, as such:
1013 * ITERATOR i;
1015 * iterator_xxxitems(&i, ...);
1016 * while (iterator_{prev,next}(&i)
1018 * //code which uses i.nItem
1020 * iterator_destroy(&i);
1022 * where xxx is either: framed, or visible.
1023 * Note that it is important that the code destroys the iterator
1024 * after it's done with it, as the creation of the iterator may
1025 * allocate memory, which thus needs to be freed.
1027 * You can iterate both forwards, and backwards through the list,
1028 * by using iterator_next or iterator_prev respectively.
1030 * Lower numbered items are draw on top of higher number items in
1031 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1032 * items may overlap). So, to test items, you should use
1033 * iterator_next
1034 * which lists the items top to bottom (in Z-order).
1035 * For drawing items, you should use
1036 * iterator_prev
1037 * which lists the items bottom to top (in Z-order).
1038 * If you keep iterating over the items after the end-of-items
1039 * marker (-1) is returned, the iterator will start from the
1040 * beginning. Typically, you don't need to test for -1,
1041 * because iterator_{next,prev} will return TRUE if more items
1042 * are to be iterated over, or FALSE otherwise.
1044 * Note: the iterator is defined to be bidirectional. That is,
1045 * any number of prev followed by any number of next, or
1046 * five versa, should leave the iterator at the same item:
1047 * prev * n, next * n = next * n, prev * n
1049 * The iterator has a notion of an out-of-order, special item,
1050 * which sits at the start of the list. This is used in
1051 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1052 * which needs to be first, as it may overlap other items.
1054 * The code is a bit messy because we have:
1055 * - a special item to deal with
1056 * - simple range, or composite range
1057 * - empty range.
1058 * If you find bugs, or want to add features, please make sure you
1059 * always check/modify *both* iterator_prev, and iterator_next.
1062 /****
1063 * This function iterates through the items in increasing order,
1064 * but prefixed by the special item, then -1. That is:
1065 * special, 1, 2, 3, ..., n, -1.
1066 * Each item is listed only once.
1068 static inline BOOL iterator_next(ITERATOR* i)
1070 if (i->nItem == -1)
1072 i->nItem = i->nSpecial;
1073 if (i->nItem != -1) return TRUE;
1075 if (i->nItem == i->nSpecial)
1077 if (i->ranges) i->index = 0;
1078 goto pickarange;
1081 i->nItem++;
1082 testitem:
1083 if (i->nItem == i->nSpecial) i->nItem++;
1084 if (i->nItem < i->range.upper) return TRUE;
1086 pickarange:
1087 if (i->ranges)
1089 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1090 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1091 else goto end;
1093 else if (i->nItem >= i->range.upper) goto end;
1095 i->nItem = i->range.lower;
1096 if (i->nItem >= 0) goto testitem;
1097 end:
1098 i->nItem = -1;
1099 return FALSE;
1102 /****
1103 * This function iterates through the items in decreasing order,
1104 * followed by the special item, then -1. That is:
1105 * n, n-1, ..., 3, 2, 1, special, -1.
1106 * Each item is listed only once.
1108 static inline BOOL iterator_prev(ITERATOR* i)
1110 BOOL start = FALSE;
1112 if (i->nItem == -1)
1114 start = TRUE;
1115 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1116 goto pickarange;
1118 if (i->nItem == i->nSpecial)
1120 i->nItem = -1;
1121 return FALSE;
1124 testitem:
1125 i->nItem--;
1126 if (i->nItem == i->nSpecial) i->nItem--;
1127 if (i->nItem >= i->range.lower) return TRUE;
1129 pickarange:
1130 if (i->ranges)
1132 if (i->index > 0)
1133 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1134 else goto end;
1136 else if (!start && i->nItem < i->range.lower) goto end;
1138 i->nItem = i->range.upper;
1139 if (i->nItem > 0) goto testitem;
1140 end:
1141 return (i->nItem = i->nSpecial) != -1;
1144 static RANGE iterator_range(const ITERATOR *i)
1146 RANGE range;
1148 if (!i->ranges) return i->range;
1150 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1152 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1153 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1155 else range.lower = range.upper = 0;
1157 return range;
1160 /***
1161 * Releases resources associated with this ierator.
1163 static inline void iterator_destroy(const ITERATOR *i)
1165 ranges_destroy(i->ranges);
1168 /***
1169 * Create an empty iterator.
1171 static inline BOOL iterator_empty(ITERATOR* i)
1173 ZeroMemory(i, sizeof(*i));
1174 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1175 return TRUE;
1178 /***
1179 * Create an iterator over a range.
1181 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1183 iterator_empty(i);
1184 i->range = range;
1185 return TRUE;
1188 /***
1189 * Create an iterator over a bunch of ranges.
1190 * Please note that the iterator will take ownership of the ranges,
1191 * and will free them upon destruction.
1193 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1195 iterator_empty(i);
1196 i->ranges = ranges;
1197 return TRUE;
1200 /***
1201 * Creates an iterator over the items which intersect lprc.
1203 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1205 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1206 RECT frame = *lprc, rcItem, rcTemp;
1207 POINT Origin;
1209 /* in case we fail, we want to return an empty iterator */
1210 if (!iterator_empty(i)) return FALSE;
1212 LISTVIEW_GetOrigin(infoPtr, &Origin);
1214 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1215 OffsetRect(&frame, -Origin.x, -Origin.y);
1217 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1219 INT nItem;
1221 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1223 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1224 if (IntersectRect(&rcTemp, &rcItem, lprc))
1225 i->nSpecial = infoPtr->nFocusedItem;
1227 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1228 /* to do better here, we need to have PosX, and PosY sorted */
1229 TRACE("building icon ranges:\n");
1230 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1232 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1233 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1234 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1235 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1236 if (IntersectRect(&rcTemp, &rcItem, &frame))
1237 ranges_additem(i->ranges, nItem);
1239 return TRUE;
1241 else if (uView == LVS_REPORT)
1243 RANGE range;
1245 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1246 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1248 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1249 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1250 if (range.upper <= range.lower) return TRUE;
1251 if (!iterator_rangeitems(i, range)) return FALSE;
1252 TRACE(" report=%s\n", debugrange(&i->range));
1254 else
1256 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1257 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1258 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1259 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1260 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1261 INT lower = nFirstCol * nPerCol + nFirstRow;
1262 RANGE item_range;
1263 INT nCol;
1265 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1266 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1268 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1270 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1271 TRACE("building list ranges:\n");
1272 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1274 item_range.lower = nCol * nPerCol + nFirstRow;
1275 if(item_range.lower >= infoPtr->nItemCount) break;
1276 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1277 TRACE(" list=%s\n", debugrange(&item_range));
1278 ranges_add(i->ranges, item_range);
1282 return TRUE;
1285 /***
1286 * Creates an iterator over the items which intersect the visible region of hdc.
1288 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1290 POINT Origin, Position;
1291 RECT rcItem, rcClip;
1292 INT rgntype;
1294 rgntype = GetClipBox(hdc, &rcClip);
1295 if (rgntype == NULLREGION) return iterator_empty(i);
1296 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1297 if (rgntype == SIMPLEREGION) return TRUE;
1299 /* first deal with the special item */
1300 if (i->nSpecial != -1)
1302 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1303 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1306 /* if we can't deal with the region, we'll just go with the simple range */
1307 LISTVIEW_GetOrigin(infoPtr, &Origin);
1308 TRACE("building visible range:\n");
1309 if (!i->ranges && i->range.lower < i->range.upper)
1311 if (!(i->ranges = ranges_create(50))) return TRUE;
1312 if (!ranges_add(i->ranges, i->range))
1314 ranges_destroy(i->ranges);
1315 i->ranges = 0;
1316 return TRUE;
1320 /* now delete the invisible items from the list */
1321 while(iterator_next(i))
1323 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1324 rcItem.left = Position.x + Origin.x;
1325 rcItem.top = Position.y + Origin.y;
1326 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1327 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1328 if (!RectVisible(hdc, &rcItem))
1329 ranges_delitem(i->ranges, i->nItem);
1331 /* the iterator should restart on the next iterator_next */
1332 TRACE("done\n");
1334 return TRUE;
1337 /******** Misc helper functions ************************************/
1339 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1340 WPARAM wParam, LPARAM lParam, BOOL isW)
1342 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1343 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1346 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1348 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1350 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1351 (uView == LVS_ICON || uView == LVS_SMALLICON);
1354 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1356 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1357 if(state == 1 || state == 2)
1359 LVITEMW lvitem;
1360 state ^= 3;
1361 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1362 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1363 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1367 /******** Internal API functions ************************************/
1369 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1371 static COLUMN_INFO mainItem;
1373 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1374 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1375 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1378 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1380 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1381 HINSTANCE hInst;
1383 if (infoPtr->hwndHeader) return 0;
1385 /* setup creation flags */
1386 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1387 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1389 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1391 /* create header */
1392 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1393 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1394 if (!infoPtr->hwndHeader) return -1;
1396 /* set header unicode format */
1397 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1399 /* set header font */
1400 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1402 return 0;
1405 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1407 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1410 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1412 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1415 /* Listview invalidation functions: use _only_ these functions to invalidate */
1417 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1419 return infoPtr->bRedraw;
1422 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1424 if(!is_redrawing(infoPtr)) return;
1425 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1426 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1429 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1431 RECT rcBox;
1433 if(!is_redrawing(infoPtr)) return;
1434 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1435 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1438 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1440 POINT Origin, Position;
1441 RECT rcBox;
1443 if(!is_redrawing(infoPtr)) return;
1444 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1445 LISTVIEW_GetOrigin(infoPtr, &Origin);
1446 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1447 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1448 rcBox.top = 0;
1449 rcBox.bottom = infoPtr->nItemHeight;
1450 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1451 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1454 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1456 LISTVIEW_InvalidateRect(infoPtr, NULL);
1459 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1461 RECT rcCol;
1463 if(!is_redrawing(infoPtr)) return;
1464 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1465 rcCol.top = infoPtr->rcList.top;
1466 rcCol.bottom = infoPtr->rcList.bottom;
1467 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1470 /***
1471 * DESCRIPTION:
1472 * Retrieves the number of items that can fit vertically in the client area.
1474 * PARAMETER(S):
1475 * [I] infoPtr : valid pointer to the listview structure
1477 * RETURN:
1478 * Number of items per row.
1480 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1482 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1484 return max(nListWidth/infoPtr->nItemWidth, 1);
1487 /***
1488 * DESCRIPTION:
1489 * Retrieves the number of items that can fit horizontally in the client
1490 * area.
1492 * PARAMETER(S):
1493 * [I] infoPtr : valid pointer to the listview structure
1495 * RETURN:
1496 * Number of items per column.
1498 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1500 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1502 return max(nListHeight / infoPtr->nItemHeight, 1);
1506 /*************************************************************************
1507 * LISTVIEW_ProcessLetterKeys
1509 * Processes keyboard messages generated by pressing the letter keys
1510 * on the keyboard.
1511 * What this does is perform a case insensitive search from the
1512 * current position with the following quirks:
1513 * - If two chars or more are pressed in quick succession we search
1514 * for the corresponding string (e.g. 'abc').
1515 * - If there is a delay we wipe away the current search string and
1516 * restart with just that char.
1517 * - If the user keeps pressing the same character, whether slowly or
1518 * fast, so that the search string is entirely composed of this
1519 * character ('aaaaa' for instance), then we search for first item
1520 * that starting with that character.
1521 * - If the user types the above character in quick succession, then
1522 * we must also search for the corresponding string ('aaaaa'), and
1523 * go to that string if there is a match.
1525 * PARAMETERS
1526 * [I] hwnd : handle to the window
1527 * [I] charCode : the character code, the actual character
1528 * [I] keyData : key data
1530 * RETURNS
1532 * Zero.
1534 * BUGS
1536 * - The current implementation has a list of characters it will
1537 * accept and it ignores everything else. In particular it will
1538 * ignore accentuated characters which seems to match what
1539 * Windows does. But I'm not sure it makes sense to follow
1540 * Windows there.
1541 * - We don't sound a beep when the search fails.
1543 * SEE ALSO
1545 * TREEVIEW_ProcessLetterKeys
1547 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1549 INT nItem;
1550 INT endidx,idx;
1551 LVITEMW item;
1552 WCHAR buffer[MAX_PATH];
1553 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1555 /* simple parameter checking */
1556 if (!charCode || !keyData) return 0;
1558 /* only allow the valid WM_CHARs through */
1559 if (!isalnumW(charCode) &&
1560 charCode != '.' && charCode != '`' && charCode != '!' &&
1561 charCode != '@' && charCode != '#' && charCode != '$' &&
1562 charCode != '%' && charCode != '^' && charCode != '&' &&
1563 charCode != '*' && charCode != '(' && charCode != ')' &&
1564 charCode != '-' && charCode != '_' && charCode != '+' &&
1565 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1566 charCode != '}' && charCode != '[' && charCode != '{' &&
1567 charCode != '/' && charCode != '?' && charCode != '>' &&
1568 charCode != '<' && charCode != ',' && charCode != '~')
1569 return 0;
1571 /* if there's one item or less, there is no where to go */
1572 if (infoPtr->nItemCount <= 1) return 0;
1574 /* update the search parameters */
1575 infoPtr->lastKeyPressTimestamp = GetTickCount();
1576 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1577 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1578 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1579 if (infoPtr->charCode != charCode)
1580 infoPtr->charCode = charCode = 0;
1581 } else {
1582 infoPtr->charCode=charCode;
1583 infoPtr->szSearchParam[0]=charCode;
1584 infoPtr->nSearchParamLength=1;
1585 /* Redundant with the 1 char string */
1586 charCode=0;
1589 /* and search from the current position */
1590 nItem=-1;
1591 if (infoPtr->nFocusedItem >= 0) {
1592 endidx=infoPtr->nFocusedItem;
1593 idx=endidx;
1594 /* if looking for single character match,
1595 * then we must always move forward
1597 if (infoPtr->nSearchParamLength == 1)
1598 idx++;
1599 } else {
1600 endidx=infoPtr->nItemCount;
1601 idx=0;
1604 /* Let application handle this for virtual listview */
1605 if (infoPtr->dwStyle & LVS_OWNERDATA)
1607 NMLVFINDITEMW nmlv;
1608 LVFINDINFOW lvfi;
1610 ZeroMemory(&lvfi, sizeof(lvfi));
1611 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1612 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1613 lvfi.psz = infoPtr->szSearchParam;
1614 nmlv.iStart = idx;
1615 nmlv.lvfi = lvfi;
1617 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1619 if (nItem != -1)
1620 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1622 return 0;
1625 do {
1626 if (idx == infoPtr->nItemCount) {
1627 if (endidx == infoPtr->nItemCount || endidx == 0)
1628 break;
1629 idx=0;
1632 /* get item */
1633 item.mask = LVIF_TEXT;
1634 item.iItem = idx;
1635 item.iSubItem = 0;
1636 item.pszText = buffer;
1637 item.cchTextMax = MAX_PATH;
1638 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1640 /* check for a match */
1641 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1642 nItem=idx;
1643 break;
1644 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1645 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1646 /* This would work but we must keep looking for a longer match */
1647 nItem=idx;
1649 idx++;
1650 } while (idx != endidx);
1652 if (nItem != -1)
1653 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1655 return 0;
1658 /*************************************************************************
1659 * LISTVIEW_UpdateHeaderSize [Internal]
1661 * Function to resize the header control
1663 * PARAMS
1664 * [I] hwnd : handle to a window
1665 * [I] nNewScrollPos : scroll pos to set
1667 * RETURNS
1668 * None.
1670 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1672 RECT winRect;
1673 POINT point[2];
1675 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1677 GetWindowRect(infoPtr->hwndHeader, &winRect);
1678 point[0].x = winRect.left;
1679 point[0].y = winRect.top;
1680 point[1].x = winRect.right;
1681 point[1].y = winRect.bottom;
1683 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1684 point[0].x = -nNewScrollPos;
1685 point[1].x += nNewScrollPos;
1687 SetWindowPos(infoPtr->hwndHeader,0,
1688 point[0].x,point[0].y,point[1].x,point[1].y,
1689 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1690 SWP_NOZORDER | SWP_NOACTIVATE);
1693 /***
1694 * DESCRIPTION:
1695 * Update the scrollbars. This functions should be called whenever
1696 * the content, size or view changes.
1698 * PARAMETER(S):
1699 * [I] infoPtr : valid pointer to the listview structure
1701 * RETURN:
1702 * None
1704 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1706 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1707 SCROLLINFO horzInfo, vertInfo;
1708 INT dx, dy;
1710 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1712 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1713 horzInfo.cbSize = sizeof(SCROLLINFO);
1714 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1716 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1717 if (uView == LVS_LIST)
1719 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1720 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1722 /* scroll by at least one column per page */
1723 if(horzInfo.nPage < infoPtr->nItemWidth)
1724 horzInfo.nPage = infoPtr->nItemWidth;
1726 horzInfo.nPage /= infoPtr->nItemWidth;
1728 else if (uView == LVS_REPORT)
1730 horzInfo.nMax = infoPtr->nItemWidth;
1732 else /* LVS_ICON, or LVS_SMALLICON */
1734 RECT rcView;
1736 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1739 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1740 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1741 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1742 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1743 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1745 /* Setting the horizontal scroll can change the listview size
1746 * (and potentially everything else) so we need to recompute
1747 * everything again for the vertical scroll
1750 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1751 vertInfo.cbSize = sizeof(SCROLLINFO);
1752 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1754 if (uView == LVS_REPORT)
1756 vertInfo.nMax = infoPtr->nItemCount;
1758 /* scroll by at least one page */
1759 if(vertInfo.nPage < infoPtr->nItemHeight)
1760 vertInfo.nPage = infoPtr->nItemHeight;
1762 if (infoPtr->nItemHeight > 0)
1763 vertInfo.nPage /= infoPtr->nItemHeight;
1765 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1767 RECT rcView;
1769 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1772 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1773 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1774 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1775 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1776 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1778 /* Change of the range may have changed the scroll pos. If so move the content */
1779 if (dx != 0 || dy != 0)
1781 RECT listRect;
1782 listRect = infoPtr->rcList;
1783 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1784 SW_ERASE | SW_INVALIDATE);
1787 /* Update the Header Control */
1788 if (uView == LVS_REPORT)
1790 horzInfo.fMask = SIF_POS;
1791 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1792 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1797 /***
1798 * DESCRIPTION:
1799 * Shows/hides the focus rectangle.
1801 * PARAMETER(S):
1802 * [I] infoPtr : valid pointer to the listview structure
1803 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1805 * RETURN:
1806 * None
1808 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1810 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1811 HDC hdc;
1813 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1815 if (infoPtr->nFocusedItem < 0) return;
1817 /* we need some gymnastics in ICON mode to handle large items */
1818 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1820 RECT rcBox;
1822 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1823 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1825 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1826 return;
1830 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1832 /* for some reason, owner draw should work only in report mode */
1833 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1835 DRAWITEMSTRUCT dis;
1836 LVITEMW item;
1838 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1839 HFONT hOldFont = SelectObject(hdc, hFont);
1841 item.iItem = infoPtr->nFocusedItem;
1842 item.iSubItem = 0;
1843 item.mask = LVIF_PARAM;
1844 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1846 ZeroMemory(&dis, sizeof(dis));
1847 dis.CtlType = ODT_LISTVIEW;
1848 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1849 dis.itemID = item.iItem;
1850 dis.itemAction = ODA_FOCUS;
1851 if (fShow) dis.itemState |= ODS_FOCUS;
1852 dis.hwndItem = infoPtr->hwndSelf;
1853 dis.hDC = hdc;
1854 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1855 dis.itemData = item.lParam;
1857 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1859 SelectObject(hdc, hOldFont);
1861 else
1863 DrawFocusRect(hdc, &infoPtr->rcFocus);
1865 done:
1866 ReleaseDC(infoPtr->hwndSelf, hdc);
1869 /***
1870 * Invalidates all visible selected items.
1872 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1874 ITERATOR i;
1876 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1877 while(iterator_next(&i))
1879 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1880 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1882 iterator_destroy(&i);
1886 /***
1887 * DESCRIPTION: [INTERNAL]
1888 * Computes an item's (left,top) corner, relative to rcView.
1889 * That is, the position has NOT been made relative to the Origin.
1890 * This is deliberate, to avoid computing the Origin over, and
1891 * over again, when this function is called in a loop. Instead,
1892 * one can factor the computation of the Origin before the loop,
1893 * and offset the value returned by this function, on every iteration.
1895 * PARAMETER(S):
1896 * [I] infoPtr : valid pointer to the listview structure
1897 * [I] nItem : item number
1898 * [O] lpptOrig : item top, left corner
1900 * RETURN:
1901 * None.
1903 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1905 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1907 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1909 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1911 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1912 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1914 else if (uView == LVS_LIST)
1916 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1917 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1918 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1920 else /* LVS_REPORT */
1922 lpptPosition->x = 0;
1923 lpptPosition->y = nItem * infoPtr->nItemHeight;
1927 /***
1928 * DESCRIPTION: [INTERNAL]
1929 * Compute the rectangles of an item. This is to localize all
1930 * the computations in one place. If you are not interested in some
1931 * of these values, simply pass in a NULL -- the function is smart
1932 * enough to compute only what's necessary. The function computes
1933 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1934 * one, the BOX rectangle. This rectangle is very cheap to compute,
1935 * and is guaranteed to contain all the other rectangles. Computing
1936 * the ICON rect is also cheap, but all the others are potentially
1937 * expensive. This gives an easy and effective optimization when
1938 * searching (like point inclusion, or rectangle intersection):
1939 * first test against the BOX, and if TRUE, test against the desired
1940 * rectangle.
1941 * If the function does not have all the necessary information
1942 * to computed the requested rectangles, will crash with a
1943 * failed assertion. This is done so we catch all programming
1944 * errors, given that the function is called only from our code.
1946 * We have the following 'special' meanings for a few fields:
1947 * * If LVIS_FOCUSED is set, we assume the item has the focus
1948 * This is important in ICON mode, where it might get a larger
1949 * then usual rectangle
1951 * Please note that subitem support works only in REPORT mode.
1953 * PARAMETER(S):
1954 * [I] infoPtr : valid pointer to the listview structure
1955 * [I] lpLVItem : item to compute the measures for
1956 * [O] lprcBox : ptr to Box rectangle
1957 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1958 * [0] lprcSelectBox : ptr to select box rectangle
1959 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1960 * [O] lprcIcon : ptr to Icon rectangle
1961 * Same as LVM_GETITEMRECT with LVIR_ICON
1962 * [O] lprcStateIcon: ptr to State Icon rectangle
1963 * [O] lprcLabel : ptr to Label rectangle
1964 * Same as LVM_GETITEMRECT with LVIR_LABEL
1966 * RETURN:
1967 * None.
1969 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1970 LPRECT lprcBox, LPRECT lprcSelectBox,
1971 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1973 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1974 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1975 RECT Box, SelectBox, Icon, Label;
1976 COLUMN_INFO *lpColumnInfo = NULL;
1977 SIZE labelSize = { 0, 0 };
1979 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1981 /* Be smart and try to figure out the minimum we have to do */
1982 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1983 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1985 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1986 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1988 if (lprcSelectBox) doSelectBox = TRUE;
1989 if (lprcLabel) doLabel = TRUE;
1990 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1991 if (doSelectBox)
1993 doIcon = TRUE;
1994 doLabel = TRUE;
1997 /************************************************************/
1998 /* compute the box rectangle (it should be cheap to do) */
1999 /************************************************************/
2000 if (lpLVItem->iSubItem || uView == LVS_REPORT)
2001 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2003 if (lpLVItem->iSubItem)
2005 Box = lpColumnInfo->rcHeader;
2007 else
2009 Box.left = 0;
2010 Box.right = infoPtr->nItemWidth;
2012 Box.top = 0;
2013 Box.bottom = infoPtr->nItemHeight;
2015 /******************************************************************/
2016 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2017 /******************************************************************/
2018 if (doIcon)
2020 LONG state_width = 0;
2022 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2023 state_width = infoPtr->iconStateSize.cx;
2025 if (uView == LVS_ICON)
2027 Icon.left = Box.left + state_width;
2028 if (infoPtr->himlNormal)
2029 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2030 Icon.top = Box.top + ICON_TOP_PADDING;
2031 Icon.right = Icon.left;
2032 Icon.bottom = Icon.top;
2033 if (infoPtr->himlNormal)
2035 Icon.right += infoPtr->iconSize.cx;
2036 Icon.bottom += infoPtr->iconSize.cy;
2039 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2041 Icon.left = Box.left + state_width;
2043 if (uView == LVS_REPORT)
2044 Icon.left += REPORT_MARGINX;
2046 Icon.top = Box.top;
2047 Icon.right = Icon.left;
2048 if (infoPtr->himlSmall &&
2049 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2050 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2051 Icon.right += infoPtr->iconSize.cx;
2052 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2054 if(lprcIcon) *lprcIcon = Icon;
2055 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2057 /* TODO: is this correct? */
2058 if (lprcStateIcon)
2060 lprcStateIcon->left = Icon.left - state_width;
2061 lprcStateIcon->right = Icon.left;
2062 lprcStateIcon->top = Icon.top;
2063 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2064 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2067 else Icon.right = 0;
2069 /************************************************************/
2070 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2071 /************************************************************/
2072 if (doLabel)
2074 /* calculate how far to the right can the label stretch */
2075 Label.right = Box.right;
2076 if (uView == LVS_REPORT)
2078 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2081 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2083 labelSize.cx = infoPtr->nItemWidth;
2084 labelSize.cy = infoPtr->nItemHeight;
2085 goto calc_label;
2088 /* we need the text in non owner draw mode */
2089 assert(lpLVItem->mask & LVIF_TEXT);
2090 if (is_textT(lpLVItem->pszText, TRUE))
2092 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2093 HDC hdc = GetDC(infoPtr->hwndSelf);
2094 HFONT hOldFont = SelectObject(hdc, hFont);
2095 UINT uFormat;
2096 RECT rcText;
2098 /* compute rough rectangle where the label will go */
2099 SetRectEmpty(&rcText);
2100 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2101 rcText.bottom = infoPtr->nItemHeight;
2102 if (uView == LVS_ICON)
2103 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2105 /* now figure out the flags */
2106 if (uView == LVS_ICON)
2107 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2108 else
2109 uFormat = LV_SL_DT_FLAGS;
2111 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2113 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2114 labelSize.cy = rcText.bottom - rcText.top;
2116 SelectObject(hdc, hOldFont);
2117 ReleaseDC(infoPtr->hwndSelf, hdc);
2120 calc_label:
2121 if (uView == LVS_ICON)
2123 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2124 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2125 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2126 Label.right = Label.left + labelSize.cx;
2127 Label.bottom = Label.top + infoPtr->nItemHeight;
2128 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2130 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2131 labelSize.cy /= infoPtr->ntmHeight;
2132 labelSize.cy = max(labelSize.cy, 1);
2133 labelSize.cy *= infoPtr->ntmHeight;
2135 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2137 else if (uView == LVS_REPORT)
2139 Label.left = Icon.right;
2140 Label.top = Box.top;
2141 Label.right = lpColumnInfo->rcHeader.right;
2142 Label.bottom = Label.top + infoPtr->nItemHeight;
2144 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2146 Label.left = Icon.right;
2147 Label.top = Box.top;
2148 Label.right = min(Label.left + labelSize.cx, Label.right);
2149 Label.bottom = Label.top + infoPtr->nItemHeight;
2152 if (lprcLabel) *lprcLabel = Label;
2153 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2156 /************************************************************/
2157 /* compute STATEICON bounding box */
2158 /************************************************************/
2159 if (doSelectBox)
2161 if (uView == LVS_REPORT)
2163 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2164 SelectBox.top = Box.top;
2165 SelectBox.bottom = Box.bottom;
2166 if (lpLVItem->iSubItem == 0)
2168 /* we need the indent in report mode */
2169 assert(lpLVItem->mask & LVIF_INDENT);
2170 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2172 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2174 else
2176 UnionRect(&SelectBox, &Icon, &Label);
2178 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2179 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2182 /* Fix the Box if necessary */
2183 if (lprcBox)
2185 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2186 else *lprcBox = Box;
2188 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2191 /***
2192 * DESCRIPTION: [INTERNAL]
2194 * PARAMETER(S):
2195 * [I] infoPtr : valid pointer to the listview structure
2196 * [I] nItem : item number
2197 * [O] lprcBox : ptr to Box rectangle
2199 * RETURN:
2200 * None.
2202 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2204 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2205 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2206 POINT Position, Origin;
2207 LVITEMW lvItem;
2209 LISTVIEW_GetOrigin(infoPtr, &Origin);
2210 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2212 /* Be smart and try to figure out the minimum we have to do */
2213 lvItem.mask = 0;
2214 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2215 lvItem.mask |= LVIF_TEXT;
2216 lvItem.iItem = nItem;
2217 lvItem.iSubItem = 0;
2218 lvItem.pszText = szDispText;
2219 lvItem.cchTextMax = DISP_TEXT_SIZE;
2220 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2221 if (uView == LVS_ICON)
2223 lvItem.mask |= LVIF_STATE;
2224 lvItem.stateMask = LVIS_FOCUSED;
2225 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2227 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2229 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2233 /***
2234 * DESCRIPTION:
2235 * Returns the current icon position, and advances it along the top.
2236 * The returned position is not offset by Origin.
2238 * PARAMETER(S):
2239 * [I] infoPtr : valid pointer to the listview structure
2240 * [O] lpPos : will get the current icon position
2242 * RETURN:
2243 * None
2245 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2247 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2249 *lpPos = infoPtr->currIconPos;
2251 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2252 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2254 infoPtr->currIconPos.x = 0;
2255 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2259 /***
2260 * DESCRIPTION:
2261 * Returns the current icon position, and advances it down the left edge.
2262 * The returned position is not offset by Origin.
2264 * PARAMETER(S):
2265 * [I] infoPtr : valid pointer to the listview structure
2266 * [O] lpPos : will get the current icon position
2268 * RETURN:
2269 * None
2271 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2273 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2275 *lpPos = infoPtr->currIconPos;
2277 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2278 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2280 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2281 infoPtr->currIconPos.y = 0;
2285 /***
2286 * DESCRIPTION:
2287 * Moves an icon to the specified position.
2288 * It takes care of invalidating the item, etc.
2290 * PARAMETER(S):
2291 * [I] infoPtr : valid pointer to the listview structure
2292 * [I] nItem : the item to move
2293 * [I] lpPos : the new icon position
2294 * [I] isNew : flags the item as being new
2296 * RETURN:
2297 * Success: TRUE
2298 * Failure: FALSE
2300 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2302 POINT old;
2304 if (!isNew)
2306 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2307 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2309 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2310 LISTVIEW_InvalidateItem(infoPtr, nItem);
2313 /* Allocating a POINTER for every item is too resource intensive,
2314 * so we'll keep the (x,y) in different arrays */
2315 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2316 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2318 LISTVIEW_InvalidateItem(infoPtr, nItem);
2320 return TRUE;
2323 /***
2324 * DESCRIPTION:
2325 * Arranges listview items in icon display mode.
2327 * PARAMETER(S):
2328 * [I] infoPtr : valid pointer to the listview structure
2329 * [I] nAlignCode : alignment code
2331 * RETURN:
2332 * SUCCESS : TRUE
2333 * FAILURE : FALSE
2335 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2337 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2338 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2339 POINT pos;
2340 INT i;
2342 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2344 TRACE("nAlignCode=%d\n", nAlignCode);
2346 if (nAlignCode == LVA_DEFAULT)
2348 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2349 else nAlignCode = LVA_ALIGNTOP;
2352 switch (nAlignCode)
2354 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2355 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2356 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2357 default: return FALSE;
2360 infoPtr->bAutoarrange = TRUE;
2361 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2362 for (i = 0; i < infoPtr->nItemCount; i++)
2364 next_pos(infoPtr, &pos);
2365 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2368 return TRUE;
2371 /***
2372 * DESCRIPTION:
2373 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2375 * PARAMETER(S):
2376 * [I] infoPtr : valid pointer to the listview structure
2377 * [O] lprcView : bounding rectangle
2379 * RETURN:
2380 * SUCCESS : TRUE
2381 * FAILURE : FALSE
2383 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2385 INT i, x, y;
2387 SetRectEmpty(lprcView);
2389 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2391 case LVS_ICON:
2392 case LVS_SMALLICON:
2393 for (i = 0; i < infoPtr->nItemCount; i++)
2395 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2396 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2397 lprcView->right = max(lprcView->right, x);
2398 lprcView->bottom = max(lprcView->bottom, y);
2400 if (infoPtr->nItemCount > 0)
2402 lprcView->right += infoPtr->nItemWidth;
2403 lprcView->bottom += infoPtr->nItemHeight;
2405 break;
2407 case LVS_LIST:
2408 y = LISTVIEW_GetCountPerColumn(infoPtr);
2409 x = infoPtr->nItemCount / y;
2410 if (infoPtr->nItemCount % y) x++;
2411 lprcView->right = x * infoPtr->nItemWidth;
2412 lprcView->bottom = y * infoPtr->nItemHeight;
2413 break;
2415 case LVS_REPORT:
2416 lprcView->right = infoPtr->nItemWidth;
2417 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2418 break;
2422 /***
2423 * DESCRIPTION:
2424 * Retrieves the bounding rectangle of all the items.
2426 * PARAMETER(S):
2427 * [I] infoPtr : valid pointer to the listview structure
2428 * [O] lprcView : bounding rectangle
2430 * RETURN:
2431 * SUCCESS : TRUE
2432 * FAILURE : FALSE
2434 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2436 POINT ptOrigin;
2438 TRACE("(lprcView=%p)\n", lprcView);
2440 if (!lprcView) return FALSE;
2442 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2443 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2444 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2446 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2448 return TRUE;
2451 /***
2452 * DESCRIPTION:
2453 * Retrieves the subitem pointer associated with the subitem index.
2455 * PARAMETER(S):
2456 * [I] hdpaSubItems : DPA handle for a specific item
2457 * [I] nSubItem : index of subitem
2459 * RETURN:
2460 * SUCCESS : subitem pointer
2461 * FAILURE : NULL
2463 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2465 SUBITEM_INFO *lpSubItem;
2466 INT i;
2468 /* we should binary search here if need be */
2469 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2471 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2472 if (lpSubItem->iSubItem == nSubItem)
2473 return lpSubItem;
2476 return NULL;
2480 /***
2481 * DESCRIPTION:
2482 * Calculates the desired item width.
2484 * PARAMETER(S):
2485 * [I] infoPtr : valid pointer to the listview structure
2487 * RETURN:
2488 * The desired item width.
2490 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2492 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2493 INT nItemWidth = 0;
2495 TRACE("uView=%d\n", uView);
2497 if (uView == LVS_ICON)
2498 nItemWidth = infoPtr->iconSpacing.cx;
2499 else if (uView == LVS_REPORT)
2501 RECT rcHeader;
2503 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2505 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2506 nItemWidth = rcHeader.right;
2509 else /* LVS_SMALLICON, or LVS_LIST */
2511 INT i;
2513 for (i = 0; i < infoPtr->nItemCount; i++)
2514 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2516 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2517 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2519 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2522 return max(nItemWidth, 1);
2525 /***
2526 * DESCRIPTION:
2527 * Calculates the desired item height.
2529 * PARAMETER(S):
2530 * [I] infoPtr : valid pointer to the listview structure
2532 * RETURN:
2533 * The desired item height.
2535 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2537 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2538 INT nItemHeight;
2540 TRACE("uView=%d\n", uView);
2542 if (uView == LVS_ICON)
2543 nItemHeight = infoPtr->iconSpacing.cy;
2544 else
2546 nItemHeight = infoPtr->ntmHeight;
2547 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2548 nItemHeight++;
2549 if (infoPtr->himlState)
2550 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2551 if (infoPtr->himlSmall)
2552 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2553 if (infoPtr->himlState || infoPtr->himlSmall)
2554 nItemHeight += HEIGHT_PADDING;
2555 if (infoPtr->nMeasureItemHeight > 0)
2556 nItemHeight = infoPtr->nMeasureItemHeight;
2559 return max(nItemHeight, 1);
2562 /***
2563 * DESCRIPTION:
2564 * Updates the width, and height of an item.
2566 * PARAMETER(S):
2567 * [I] infoPtr : valid pointer to the listview structure
2569 * RETURN:
2570 * None.
2572 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2574 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2575 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2579 /***
2580 * DESCRIPTION:
2581 * Retrieves and saves important text metrics info for the current
2582 * Listview font.
2584 * PARAMETER(S):
2585 * [I] infoPtr : valid pointer to the listview structure
2588 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2590 HDC hdc = GetDC(infoPtr->hwndSelf);
2591 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2592 HFONT hOldFont = SelectObject(hdc, hFont);
2593 TEXTMETRICW tm;
2594 SIZE sz;
2596 if (GetTextMetricsW(hdc, &tm))
2598 infoPtr->ntmHeight = tm.tmHeight;
2599 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2602 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2603 infoPtr->nEllipsisWidth = sz.cx;
2605 SelectObject(hdc, hOldFont);
2606 ReleaseDC(infoPtr->hwndSelf, hdc);
2608 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2611 /***
2612 * DESCRIPTION:
2613 * A compare function for ranges
2615 * PARAMETER(S)
2616 * [I] range1 : pointer to range 1;
2617 * [I] range2 : pointer to range 2;
2618 * [I] flags : flags
2620 * RETURNS:
2621 * > 0 : if range 1 > range 2
2622 * < 0 : if range 2 > range 1
2623 * = 0 : if range intersects range 2
2625 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2627 INT cmp;
2629 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2630 cmp = -1;
2631 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2632 cmp = 1;
2633 else
2634 cmp = 0;
2636 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2638 return cmp;
2641 #if DEBUG_RANGES
2642 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2643 #else
2644 #define ranges_check(ranges, desc) do { } while(0)
2645 #endif
2647 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2649 INT i;
2650 RANGE *prev, *curr;
2652 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2653 assert (ranges);
2654 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2655 ranges_dump(ranges);
2656 prev = DPA_GetPtr(ranges->hdpa, 0);
2657 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2658 assert (prev->lower >= 0 && prev->lower < prev->upper);
2659 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2661 curr = DPA_GetPtr(ranges->hdpa, i);
2662 assert (prev->upper <= curr->lower);
2663 assert (curr->lower < curr->upper);
2664 prev = curr;
2666 TRACE("--- Done checking---\n");
2669 static RANGES ranges_create(int count)
2671 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2672 if (!ranges) return NULL;
2673 ranges->hdpa = DPA_Create(count);
2674 if (ranges->hdpa) return ranges;
2675 Free(ranges);
2676 return NULL;
2679 static void ranges_clear(RANGES ranges)
2681 INT i;
2683 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2684 Free(DPA_GetPtr(ranges->hdpa, i));
2685 DPA_DeleteAllPtrs(ranges->hdpa);
2689 static void ranges_destroy(RANGES ranges)
2691 if (!ranges) return;
2692 ranges_clear(ranges);
2693 DPA_Destroy(ranges->hdpa);
2694 Free(ranges);
2697 static RANGES ranges_clone(RANGES ranges)
2699 RANGES clone;
2700 INT i;
2702 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2704 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2706 RANGE *newrng = Alloc(sizeof(RANGE));
2707 if (!newrng) goto fail;
2708 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2709 DPA_SetPtr(clone->hdpa, i, newrng);
2711 return clone;
2713 fail:
2714 TRACE ("clone failed\n");
2715 ranges_destroy(clone);
2716 return NULL;
2719 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2721 INT i;
2723 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2724 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2726 return ranges;
2729 static void ranges_dump(RANGES ranges)
2731 INT i;
2733 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2734 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2737 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2739 RANGE srchrng = { nItem, nItem + 1 };
2741 TRACE("(nItem=%d)\n", nItem);
2742 ranges_check(ranges, "before contain");
2743 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2746 static INT ranges_itemcount(RANGES ranges)
2748 INT i, count = 0;
2750 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2752 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2753 count += sel->upper - sel->lower;
2756 return count;
2759 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2761 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2762 INT index;
2764 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2765 if (index == -1) return TRUE;
2767 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2769 chkrng = DPA_GetPtr(ranges->hdpa, index);
2770 if (chkrng->lower >= nItem)
2771 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2772 if (chkrng->upper > nItem)
2773 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2775 return TRUE;
2778 static BOOL ranges_add(RANGES ranges, RANGE range)
2780 RANGE srchrgn;
2781 INT index;
2783 TRACE("(%s)\n", debugrange(&range));
2784 ranges_check(ranges, "before add");
2786 /* try find overlapping regions first */
2787 srchrgn.lower = range.lower - 1;
2788 srchrgn.upper = range.upper + 1;
2789 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2791 if (index == -1)
2793 RANGE *newrgn;
2795 TRACE("Adding new range\n");
2797 /* create the brand new range to insert */
2798 newrgn = Alloc(sizeof(RANGE));
2799 if(!newrgn) goto fail;
2800 *newrgn = range;
2802 /* figure out where to insert it */
2803 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2804 TRACE("index=%d\n", index);
2805 if (index == -1) index = 0;
2807 /* and get it over with */
2808 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2810 Free(newrgn);
2811 goto fail;
2814 else
2816 RANGE *chkrgn, *mrgrgn;
2817 INT fromindex, mergeindex;
2819 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2820 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2822 chkrgn->lower = min(range.lower, chkrgn->lower);
2823 chkrgn->upper = max(range.upper, chkrgn->upper);
2825 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2827 /* merge now common ranges */
2828 fromindex = 0;
2829 srchrgn.lower = chkrgn->lower - 1;
2830 srchrgn.upper = chkrgn->upper + 1;
2834 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2835 if (mergeindex == -1) break;
2836 if (mergeindex == index)
2838 fromindex = index + 1;
2839 continue;
2842 TRACE("Merge with index %i\n", mergeindex);
2844 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2845 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2846 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2847 Free(mrgrgn);
2848 DPA_DeletePtr(ranges->hdpa, mergeindex);
2849 if (mergeindex < index) index --;
2850 } while(1);
2853 ranges_check(ranges, "after add");
2854 return TRUE;
2856 fail:
2857 ranges_check(ranges, "failed add");
2858 return FALSE;
2861 static BOOL ranges_del(RANGES ranges, RANGE range)
2863 RANGE *chkrgn;
2864 INT index;
2866 TRACE("(%s)\n", debugrange(&range));
2867 ranges_check(ranges, "before del");
2869 /* we don't use DPAS_SORTED here, since we need *
2870 * to find the first overlapping range */
2871 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2872 while(index != -1)
2874 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2876 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2878 /* case 1: Same range */
2879 if ( (chkrgn->upper == range.upper) &&
2880 (chkrgn->lower == range.lower) )
2882 DPA_DeletePtr(ranges->hdpa, index);
2883 break;
2885 /* case 2: engulf */
2886 else if ( (chkrgn->upper <= range.upper) &&
2887 (chkrgn->lower >= range.lower) )
2889 DPA_DeletePtr(ranges->hdpa, index);
2891 /* case 3: overlap upper */
2892 else if ( (chkrgn->upper <= range.upper) &&
2893 (chkrgn->lower < range.lower) )
2895 chkrgn->upper = range.lower;
2897 /* case 4: overlap lower */
2898 else if ( (chkrgn->upper > range.upper) &&
2899 (chkrgn->lower >= range.lower) )
2901 chkrgn->lower = range.upper;
2902 break;
2904 /* case 5: fully internal */
2905 else
2907 RANGE tmprgn = *chkrgn, *newrgn;
2909 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2910 newrgn->lower = chkrgn->lower;
2911 newrgn->upper = range.lower;
2912 chkrgn->lower = range.upper;
2913 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2915 Free(newrgn);
2916 goto fail;
2918 chkrgn = &tmprgn;
2919 break;
2922 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2925 ranges_check(ranges, "after del");
2926 return TRUE;
2928 fail:
2929 ranges_check(ranges, "failed del");
2930 return FALSE;
2933 /***
2934 * DESCRIPTION:
2935 * Removes all selection ranges
2937 * Parameters(s):
2938 * [I] infoPtr : valid pointer to the listview structure
2939 * [I] toSkip : item range to skip removing the selection
2941 * RETURNS:
2942 * SUCCESS : TRUE
2943 * FAILURE : TRUE
2945 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2947 LVITEMW lvItem;
2948 ITERATOR i;
2949 RANGES clone;
2951 TRACE("()\n");
2953 lvItem.state = 0;
2954 lvItem.stateMask = LVIS_SELECTED;
2956 /* need to clone the DPA because callbacks can change it */
2957 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2958 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2959 while(iterator_next(&i))
2960 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2961 /* note that the iterator destructor will free the cloned range */
2962 iterator_destroy(&i);
2964 return TRUE;
2967 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2969 RANGES toSkip;
2971 if (!(toSkip = ranges_create(1))) return FALSE;
2972 if (nItem != -1) ranges_additem(toSkip, nItem);
2973 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2974 ranges_destroy(toSkip);
2975 return TRUE;
2978 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2980 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2983 /***
2984 * DESCRIPTION:
2985 * Retrieves the number of items that are marked as selected.
2987 * PARAMETER(S):
2988 * [I] infoPtr : valid pointer to the listview structure
2990 * RETURN:
2991 * Number of items selected.
2993 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2995 INT nSelectedCount = 0;
2997 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2999 INT i;
3000 for (i = 0; i < infoPtr->nItemCount; i++)
3002 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3003 nSelectedCount++;
3006 else
3007 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3009 TRACE("nSelectedCount=%d\n", nSelectedCount);
3010 return nSelectedCount;
3013 /***
3014 * DESCRIPTION:
3015 * Manages the item focus.
3017 * PARAMETER(S):
3018 * [I] infoPtr : valid pointer to the listview structure
3019 * [I] nItem : item index
3021 * RETURN:
3022 * TRUE : focused item changed
3023 * FALSE : focused item has NOT changed
3025 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3027 INT oldFocus = infoPtr->nFocusedItem;
3028 LVITEMW lvItem;
3030 if (nItem == infoPtr->nFocusedItem) return FALSE;
3032 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3033 lvItem.stateMask = LVIS_FOCUSED;
3034 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3036 return oldFocus != infoPtr->nFocusedItem;
3039 /* Helper function for LISTVIEW_ShiftIndices *only* */
3040 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3042 if (nShiftItem < nItem) return nShiftItem;
3044 if (nShiftItem > nItem) return nShiftItem + direction;
3046 if (direction > 0) return nShiftItem + direction;
3048 return min(nShiftItem, infoPtr->nItemCount - 1);
3052 * DESCRIPTION:
3053 * Updates the various indices after an item has been inserted or deleted.
3055 * PARAMETER(S):
3056 * [I] infoPtr : valid pointer to the listview structure
3057 * [I] nItem : item index
3058 * [I] direction : Direction of shift, +1 or -1.
3060 * RETURN:
3061 * None
3063 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3065 INT nNewFocus;
3066 BOOL bOldChange;
3068 /* temporarily disable change notification while shifting items */
3069 bOldChange = infoPtr->bDoChangeNotify;
3070 infoPtr->bDoChangeNotify = FALSE;
3072 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3074 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3076 assert(abs(direction) == 1);
3078 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3080 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3081 if (nNewFocus != infoPtr->nFocusedItem)
3082 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3084 /* But we are not supposed to modify nHotItem! */
3086 infoPtr->bDoChangeNotify = bOldChange;
3091 * DESCRIPTION:
3092 * Adds a block of selections.
3094 * PARAMETER(S):
3095 * [I] infoPtr : valid pointer to the listview structure
3096 * [I] nItem : item index
3098 * RETURN:
3099 * Whether the window is still valid.
3101 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3103 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3104 INT nLast = max(infoPtr->nSelectionMark, nItem);
3105 HWND hwndSelf = infoPtr->hwndSelf;
3106 NMLVODSTATECHANGE nmlv;
3107 LVITEMW item;
3108 BOOL bOldChange;
3109 INT i;
3111 /* Temporarily disable change notification
3112 * If the control is LVS_OWNERDATA, we need to send
3113 * only one LVN_ODSTATECHANGED notification.
3114 * See MSDN documentation for LVN_ITEMCHANGED.
3116 bOldChange = infoPtr->bDoChangeNotify;
3117 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3119 if (nFirst == -1) nFirst = nItem;
3121 item.state = LVIS_SELECTED;
3122 item.stateMask = LVIS_SELECTED;
3124 for (i = nFirst; i <= nLast; i++)
3125 LISTVIEW_SetItemState(infoPtr,i,&item);
3127 ZeroMemory(&nmlv, sizeof(nmlv));
3128 nmlv.iFrom = nFirst;
3129 nmlv.iTo = nLast;
3130 nmlv.uNewState = 0;
3131 nmlv.uOldState = item.state;
3133 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3134 if (!IsWindow(hwndSelf))
3135 return FALSE;
3136 infoPtr->bDoChangeNotify = bOldChange;
3137 return TRUE;
3141 /***
3142 * DESCRIPTION:
3143 * Sets a single group selection.
3145 * PARAMETER(S):
3146 * [I] infoPtr : valid pointer to the listview structure
3147 * [I] nItem : item index
3149 * RETURN:
3150 * None
3152 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3154 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3155 RANGES selection;
3156 LVITEMW item;
3157 ITERATOR i;
3158 BOOL bOldChange;
3160 if (!(selection = ranges_create(100))) return;
3162 item.state = LVIS_SELECTED;
3163 item.stateMask = LVIS_SELECTED;
3165 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3167 if (infoPtr->nSelectionMark == -1)
3169 infoPtr->nSelectionMark = nItem;
3170 ranges_additem(selection, nItem);
3172 else
3174 RANGE sel;
3176 sel.lower = min(infoPtr->nSelectionMark, nItem);
3177 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3178 ranges_add(selection, sel);
3181 else
3183 RECT rcItem, rcSel, rcSelMark;
3184 POINT ptItem;
3186 rcItem.left = LVIR_BOUNDS;
3187 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3188 rcSelMark.left = LVIR_BOUNDS;
3189 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3190 UnionRect(&rcSel, &rcItem, &rcSelMark);
3191 iterator_frameditems(&i, infoPtr, &rcSel);
3192 while(iterator_next(&i))
3194 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3195 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3197 iterator_destroy(&i);
3200 /* disable per item notifications on LVS_OWNERDATA style
3201 FIXME: single LVN_ODSTATECHANGED should be used */
3202 bOldChange = infoPtr->bDoChangeNotify;
3203 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3205 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3208 iterator_rangesitems(&i, selection);
3209 while(iterator_next(&i))
3210 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3211 /* this will also destroy the selection */
3212 iterator_destroy(&i);
3214 infoPtr->bDoChangeNotify = bOldChange;
3216 LISTVIEW_SetItemFocus(infoPtr, nItem);
3219 /***
3220 * DESCRIPTION:
3221 * Sets a single selection.
3223 * PARAMETER(S):
3224 * [I] infoPtr : valid pointer to the listview structure
3225 * [I] nItem : item index
3227 * RETURN:
3228 * None
3230 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3232 LVITEMW lvItem;
3234 TRACE("nItem=%d\n", nItem);
3236 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3238 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3239 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3240 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3242 infoPtr->nSelectionMark = nItem;
3245 /***
3246 * DESCRIPTION:
3247 * Set selection(s) with keyboard.
3249 * PARAMETER(S):
3250 * [I] infoPtr : valid pointer to the listview structure
3251 * [I] nItem : item index
3252 * [I] space : VK_SPACE code sent
3254 * RETURN:
3255 * SUCCESS : TRUE (needs to be repainted)
3256 * FAILURE : FALSE (nothing has changed)
3258 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3260 /* FIXME: pass in the state */
3261 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3262 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3263 BOOL bResult = FALSE;
3265 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3266 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3268 if (infoPtr->dwStyle & LVS_SINGLESEL)
3270 bResult = TRUE;
3271 LISTVIEW_SetSelection(infoPtr, nItem);
3273 else
3275 if (wShift)
3277 bResult = TRUE;
3278 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3280 else if (wCtrl)
3282 LVITEMW lvItem;
3283 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3284 lvItem.stateMask = LVIS_SELECTED;
3285 if (space)
3287 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3288 if (lvItem.state & LVIS_SELECTED)
3289 infoPtr->nSelectionMark = nItem;
3291 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3293 else
3295 bResult = TRUE;
3296 LISTVIEW_SetSelection(infoPtr, nItem);
3299 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3302 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3303 return bResult;
3306 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3308 LVHITTESTINFO lvHitTestInfo;
3310 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3311 lvHitTestInfo.pt.x = pt.x;
3312 lvHitTestInfo.pt.y = pt.y;
3314 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3316 lpLVItem->mask = LVIF_PARAM;
3317 lpLVItem->iItem = lvHitTestInfo.iItem;
3318 lpLVItem->iSubItem = 0;
3320 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3323 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3325 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3326 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3327 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3330 /***
3331 * DESCRIPTION:
3332 * Called when the mouse is being actively tracked and has hovered for a specified
3333 * amount of time
3335 * PARAMETER(S):
3336 * [I] infoPtr : valid pointer to the listview structure
3337 * [I] fwKeys : key indicator
3338 * [I] x,y : mouse position
3340 * RETURN:
3341 * 0 if the message was processed, non-zero if there was an error
3343 * INFO:
3344 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3345 * over the item for a certain period of time.
3348 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3350 if (LISTVIEW_isHotTracking(infoPtr))
3352 LVITEMW item;
3353 POINT pt;
3355 pt.x = x;
3356 pt.y = y;
3358 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3359 LISTVIEW_SetSelection(infoPtr, item.iItem);
3362 return 0;
3365 /***
3366 * DESCRIPTION:
3367 * Called whenever WM_MOUSEMOVE is received.
3369 * PARAMETER(S):
3370 * [I] infoPtr : valid pointer to the listview structure
3371 * [I] fwKeys : key indicator
3372 * [I] x,y : mouse position
3374 * RETURN:
3375 * 0 if the message is processed, non-zero if there was an error
3377 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3379 TRACKMOUSEEVENT trackinfo;
3381 if (!(fwKeys & MK_LBUTTON))
3382 infoPtr->bLButtonDown = FALSE;
3384 if (infoPtr->bLButtonDown)
3386 POINT tmp;
3387 RECT rect;
3388 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3389 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3391 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3392 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3393 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3394 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3396 tmp.x = x;
3397 tmp.y = y;
3399 if (!PtInRect(&rect, tmp))
3401 LVHITTESTINFO lvHitTestInfo;
3402 NMLISTVIEW nmlv;
3404 lvHitTestInfo.pt = infoPtr->ptClickPos;
3405 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3407 ZeroMemory(&nmlv, sizeof(nmlv));
3408 nmlv.iItem = lvHitTestInfo.iItem;
3409 nmlv.ptAction = infoPtr->ptClickPos;
3411 if (!infoPtr->bDragging)
3413 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3414 infoPtr->bDragging = TRUE;
3417 return 0;
3420 else
3421 infoPtr->bLButtonDown = FALSE;
3423 /* see if we are supposed to be tracking mouse hovering */
3424 if (LISTVIEW_isHotTracking(infoPtr)) {
3425 /* fill in the trackinfo struct */
3426 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3427 trackinfo.dwFlags = TME_QUERY;
3428 trackinfo.hwndTrack = infoPtr->hwndSelf;
3429 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3431 /* see if we are already tracking this hwnd */
3432 _TrackMouseEvent(&trackinfo);
3434 if(!(trackinfo.dwFlags & TME_HOVER)) {
3435 trackinfo.dwFlags = TME_HOVER;
3437 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3438 _TrackMouseEvent(&trackinfo);
3442 return 0;
3446 /***
3447 * Tests whether the item is assignable to a list with style lStyle
3449 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3451 if ( (lpLVItem->mask & LVIF_TEXT) &&
3452 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3453 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3455 return TRUE;
3459 /***
3460 * DESCRIPTION:
3461 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3463 * PARAMETER(S):
3464 * [I] infoPtr : valid pointer to the listview structure
3465 * [I] lpLVItem : valid pointer to new item attributes
3466 * [I] isNew : the item being set is being inserted
3467 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3468 * [O] bChanged : will be set to TRUE if the item really changed
3470 * RETURN:
3471 * SUCCESS : TRUE
3472 * FAILURE : FALSE
3474 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3476 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3477 ITEM_INFO *lpItem;
3478 NMLISTVIEW nmlv;
3479 UINT uChanged = 0;
3480 LVITEMW item;
3481 /* stateMask is ignored for LVM_INSERTITEM */
3482 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3484 TRACE("()\n");
3486 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3488 if (lpLVItem->mask == 0) return TRUE;
3490 if (infoPtr->dwStyle & LVS_OWNERDATA)
3492 /* a virtual listview only stores selection and focus */
3493 if (lpLVItem->mask & ~LVIF_STATE)
3494 return FALSE;
3495 lpItem = NULL;
3497 else
3499 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3500 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3501 assert (lpItem);
3504 /* we need to get the lParam and state of the item */
3505 item.iItem = lpLVItem->iItem;
3506 item.iSubItem = lpLVItem->iSubItem;
3507 item.mask = LVIF_STATE | LVIF_PARAM;
3508 item.stateMask = ~0;
3509 item.state = 0;
3510 item.lParam = 0;
3511 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3513 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3514 /* determine what fields will change */
3515 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3516 uChanged |= LVIF_STATE;
3518 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3519 uChanged |= LVIF_IMAGE;
3521 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3522 uChanged |= LVIF_PARAM;
3524 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3525 uChanged |= LVIF_INDENT;
3527 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3528 uChanged |= LVIF_TEXT;
3530 TRACE("uChanged=0x%x\n", uChanged);
3531 if (!uChanged) return TRUE;
3532 *bChanged = TRUE;
3534 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3535 nmlv.iItem = lpLVItem->iItem;
3536 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3537 nmlv.uOldState = item.state;
3538 nmlv.uChanged = uChanged;
3539 nmlv.lParam = item.lParam;
3541 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3542 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3543 /* are enabled */
3544 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3546 HWND hwndSelf = infoPtr->hwndSelf;
3548 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3549 return FALSE;
3550 if (!IsWindow(hwndSelf))
3551 return FALSE;
3554 /* copy information */
3555 if (lpLVItem->mask & LVIF_TEXT)
3556 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3558 if (lpLVItem->mask & LVIF_IMAGE)
3559 lpItem->hdr.iImage = lpLVItem->iImage;
3561 if (lpLVItem->mask & LVIF_PARAM)
3562 lpItem->lParam = lpLVItem->lParam;
3564 if (lpLVItem->mask & LVIF_INDENT)
3565 lpItem->iIndent = lpLVItem->iIndent;
3567 if (uChanged & LVIF_STATE)
3569 if (lpItem && (stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3571 lpItem->state &= ~stateMask;
3572 lpItem->state |= (lpLVItem->state & stateMask);
3574 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3576 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3577 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3579 else if (stateMask & LVIS_SELECTED)
3580 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3582 /* if we are asked to change focus, and we manage it, do it */
3583 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3585 if (lpLVItem->state & LVIS_FOCUSED)
3587 LISTVIEW_SetItemFocus(infoPtr, -1);
3588 infoPtr->nFocusedItem = lpLVItem->iItem;
3589 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3591 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3592 infoPtr->nFocusedItem = -1;
3596 /* if we're inserting the item, we're done */
3597 if (isNew) return TRUE;
3599 /* send LVN_ITEMCHANGED notification */
3600 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3601 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3603 return TRUE;
3606 /***
3607 * DESCRIPTION:
3608 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3610 * PARAMETER(S):
3611 * [I] infoPtr : valid pointer to the listview structure
3612 * [I] lpLVItem : valid pointer to new subitem attributes
3613 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3614 * [O] bChanged : will be set to TRUE if the item really changed
3616 * RETURN:
3617 * SUCCESS : TRUE
3618 * FAILURE : FALSE
3620 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3622 HDPA hdpaSubItems;
3623 SUBITEM_INFO *lpSubItem;
3625 /* we do not support subitems for virtual listviews */
3626 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3628 /* set subitem only if column is present */
3629 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3631 /* First do some sanity checks */
3632 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3633 particularly useful. We currently do not actually do anything with
3634 the flag on subitems.
3636 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3637 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3639 /* get the subitem structure, and create it if not there */
3640 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3641 assert (hdpaSubItems);
3643 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3644 if (!lpSubItem)
3646 SUBITEM_INFO *tmpSubItem;
3647 INT i;
3649 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3650 if (!lpSubItem) return FALSE;
3651 /* we could binary search here, if need be...*/
3652 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3654 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3655 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3657 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3659 Free(lpSubItem);
3660 return FALSE;
3662 lpSubItem->iSubItem = lpLVItem->iSubItem;
3663 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3664 *bChanged = TRUE;
3667 if (lpLVItem->mask & LVIF_IMAGE)
3668 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3670 lpSubItem->hdr.iImage = lpLVItem->iImage;
3671 *bChanged = TRUE;
3674 if (lpLVItem->mask & LVIF_TEXT)
3675 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3677 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3678 *bChanged = TRUE;
3681 return TRUE;
3684 /***
3685 * DESCRIPTION:
3686 * Sets item attributes.
3688 * PARAMETER(S):
3689 * [I] infoPtr : valid pointer to the listview structure
3690 * [I] lpLVItem : new item attributes
3691 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3693 * RETURN:
3694 * SUCCESS : TRUE
3695 * FAILURE : FALSE
3697 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3699 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3700 HWND hwndSelf = infoPtr->hwndSelf;
3701 LPWSTR pszText = NULL;
3702 BOOL bResult, bChanged = FALSE;
3704 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3706 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3707 return FALSE;
3709 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3710 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3712 pszText = lpLVItem->pszText;
3713 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3716 /* actually set the fields */
3717 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3719 if (lpLVItem->iSubItem)
3720 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3721 else
3722 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3723 if (!IsWindow(hwndSelf))
3724 return FALSE;
3726 /* redraw item, if necessary */
3727 if (bChanged && !infoPtr->bIsDrawing)
3729 /* this little optimization eliminates some nasty flicker */
3730 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3731 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3732 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3733 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3734 else
3735 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3737 /* restore text */
3738 if (pszText)
3740 textfreeT(lpLVItem->pszText, isW);
3741 lpLVItem->pszText = pszText;
3744 return bResult;
3747 /***
3748 * DESCRIPTION:
3749 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3751 * PARAMETER(S):
3752 * [I] infoPtr : valid pointer to the listview structure
3754 * RETURN:
3755 * item index
3757 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3759 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3760 INT nItem = 0;
3761 SCROLLINFO scrollInfo;
3763 scrollInfo.cbSize = sizeof(SCROLLINFO);
3764 scrollInfo.fMask = SIF_POS;
3766 if (uView == LVS_LIST)
3768 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3769 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3771 else if (uView == LVS_REPORT)
3773 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3774 nItem = scrollInfo.nPos;
3776 else
3778 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3779 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3782 TRACE("nItem=%d\n", nItem);
3784 return nItem;
3788 /***
3789 * DESCRIPTION:
3790 * Erases the background of the given rectangle
3792 * PARAMETER(S):
3793 * [I] infoPtr : valid pointer to the listview structure
3794 * [I] hdc : device context handle
3795 * [I] lprcBox : clipping rectangle
3797 * RETURN:
3798 * Success: TRUE
3799 * Failure: FALSE
3801 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3803 if (!infoPtr->hBkBrush) return FALSE;
3805 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3807 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3810 /***
3811 * DESCRIPTION:
3812 * Draws an item.
3814 * PARAMETER(S):
3815 * [I] infoPtr : valid pointer to the listview structure
3816 * [I] hdc : device context handle
3817 * [I] nItem : item index
3818 * [I] nSubItem : subitem index
3819 * [I] pos : item position in client coordinates
3820 * [I] cdmode : custom draw mode
3822 * RETURN:
3823 * Success: TRUE
3824 * Failure: FALSE
3826 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3828 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3829 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3830 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3831 DWORD cdsubitemmode = CDRF_DODEFAULT;
3832 LPRECT lprcFocus;
3833 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3834 NMLVCUSTOMDRAW nmlvcd;
3835 HIMAGELIST himl;
3836 LVITEMW lvItem;
3837 HFONT hOldFont;
3839 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3841 /* get information needed for drawing the item */
3842 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3843 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3844 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3845 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3846 lvItem.iItem = nItem;
3847 lvItem.iSubItem = nSubItem;
3848 lvItem.state = 0;
3849 lvItem.lParam = 0;
3850 lvItem.cchTextMax = DISP_TEXT_SIZE;
3851 lvItem.pszText = szDispText;
3852 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3853 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3854 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3855 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3856 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3858 /* now check if we need to update the focus rectangle */
3859 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3861 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3862 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3863 OffsetRect(&rcBox, pos.x, pos.y);
3864 OffsetRect(&rcSelect, pos.x, pos.y);
3865 OffsetRect(&rcIcon, pos.x, pos.y);
3866 OffsetRect(&rcStateIcon, pos.x, pos.y);
3867 OffsetRect(&rcLabel, pos.x, pos.y);
3868 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3869 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3870 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3872 /* fill in the custom draw structure */
3873 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3875 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3876 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3877 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3878 if (cdmode & CDRF_NOTIFYITEMDRAW)
3879 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3880 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3881 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3882 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3883 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3885 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3886 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3888 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3889 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3890 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3891 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3893 /* in full row select, subitems, will just use main item's colors */
3894 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3895 nmlvcd.clrTextBk = CLR_NONE;
3897 /* state icons */
3898 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3900 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3901 if (uStateImage)
3903 TRACE("uStateImage=%d\n", uStateImage);
3904 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3905 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3909 /* small icons */
3910 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3911 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3913 TRACE("iImage=%d\n", lvItem.iImage);
3914 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3915 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3916 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3919 /* Don't bother painting item being edited */
3920 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3922 /* FIXME: temporary hack */
3923 rcSelect.left = rcLabel.left;
3925 /* draw the selection background, if we're drawing the main item */
3926 if (nSubItem == 0)
3928 /* in icon mode, the label rect is really what we want to draw the
3929 * background for */
3930 if (uView == LVS_ICON)
3931 rcSelect = rcLabel;
3933 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3934 rcSelect.right = rcBox.right;
3936 if (nmlvcd.clrTextBk != CLR_NONE)
3937 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3938 if(lprcFocus) *lprcFocus = rcSelect;
3941 /* figure out the text drawing flags */
3942 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3943 if (uView == LVS_ICON)
3944 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3945 else if (nSubItem)
3947 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3949 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3950 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3951 default: uFormat |= DT_LEFT;
3954 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3956 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3957 else rcLabel.left += LABEL_HOR_PADDING;
3959 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3961 /* for GRIDLINES reduce the bottom so the text formats correctly */
3962 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3963 rcLabel.bottom--;
3965 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3967 postpaint:
3968 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3969 notify_postpaint(infoPtr, &nmlvcd);
3970 if (cdsubitemmode & CDRF_NEWFONT)
3971 SelectObject(hdc, hOldFont);
3972 return TRUE;
3975 /***
3976 * DESCRIPTION:
3977 * Draws listview items when in owner draw mode.
3979 * PARAMETER(S):
3980 * [I] infoPtr : valid pointer to the listview structure
3981 * [I] hdc : device context handle
3983 * RETURN:
3984 * None
3986 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3988 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3989 DWORD cditemmode = CDRF_DODEFAULT;
3990 NMLVCUSTOMDRAW nmlvcd;
3991 POINT Origin, Position;
3992 DRAWITEMSTRUCT dis;
3993 LVITEMW item;
3995 TRACE("()\n");
3997 ZeroMemory(&dis, sizeof(dis));
3999 /* Get scroll info once before loop */
4000 LISTVIEW_GetOrigin(infoPtr, &Origin);
4002 /* iterate through the invalidated rows */
4003 while(iterator_next(i))
4005 item.iItem = i->nItem;
4006 item.iSubItem = 0;
4007 item.mask = LVIF_PARAM | LVIF_STATE;
4008 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4009 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4011 dis.CtlType = ODT_LISTVIEW;
4012 dis.CtlID = uID;
4013 dis.itemID = item.iItem;
4014 dis.itemAction = ODA_DRAWENTIRE;
4015 dis.itemState = 0;
4016 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4017 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4018 dis.hwndItem = infoPtr->hwndSelf;
4019 dis.hDC = hdc;
4020 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4021 dis.rcItem.left = Position.x + Origin.x;
4022 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4023 dis.rcItem.top = Position.y + Origin.y;
4024 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4025 dis.itemData = item.lParam;
4027 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4030 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4031 * structure for the rest. of the paint cycle
4033 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4034 if (cdmode & CDRF_NOTIFYITEMDRAW)
4035 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4037 if (!(cditemmode & CDRF_SKIPDEFAULT))
4039 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4040 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4043 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4044 notify_postpaint(infoPtr, &nmlvcd);
4048 /***
4049 * DESCRIPTION:
4050 * Draws listview items when in report display mode.
4052 * PARAMETER(S):
4053 * [I] infoPtr : valid pointer to the listview structure
4054 * [I] hdc : device context handle
4055 * [I] cdmode : custom draw mode
4057 * RETURN:
4058 * None
4060 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4062 INT rgntype;
4063 RECT rcClip, rcItem;
4064 POINT Origin, Position;
4065 RANGE colRange;
4066 ITERATOR j;
4068 TRACE("()\n");
4070 /* figure out what to draw */
4071 rgntype = GetClipBox(hdc, &rcClip);
4072 if (rgntype == NULLREGION) return;
4074 /* Get scroll info once before loop */
4075 LISTVIEW_GetOrigin(infoPtr, &Origin);
4077 /* narrow down the columns we need to paint */
4078 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4080 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4081 if (rcItem.right + Origin.x >= rcClip.left) break;
4083 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4085 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4086 if (rcItem.left + Origin.x < rcClip.right) break;
4088 iterator_rangeitems(&j, colRange);
4090 /* in full row select, we _have_ to draw the main item */
4091 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4092 j.nSpecial = 0;
4094 /* iterate through the invalidated rows */
4095 while(iterator_next(i))
4097 /* iterate through the invalidated columns */
4098 while(iterator_next(&j))
4100 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4101 Position.x += Origin.x;
4102 Position.y += Origin.y;
4104 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4106 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4107 rcItem.top = 0;
4108 rcItem.bottom = infoPtr->nItemHeight;
4109 OffsetRect(&rcItem, Position.x, Position.y);
4110 if (!RectVisible(hdc, &rcItem)) continue;
4113 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4116 iterator_destroy(&j);
4119 /***
4120 * DESCRIPTION:
4121 * Draws the gridlines if necessary when in report display mode.
4123 * PARAMETER(S):
4124 * [I] infoPtr : valid pointer to the listview structure
4125 * [I] hdc : device context handle
4127 * RETURN:
4128 * None
4130 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4132 INT rgntype;
4133 INT y, itemheight;
4134 HPEN hPen, hOldPen;
4135 RECT rcClip, rcItem;
4136 POINT Origin;
4137 RANGE colRange;
4138 ITERATOR j;
4140 TRACE("()\n");
4142 /* figure out what to draw */
4143 rgntype = GetClipBox(hdc, &rcClip);
4144 if (rgntype == NULLREGION) return;
4146 /* Get scroll info once before loop */
4147 LISTVIEW_GetOrigin(infoPtr, &Origin);
4149 /* narrow down the columns we need to paint */
4150 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4152 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4153 if (rcItem.right + Origin.x >= rcClip.left) break;
4155 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4157 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4158 if (rcItem.left + Origin.x < rcClip.right) break;
4160 iterator_rangeitems(&j, colRange);
4162 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4164 hOldPen = SelectObject ( hdc, hPen );
4166 /* draw the vertical lines for the columns */
4167 iterator_rangeitems(&j, colRange);
4168 while(iterator_next(&j))
4170 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4171 if (rcItem.left == 0) continue; /* skip first column */
4172 rcItem.left += Origin.x;
4173 rcItem.right += Origin.x;
4174 rcItem.top = infoPtr->rcList.top;
4175 rcItem.bottom = infoPtr->rcList.bottom;
4176 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4177 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4178 LineTo (hdc, rcItem.left, rcItem.bottom);
4180 iterator_destroy(&j);
4182 /* draw the horizontial lines for the rows */
4183 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4184 rcItem.left = infoPtr->rcList.left + Origin.x;
4185 rcItem.right = infoPtr->rcList.right + Origin.x;
4186 rcItem.bottom = rcItem.top = Origin.y - 1;
4187 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4188 LineTo(hdc, rcItem.right, rcItem.top);
4189 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4191 rcItem.bottom = rcItem.top = y;
4192 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4193 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4194 LineTo (hdc, rcItem.right, rcItem.top);
4197 SelectObject( hdc, hOldPen );
4198 DeleteObject( hPen );
4202 /***
4203 * DESCRIPTION:
4204 * Draws listview items when in list display mode.
4206 * PARAMETER(S):
4207 * [I] infoPtr : valid pointer to the listview structure
4208 * [I] hdc : device context handle
4209 * [I] cdmode : custom draw mode
4211 * RETURN:
4212 * None
4214 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4216 POINT Origin, Position;
4218 /* Get scroll info once before loop */
4219 LISTVIEW_GetOrigin(infoPtr, &Origin);
4221 while(iterator_prev(i))
4223 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4224 Position.x += Origin.x;
4225 Position.y += Origin.y;
4227 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4232 /***
4233 * DESCRIPTION:
4234 * Draws listview items.
4236 * PARAMETER(S):
4237 * [I] infoPtr : valid pointer to the listview structure
4238 * [I] hdc : device context handle
4239 * [I] prcErase : rect to be erased before refresh (may be NULL)
4241 * RETURN:
4242 * NoneX
4244 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4246 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4247 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4248 NMLVCUSTOMDRAW nmlvcd;
4249 HFONT hOldFont = 0;
4250 DWORD cdmode;
4251 INT oldBkMode = 0;
4252 RECT rcClient;
4253 ITERATOR i;
4254 HDC hdcOrig = hdc;
4255 HBITMAP hbmp = NULL;
4257 LISTVIEW_DUMP(infoPtr);
4259 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4260 TRACE("double buffering\n");
4262 hdc = CreateCompatibleDC(hdcOrig);
4263 if (!hdc) {
4264 ERR("Failed to create DC for backbuffer\n");
4265 return;
4267 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4268 infoPtr->rcList.bottom);
4269 if (!hbmp) {
4270 ERR("Failed to create bitmap for backbuffer\n");
4271 DeleteDC(hdc);
4272 return;
4275 SelectObject(hdc, hbmp);
4276 SelectObject(hdc, infoPtr->hFont);
4277 } else {
4278 /* Save dc values we're gonna trash while drawing
4279 * FIXME: Should be done in LISTVIEW_DrawItem() */
4280 hOldFont = SelectObject(hdc, infoPtr->hFont);
4281 oldBkMode = GetBkMode(hdc);
4282 oldBkColor = GetBkColor(hdc);
4283 oldTextColor = GetTextColor(hdc);
4286 infoPtr->bIsDrawing = TRUE;
4288 if (prcErase) {
4289 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4290 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4291 /* If no erasing was done (usually because RedrawWindow was called
4292 * with RDW_INVALIDATE only) we need to copy the old contents into
4293 * the backbuffer before continuing. */
4294 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4295 infoPtr->rcList.right - infoPtr->rcList.left,
4296 infoPtr->rcList.bottom - infoPtr->rcList.top,
4297 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4300 /* FIXME: Shouldn't need to do this */
4301 oldClrTextBk = infoPtr->clrTextBk;
4302 oldClrText = infoPtr->clrText;
4304 infoPtr->cditemmode = CDRF_DODEFAULT;
4306 GetClientRect(infoPtr->hwndSelf, &rcClient);
4307 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4308 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4309 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4310 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4312 /* Use these colors to draw the items */
4313 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4314 infoPtr->clrText = nmlvcd.clrText;
4316 /* nothing to draw */
4317 if(infoPtr->nItemCount == 0) goto enddraw;
4319 /* figure out what we need to draw */
4320 iterator_visibleitems(&i, infoPtr, hdc);
4322 /* send cache hint notification */
4323 if (infoPtr->dwStyle & LVS_OWNERDATA)
4325 RANGE range = iterator_range(&i);
4326 NMLVCACHEHINT nmlv;
4328 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4329 nmlv.iFrom = range.lower;
4330 nmlv.iTo = range.upper - 1;
4331 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4334 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4335 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4336 else
4338 if (uView == LVS_REPORT)
4339 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4340 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4341 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4343 /* if we have a focus rect, draw it */
4344 if (infoPtr->bFocus)
4345 DrawFocusRect(hdc, &infoPtr->rcFocus);
4347 iterator_destroy(&i);
4349 enddraw:
4350 /* For LVS_EX_GRIDLINES go and draw lines */
4351 /* This includes the case where there were *no* items */
4352 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4353 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4354 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4356 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4357 notify_postpaint(infoPtr, &nmlvcd);
4359 infoPtr->clrTextBk = oldClrTextBk;
4360 infoPtr->clrText = oldClrText;
4362 if(hbmp) {
4363 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4364 infoPtr->rcList.right - infoPtr->rcList.left,
4365 infoPtr->rcList.bottom - infoPtr->rcList.top,
4366 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4368 DeleteObject(hbmp);
4369 DeleteDC(hdc);
4370 } else {
4371 SelectObject(hdc, hOldFont);
4372 SetBkMode(hdc, oldBkMode);
4373 SetBkColor(hdc, oldBkColor);
4374 SetTextColor(hdc, oldTextColor);
4377 infoPtr->bIsDrawing = FALSE;
4381 /***
4382 * DESCRIPTION:
4383 * Calculates the approximate width and height of a given number of items.
4385 * PARAMETER(S):
4386 * [I] infoPtr : valid pointer to the listview structure
4387 * [I] nItemCount : number of items
4388 * [I] wWidth : width
4389 * [I] wHeight : height
4391 * RETURN:
4392 * Returns a DWORD. The width in the low word and the height in high word.
4394 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4395 WORD wWidth, WORD wHeight)
4397 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4398 INT nItemCountPerColumn = 1;
4399 INT nColumnCount = 0;
4400 DWORD dwViewRect = 0;
4402 if (nItemCount == -1)
4403 nItemCount = infoPtr->nItemCount;
4405 if (uView == LVS_LIST)
4407 if (wHeight == 0xFFFF)
4409 /* use current height */
4410 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4413 if (wHeight < infoPtr->nItemHeight)
4414 wHeight = infoPtr->nItemHeight;
4416 if (nItemCount > 0)
4418 if (infoPtr->nItemHeight > 0)
4420 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4421 if (nItemCountPerColumn == 0)
4422 nItemCountPerColumn = 1;
4424 if (nItemCount % nItemCountPerColumn != 0)
4425 nColumnCount = nItemCount / nItemCountPerColumn;
4426 else
4427 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4431 /* Microsoft padding magic */
4432 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4433 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4435 dwViewRect = MAKELONG(wWidth, wHeight);
4437 else if (uView == LVS_REPORT)
4439 RECT rcBox;
4441 if (infoPtr->nItemCount > 0)
4443 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4444 wWidth = rcBox.right - rcBox.left;
4445 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4447 else
4449 /* use current height and width */
4450 if (wHeight == 0xffff)
4451 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4452 if (wWidth == 0xffff)
4453 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4456 dwViewRect = MAKELONG(wWidth, wHeight);
4458 else if (uView == LVS_SMALLICON)
4459 FIXME("uView == LVS_SMALLICON: not implemented\n");
4460 else if (uView == LVS_ICON)
4461 FIXME("uView == LVS_ICON: not implemented\n");
4463 return dwViewRect;
4467 /***
4468 * DESCRIPTION:
4469 * Create a drag image list for the specified item.
4471 * PARAMETER(S):
4472 * [I] infoPtr : valid pointer to the listview structure
4473 * [I] iItem : index of item
4474 * [O] lppt : Upperr-left corner of the image
4476 * RETURN:
4477 * Returns a handle to the image list if successful, NULL otherwise.
4479 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4481 RECT rcItem;
4482 SIZE size;
4483 POINT pos;
4484 HDC hdc, hdcOrig;
4485 HBITMAP hbmp, hOldbmp;
4486 HIMAGELIST dragList = 0;
4487 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4489 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4490 return 0;
4492 rcItem.left = LVIR_BOUNDS;
4493 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4494 return 0;
4496 lppt->x = rcItem.left;
4497 lppt->y = rcItem.top;
4499 size.cx = rcItem.right - rcItem.left;
4500 size.cy = rcItem.bottom - rcItem.top;
4502 hdcOrig = GetDC(infoPtr->hwndSelf);
4503 hdc = CreateCompatibleDC(hdcOrig);
4504 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4505 hOldbmp = SelectObject(hdc, hbmp);
4507 rcItem.left = rcItem.top = 0;
4508 rcItem.right = size.cx;
4509 rcItem.bottom = size.cy;
4510 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4512 pos.x = pos.y = 0;
4513 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4515 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4516 SelectObject(hdc, hOldbmp);
4517 ImageList_Add(dragList, hbmp, 0);
4519 else
4520 SelectObject(hdc, hOldbmp);
4522 DeleteObject(hbmp);
4523 DeleteDC(hdc);
4524 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4526 TRACE("ret=%p\n", dragList);
4528 return dragList;
4532 /***
4533 * DESCRIPTION:
4534 * Removes all listview items and subitems.
4536 * PARAMETER(S):
4537 * [I] infoPtr : valid pointer to the listview structure
4539 * RETURN:
4540 * SUCCESS : TRUE
4541 * FAILURE : FALSE
4543 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4545 NMLISTVIEW nmlv;
4546 HDPA hdpaSubItems = NULL;
4547 BOOL bSuppress;
4548 ITEMHDR *hdrItem;
4549 INT i, j;
4551 TRACE("()\n");
4553 /* we do it directly, to avoid notifications */
4554 ranges_clear(infoPtr->selectionRanges);
4555 infoPtr->nSelectionMark = -1;
4556 infoPtr->nFocusedItem = -1;
4557 SetRectEmpty(&infoPtr->rcFocus);
4558 /* But we are supposed to leave nHotItem as is! */
4561 /* send LVN_DELETEALLITEMS notification */
4562 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4563 nmlv.iItem = -1;
4564 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4566 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4568 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4570 /* send LVN_DELETEITEM notification, if not suppressed
4571 and if it is not a virtual listview */
4572 if (!bSuppress) notify_deleteitem(infoPtr, i);
4573 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4574 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4576 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4577 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4578 Free(hdrItem);
4580 DPA_Destroy(hdpaSubItems);
4581 DPA_DeletePtr(infoPtr->hdpaItems, i);
4583 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4584 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4585 infoPtr->nItemCount --;
4588 if (!destroy)
4590 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4591 LISTVIEW_UpdateScroll(infoPtr);
4593 LISTVIEW_InvalidateList(infoPtr);
4595 return TRUE;
4598 /***
4599 * DESCRIPTION:
4600 * Scrolls, and updates the columns, when a column is changing width.
4602 * PARAMETER(S):
4603 * [I] infoPtr : valid pointer to the listview structure
4604 * [I] nColumn : column to scroll
4605 * [I] dx : amount of scroll, in pixels
4607 * RETURN:
4608 * None.
4610 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4612 COLUMN_INFO *lpColumnInfo;
4613 RECT rcOld, rcCol;
4614 POINT ptOrigin;
4615 INT nCol;
4617 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4618 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4619 rcCol = lpColumnInfo->rcHeader;
4620 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4621 rcCol.left = rcCol.right;
4623 /* adjust the other columns */
4624 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4626 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4627 lpColumnInfo->rcHeader.left += dx;
4628 lpColumnInfo->rcHeader.right += dx;
4631 /* do not update screen if not in report mode */
4632 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4634 /* if we have a focus, we must first erase the focus rect */
4635 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4637 /* Need to reset the item width when inserting a new column */
4638 infoPtr->nItemWidth += dx;
4640 LISTVIEW_UpdateScroll(infoPtr);
4641 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4643 /* scroll to cover the deleted column, and invalidate for redraw */
4644 rcOld = infoPtr->rcList;
4645 rcOld.left = ptOrigin.x + rcCol.left + dx;
4646 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4648 /* we can restore focus now */
4649 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4652 /***
4653 * DESCRIPTION:
4654 * Removes a column from the listview control.
4656 * PARAMETER(S):
4657 * [I] infoPtr : valid pointer to the listview structure
4658 * [I] nColumn : column index
4660 * RETURN:
4661 * SUCCESS : TRUE
4662 * FAILURE : FALSE
4664 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4666 RECT rcCol;
4668 TRACE("nColumn=%d\n", nColumn);
4670 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4671 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4673 /* While the MSDN specifically says that column zero should not be deleted,
4674 what actually happens is that the column itself is deleted but no items or subitems
4675 are removed.
4678 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4680 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4681 return FALSE;
4683 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4684 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4686 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4688 SUBITEM_INFO *lpSubItem, *lpDelItem;
4689 HDPA hdpaSubItems;
4690 INT nItem, nSubItem, i;
4692 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4694 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4695 nSubItem = 0;
4696 lpDelItem = 0;
4697 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4699 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4700 if (lpSubItem->iSubItem == nColumn)
4702 nSubItem = i;
4703 lpDelItem = lpSubItem;
4705 else if (lpSubItem->iSubItem > nColumn)
4707 lpSubItem->iSubItem--;
4711 /* if we found our subitem, zapp it */
4712 if (nSubItem > 0)
4714 /* free string */
4715 if (is_textW(lpDelItem->hdr.pszText))
4716 Free(lpDelItem->hdr.pszText);
4718 /* free item */
4719 Free(lpDelItem);
4721 /* free dpa memory */
4722 DPA_DeletePtr(hdpaSubItems, nSubItem);
4727 /* update the other column info */
4728 LISTVIEW_UpdateItemSize(infoPtr);
4729 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4730 LISTVIEW_InvalidateList(infoPtr);
4731 else
4732 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4734 return TRUE;
4737 /***
4738 * DESCRIPTION:
4739 * Invalidates the listview after an item's insertion or deletion.
4741 * PARAMETER(S):
4742 * [I] infoPtr : valid pointer to the listview structure
4743 * [I] nItem : item index
4744 * [I] dir : -1 if deleting, 1 if inserting
4746 * RETURN:
4747 * None
4749 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4751 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4752 INT nPerCol, nItemCol, nItemRow;
4753 RECT rcScroll;
4754 POINT Origin;
4756 /* if we don't refresh, what's the point of scrolling? */
4757 if (!is_redrawing(infoPtr)) return;
4759 assert (abs(dir) == 1);
4761 /* arrange icons if autoarrange is on */
4762 if (is_autoarrange(infoPtr))
4764 BOOL arrange = TRUE;
4765 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4766 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4767 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4770 /* scrollbars need updating */
4771 LISTVIEW_UpdateScroll(infoPtr);
4773 /* figure out the item's position */
4774 if (uView == LVS_REPORT)
4775 nPerCol = infoPtr->nItemCount + 1;
4776 else if (uView == LVS_LIST)
4777 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4778 else /* LVS_ICON, or LVS_SMALLICON */
4779 return;
4781 nItemCol = nItem / nPerCol;
4782 nItemRow = nItem % nPerCol;
4783 LISTVIEW_GetOrigin(infoPtr, &Origin);
4785 /* move the items below up a slot */
4786 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4787 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4788 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4789 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4790 OffsetRect(&rcScroll, Origin.x, Origin.y);
4791 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4792 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4794 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4795 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4796 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4799 /* report has only that column, so we're done */
4800 if (uView == LVS_REPORT) return;
4802 /* now for LISTs, we have to deal with the columns to the right */
4803 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4804 rcScroll.top = 0;
4805 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4806 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4807 OffsetRect(&rcScroll, Origin.x, Origin.y);
4808 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4809 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4810 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4813 /***
4814 * DESCRIPTION:
4815 * Removes an item from the listview control.
4817 * PARAMETER(S):
4818 * [I] infoPtr : valid pointer to the listview structure
4819 * [I] nItem : item index
4821 * RETURN:
4822 * SUCCESS : TRUE
4823 * FAILURE : FALSE
4825 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4827 LVITEMW item;
4828 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4829 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4831 TRACE("(nItem=%d)\n", nItem);
4833 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4835 /* remove selection, and focus */
4836 item.state = 0;
4837 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4838 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4840 /* send LVN_DELETEITEM notification. */
4841 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4843 /* we need to do this here, because we'll be deleting stuff */
4844 if (is_icon)
4845 LISTVIEW_InvalidateItem(infoPtr, nItem);
4847 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4849 HDPA hdpaSubItems;
4850 ITEMHDR *hdrItem;
4851 INT i;
4853 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4854 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4856 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4857 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4858 Free(hdrItem);
4860 DPA_Destroy(hdpaSubItems);
4863 if (is_icon)
4865 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4866 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4869 infoPtr->nItemCount--;
4870 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4872 /* now is the invalidation fun */
4873 if (!is_icon)
4874 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4875 return TRUE;
4879 /***
4880 * DESCRIPTION:
4881 * Callback implementation for editlabel control
4883 * PARAMETER(S):
4884 * [I] infoPtr : valid pointer to the listview structure
4885 * [I] pszText : modified text
4886 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4888 * RETURN:
4889 * SUCCESS : TRUE
4890 * FAILURE : FALSE
4892 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4894 HWND hwndSelf = infoPtr->hwndSelf;
4895 NMLVDISPINFOW dispInfo;
4896 INT editedItem = infoPtr->nEditLabelItem;
4897 BOOL bSame;
4899 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4901 infoPtr->nEditLabelItem = -1;
4903 ZeroMemory(&dispInfo, sizeof(dispInfo));
4904 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4905 dispInfo.item.iItem = editedItem;
4906 dispInfo.item.iSubItem = 0;
4907 dispInfo.item.stateMask = ~0;
4908 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4910 if (isW)
4911 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4912 else
4914 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4915 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4916 textfreeT(tmp, FALSE);
4918 if (bSame) return TRUE;
4920 /* add the text from the edit in */
4921 dispInfo.item.mask |= LVIF_TEXT;
4922 dispInfo.item.pszText = pszText;
4923 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4925 /* Do we need to update the Item Text */
4926 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4927 if (!IsWindow(hwndSelf))
4928 return FALSE;
4929 if (!pszText) return TRUE;
4931 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4933 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4934 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4935 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4937 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4938 return TRUE;
4942 ZeroMemory(&dispInfo, sizeof(dispInfo));
4943 dispInfo.item.mask = LVIF_TEXT;
4944 dispInfo.item.iItem = editedItem;
4945 dispInfo.item.iSubItem = 0;
4946 dispInfo.item.pszText = pszText;
4947 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4948 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4951 /***
4952 * DESCRIPTION:
4953 * Begin in place editing of specified list view item
4955 * PARAMETER(S):
4956 * [I] infoPtr : valid pointer to the listview structure
4957 * [I] nItem : item index
4958 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4960 * RETURN:
4961 * SUCCESS : TRUE
4962 * FAILURE : FALSE
4964 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4966 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4967 NMLVDISPINFOW dispInfo;
4968 RECT rect;
4969 HWND hwndSelf = infoPtr->hwndSelf;
4971 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4973 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4974 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4976 infoPtr->nEditLabelItem = nItem;
4978 /* Is the EditBox still there, if so remove it */
4979 if(infoPtr->hwndEdit != 0)
4981 SetFocus(infoPtr->hwndSelf);
4982 infoPtr->hwndEdit = 0;
4985 LISTVIEW_SetSelection(infoPtr, nItem);
4986 LISTVIEW_SetItemFocus(infoPtr, nItem);
4987 LISTVIEW_InvalidateItem(infoPtr, nItem);
4989 rect.left = LVIR_LABEL;
4990 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4992 ZeroMemory(&dispInfo, sizeof(dispInfo));
4993 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4994 dispInfo.item.iItem = nItem;
4995 dispInfo.item.iSubItem = 0;
4996 dispInfo.item.stateMask = ~0;
4997 dispInfo.item.pszText = szDispText;
4998 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4999 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5001 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
5002 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
5003 if (!infoPtr->hwndEdit) return 0;
5005 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5007 if (!IsWindow(hwndSelf))
5008 return 0;
5009 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5010 infoPtr->hwndEdit = 0;
5011 return 0;
5014 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5015 SetFocus(infoPtr->hwndEdit);
5016 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5017 return infoPtr->hwndEdit;
5021 /***
5022 * DESCRIPTION:
5023 * Ensures the specified item is visible, scrolling into view if necessary.
5025 * PARAMETER(S):
5026 * [I] infoPtr : valid pointer to the listview structure
5027 * [I] nItem : item index
5028 * [I] bPartial : partially or entirely visible
5030 * RETURN:
5031 * SUCCESS : TRUE
5032 * FAILURE : FALSE
5034 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5036 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5037 INT nScrollPosHeight = 0;
5038 INT nScrollPosWidth = 0;
5039 INT nHorzAdjust = 0;
5040 INT nVertAdjust = 0;
5041 INT nHorzDiff = 0;
5042 INT nVertDiff = 0;
5043 RECT rcItem, rcTemp;
5045 rcItem.left = LVIR_BOUNDS;
5046 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5048 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5050 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5052 /* scroll left/right, but in LVS_REPORT mode */
5053 if (uView == LVS_LIST)
5054 nScrollPosWidth = infoPtr->nItemWidth;
5055 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5056 nScrollPosWidth = 1;
5058 if (rcItem.left < infoPtr->rcList.left)
5060 nHorzAdjust = -1;
5061 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5063 else
5065 nHorzAdjust = 1;
5066 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5070 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5072 /* scroll up/down, but not in LVS_LIST mode */
5073 if (uView == LVS_REPORT)
5074 nScrollPosHeight = infoPtr->nItemHeight;
5075 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5076 nScrollPosHeight = 1;
5078 if (rcItem.top < infoPtr->rcList.top)
5080 nVertAdjust = -1;
5081 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5083 else
5085 nVertAdjust = 1;
5086 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5090 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5092 if (nScrollPosWidth)
5094 INT diff = nHorzDiff / nScrollPosWidth;
5095 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5096 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5099 if (nScrollPosHeight)
5101 INT diff = nVertDiff / nScrollPosHeight;
5102 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5103 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5106 return TRUE;
5109 /***
5110 * DESCRIPTION:
5111 * Searches for an item with specific characteristics.
5113 * PARAMETER(S):
5114 * [I] hwnd : window handle
5115 * [I] nStart : base item index
5116 * [I] lpFindInfo : item information to look for
5118 * RETURN:
5119 * SUCCESS : index of item
5120 * FAILURE : -1
5122 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5123 const LVFINDINFOW *lpFindInfo)
5125 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5126 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5127 BOOL bWrap = FALSE, bNearest = FALSE;
5128 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5129 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5130 POINT Position, Destination;
5131 LVITEMW lvItem;
5133 /* Search in virtual listviews should be done by application, not by
5134 listview control, so we just send LVN_ODFINDITEMW and return the result */
5135 if (infoPtr->dwStyle & LVS_OWNERDATA)
5137 NMLVFINDITEMW nmlv;
5139 nmlv.iStart = nStart;
5140 nmlv.lvfi = *lpFindInfo;
5141 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5144 if (!lpFindInfo || nItem < 0) return -1;
5146 lvItem.mask = 0;
5147 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5149 lvItem.mask |= LVIF_TEXT;
5150 lvItem.pszText = szDispText;
5151 lvItem.cchTextMax = DISP_TEXT_SIZE;
5154 if (lpFindInfo->flags & LVFI_WRAP)
5155 bWrap = TRUE;
5157 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5158 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5160 POINT Origin;
5161 RECT rcArea;
5163 LISTVIEW_GetOrigin(infoPtr, &Origin);
5164 Destination.x = lpFindInfo->pt.x - Origin.x;
5165 Destination.y = lpFindInfo->pt.y - Origin.y;
5166 switch(lpFindInfo->vkDirection)
5168 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5169 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5170 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5171 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5172 case VK_HOME: Destination.x = Destination.y = 0; break;
5173 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5174 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5175 case VK_END:
5176 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5177 Destination.x = rcArea.right;
5178 Destination.y = rcArea.bottom;
5179 break;
5180 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5182 bNearest = TRUE;
5184 else Destination.x = Destination.y = 0;
5186 /* if LVFI_PARAM is specified, all other flags are ignored */
5187 if (lpFindInfo->flags & LVFI_PARAM)
5189 lvItem.mask |= LVIF_PARAM;
5190 bNearest = FALSE;
5191 lvItem.mask &= ~LVIF_TEXT;
5194 again:
5195 for (; nItem < nLast; nItem++)
5197 lvItem.iItem = nItem;
5198 lvItem.iSubItem = 0;
5199 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5201 if (lvItem.mask & LVIF_PARAM)
5203 if (lpFindInfo->lParam == lvItem.lParam)
5204 return nItem;
5205 else
5206 continue;
5209 if (lvItem.mask & LVIF_TEXT)
5211 if (lpFindInfo->flags & LVFI_PARTIAL)
5213 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5215 else
5217 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5221 if (!bNearest) return nItem;
5223 /* This is very inefficient. To do a good job here,
5224 * we need a sorted array of (x,y) item positions */
5225 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5227 /* compute the distance^2 to the destination */
5228 xdist = Destination.x - Position.x;
5229 ydist = Destination.y - Position.y;
5230 dist = xdist * xdist + ydist * ydist;
5232 /* remember the distance, and item if it's closer */
5233 if (dist < mindist)
5235 mindist = dist;
5236 nNearestItem = nItem;
5240 if (bWrap)
5242 nItem = 0;
5243 nLast = min(nStart + 1, infoPtr->nItemCount);
5244 bWrap = FALSE;
5245 goto again;
5248 return nNearestItem;
5251 /***
5252 * DESCRIPTION:
5253 * Searches for an item with specific characteristics.
5255 * PARAMETER(S):
5256 * [I] hwnd : window handle
5257 * [I] nStart : base item index
5258 * [I] lpFindInfo : item information to look for
5260 * RETURN:
5261 * SUCCESS : index of item
5262 * FAILURE : -1
5264 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5265 const LVFINDINFOA *lpFindInfo)
5267 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5268 LVFINDINFOW fiw;
5269 INT res;
5270 LPWSTR strW = NULL;
5272 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5273 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5274 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5275 textfreeT(strW, FALSE);
5276 return res;
5279 /***
5280 * DESCRIPTION:
5281 * Retrieves the background image of the listview control.
5283 * PARAMETER(S):
5284 * [I] infoPtr : valid pointer to the listview structure
5285 * [O] lpBkImage : background image attributes
5287 * RETURN:
5288 * SUCCESS : TRUE
5289 * FAILURE : FALSE
5291 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5292 /* { */
5293 /* FIXME (listview, "empty stub!\n"); */
5294 /* return FALSE; */
5295 /* } */
5297 /***
5298 * DESCRIPTION:
5299 * Retrieves column attributes.
5301 * PARAMETER(S):
5302 * [I] infoPtr : valid pointer to the listview structure
5303 * [I] nColumn : column index
5304 * [IO] lpColumn : column information
5305 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5306 * otherwise it is in fact a LPLVCOLUMNA
5308 * RETURN:
5309 * SUCCESS : TRUE
5310 * FAILURE : FALSE
5312 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5314 COLUMN_INFO *lpColumnInfo;
5315 HDITEMW hdi;
5317 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5318 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5320 /* initialize memory */
5321 ZeroMemory(&hdi, sizeof(hdi));
5323 if (lpColumn->mask & LVCF_TEXT)
5325 hdi.mask |= HDI_TEXT;
5326 hdi.pszText = lpColumn->pszText;
5327 hdi.cchTextMax = lpColumn->cchTextMax;
5330 if (lpColumn->mask & LVCF_IMAGE)
5331 hdi.mask |= HDI_IMAGE;
5333 if (lpColumn->mask & LVCF_ORDER)
5334 hdi.mask |= HDI_ORDER;
5336 if (lpColumn->mask & LVCF_SUBITEM)
5337 hdi.mask |= HDI_LPARAM;
5339 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5341 if (lpColumn->mask & LVCF_FMT)
5342 lpColumn->fmt = lpColumnInfo->fmt;
5344 if (lpColumn->mask & LVCF_WIDTH)
5345 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5347 if (lpColumn->mask & LVCF_IMAGE)
5348 lpColumn->iImage = hdi.iImage;
5350 if (lpColumn->mask & LVCF_ORDER)
5351 lpColumn->iOrder = hdi.iOrder;
5353 if (lpColumn->mask & LVCF_SUBITEM)
5354 lpColumn->iSubItem = hdi.lParam;
5356 return TRUE;
5360 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5362 INT i;
5364 if (!lpiArray)
5365 return FALSE;
5367 /* FIXME: little hack */
5368 for (i = 0; i < iCount; i++)
5369 lpiArray[i] = i;
5371 return TRUE;
5374 /***
5375 * DESCRIPTION:
5376 * Retrieves the column width.
5378 * PARAMETER(S):
5379 * [I] infoPtr : valid pointer to the listview structure
5380 * [I] int : column index
5382 * RETURN:
5383 * SUCCESS : column width
5384 * FAILURE : zero
5386 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5388 INT nColumnWidth = 0;
5389 HDITEMW hdItem;
5391 TRACE("nColumn=%d\n", nColumn);
5393 /* we have a 'column' in LIST and REPORT mode only */
5394 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5396 case LVS_LIST:
5397 nColumnWidth = infoPtr->nItemWidth;
5398 break;
5399 case LVS_REPORT:
5400 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5401 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5402 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5404 * TODO: should we do the same in LVM_GETCOLUMN?
5406 hdItem.mask = HDI_WIDTH;
5407 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5409 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5410 return 0;
5412 nColumnWidth = hdItem.cxy;
5413 break;
5416 TRACE("nColumnWidth=%d\n", nColumnWidth);
5417 return nColumnWidth;
5420 /***
5421 * DESCRIPTION:
5422 * In list or report display mode, retrieves the number of items that can fit
5423 * vertically in the visible area. In icon or small icon display mode,
5424 * retrieves the total number of visible items.
5426 * PARAMETER(S):
5427 * [I] infoPtr : valid pointer to the listview structure
5429 * RETURN:
5430 * Number of fully visible items.
5432 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5434 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5436 case LVS_ICON:
5437 case LVS_SMALLICON:
5438 return infoPtr->nItemCount;
5439 case LVS_REPORT:
5440 return LISTVIEW_GetCountPerColumn(infoPtr);
5441 case LVS_LIST:
5442 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5444 assert(FALSE);
5445 return 0;
5448 /***
5449 * DESCRIPTION:
5450 * Retrieves an image list handle.
5452 * PARAMETER(S):
5453 * [I] infoPtr : valid pointer to the listview structure
5454 * [I] nImageList : image list identifier
5456 * RETURN:
5457 * SUCCESS : image list handle
5458 * FAILURE : NULL
5460 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5462 switch (nImageList)
5464 case LVSIL_NORMAL: return infoPtr->himlNormal;
5465 case LVSIL_SMALL: return infoPtr->himlSmall;
5466 case LVSIL_STATE: return infoPtr->himlState;
5468 return NULL;
5471 /* LISTVIEW_GetISearchString */
5473 /***
5474 * DESCRIPTION:
5475 * Retrieves item attributes.
5477 * PARAMETER(S):
5478 * [I] hwnd : window handle
5479 * [IO] lpLVItem : item info
5480 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5481 * if FALSE, then lpLVItem is a LPLVITEMA.
5483 * NOTE:
5484 * This is the internal 'GetItem' interface -- it tries to
5485 * be smart and avoid text copies, if possible, by modifying
5486 * lpLVItem->pszText to point to the text string. Please note
5487 * that this is not always possible (e.g. OWNERDATA), so on
5488 * entry you *must* supply valid values for pszText, and cchTextMax.
5489 * The only difference to the documented interface is that upon
5490 * return, you should use *only* the lpLVItem->pszText, rather than
5491 * the buffer pointer you provided on input. Most code already does
5492 * that, so it's not a problem.
5493 * For the two cases when the text must be copied (that is,
5494 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5496 * RETURN:
5497 * SUCCESS : TRUE
5498 * FAILURE : FALSE
5500 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5502 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5503 NMLVDISPINFOW dispInfo;
5504 ITEM_INFO *lpItem;
5505 ITEMHDR* pItemHdr;
5506 HDPA hdpaSubItems;
5507 INT isubitem;
5509 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5511 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5512 return FALSE;
5514 if (lpLVItem->mask == 0) return TRUE;
5516 /* make a local copy */
5517 isubitem = lpLVItem->iSubItem;
5519 /* a quick optimization if all we're asked is the focus state
5520 * these queries are worth optimising since they are common,
5521 * and can be answered in constant time, without the heavy accesses */
5522 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5523 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5525 lpLVItem->state = 0;
5526 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5527 lpLVItem->state |= LVIS_FOCUSED;
5528 return TRUE;
5531 ZeroMemory(&dispInfo, sizeof(dispInfo));
5533 /* if the app stores all the data, handle it separately */
5534 if (infoPtr->dwStyle & LVS_OWNERDATA)
5536 dispInfo.item.state = 0;
5538 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5539 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5541 /* NOTE: copy only fields which we _know_ are initialized, some apps
5542 * depend on the uninitialized fields being 0 */
5543 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5544 dispInfo.item.iItem = lpLVItem->iItem;
5545 dispInfo.item.iSubItem = isubitem;
5546 if (lpLVItem->mask & LVIF_TEXT)
5548 dispInfo.item.pszText = lpLVItem->pszText;
5549 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5551 if (lpLVItem->mask & LVIF_STATE)
5552 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5553 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5554 dispInfo.item.stateMask = lpLVItem->stateMask;
5555 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5557 /* full size structure expected - _WIN32IE >= 0x560 */
5558 *lpLVItem = dispInfo.item;
5560 else if (lpLVItem->mask & LVIF_INDENT)
5562 /* indent member expected - _WIN32IE >= 0x300 */
5563 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5565 else
5567 /* minimal structure expected */
5568 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5570 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5573 /* make sure lParam is zeroed out */
5574 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5576 /* we store only a little state, so if we're not asked, we're done */
5577 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5579 /* if focus is handled by us, report it */
5580 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5582 lpLVItem->state &= ~LVIS_FOCUSED;
5583 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5584 lpLVItem->state |= LVIS_FOCUSED;
5587 /* and do the same for selection, if we handle it */
5588 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5590 lpLVItem->state &= ~LVIS_SELECTED;
5591 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5592 lpLVItem->state |= LVIS_SELECTED;
5595 return TRUE;
5598 /* find the item and subitem structures before we proceed */
5599 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5600 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5601 assert (lpItem);
5603 if (isubitem)
5605 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5606 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5607 if (!lpSubItem)
5609 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5610 isubitem = 0;
5613 else
5614 pItemHdr = &lpItem->hdr;
5616 /* Do we need to query the state from the app? */
5617 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5619 dispInfo.item.mask |= LVIF_STATE;
5620 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5623 /* Do we need to enquire about the image? */
5624 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5625 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5627 dispInfo.item.mask |= LVIF_IMAGE;
5628 dispInfo.item.iImage = I_IMAGECALLBACK;
5631 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5632 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5634 dispInfo.item.mask |= LVIF_TEXT;
5635 dispInfo.item.pszText = lpLVItem->pszText;
5636 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5637 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5638 *dispInfo.item.pszText = '\0';
5641 /* If we don't have all the requested info, query the application */
5642 if (dispInfo.item.mask != 0)
5644 dispInfo.item.iItem = lpLVItem->iItem;
5645 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5646 dispInfo.item.lParam = lpItem->lParam;
5647 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5648 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5651 /* we should not store values for subitems */
5652 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5654 /* Now, handle the iImage field */
5655 if (dispInfo.item.mask & LVIF_IMAGE)
5657 lpLVItem->iImage = dispInfo.item.iImage;
5658 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5659 pItemHdr->iImage = dispInfo.item.iImage;
5661 else if (lpLVItem->mask & LVIF_IMAGE)
5663 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5664 lpLVItem->iImage = pItemHdr->iImage;
5665 else
5666 lpLVItem->iImage = 0;
5669 /* The pszText field */
5670 if (dispInfo.item.mask & LVIF_TEXT)
5672 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5673 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5675 lpLVItem->pszText = dispInfo.item.pszText;
5677 else if (lpLVItem->mask & LVIF_TEXT)
5679 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5680 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5683 /* Next is the lParam field */
5684 if (dispInfo.item.mask & LVIF_PARAM)
5686 lpLVItem->lParam = dispInfo.item.lParam;
5687 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5688 lpItem->lParam = dispInfo.item.lParam;
5690 else if (lpLVItem->mask & LVIF_PARAM)
5691 lpLVItem->lParam = lpItem->lParam;
5693 /* if this is a subitem, we're done */
5694 if (isubitem) return TRUE;
5696 /* ... the state field (this one is different due to uCallbackmask) */
5697 if (lpLVItem->mask & LVIF_STATE)
5699 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5700 if (dispInfo.item.mask & LVIF_STATE)
5702 lpLVItem->state &= ~dispInfo.item.stateMask;
5703 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5705 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5707 lpLVItem->state &= ~LVIS_FOCUSED;
5708 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5709 lpLVItem->state |= LVIS_FOCUSED;
5711 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5713 lpLVItem->state &= ~LVIS_SELECTED;
5714 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5715 lpLVItem->state |= LVIS_SELECTED;
5719 /* and last, but not least, the indent field */
5720 if (lpLVItem->mask & LVIF_INDENT)
5721 lpLVItem->iIndent = lpItem->iIndent;
5723 return TRUE;
5726 /***
5727 * DESCRIPTION:
5728 * Retrieves item attributes.
5730 * PARAMETER(S):
5731 * [I] hwnd : window handle
5732 * [IO] lpLVItem : item info
5733 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5734 * if FALSE, then lpLVItem is a LPLVITEMA.
5736 * NOTE:
5737 * This is the external 'GetItem' interface -- it properly copies
5738 * the text in the provided buffer.
5740 * RETURN:
5741 * SUCCESS : TRUE
5742 * FAILURE : FALSE
5744 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5746 LPWSTR pszText;
5747 BOOL bResult;
5749 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5750 return FALSE;
5752 pszText = lpLVItem->pszText;
5753 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5754 if (bResult && lpLVItem->pszText != pszText)
5755 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5756 lpLVItem->pszText = pszText;
5758 return bResult;
5762 /***
5763 * DESCRIPTION:
5764 * Retrieves the position (upper-left) of the listview control item.
5765 * Note that for LVS_ICON style, the upper-left is that of the icon
5766 * and not the bounding box.
5768 * PARAMETER(S):
5769 * [I] infoPtr : valid pointer to the listview structure
5770 * [I] nItem : item index
5771 * [O] lpptPosition : coordinate information
5773 * RETURN:
5774 * SUCCESS : TRUE
5775 * FAILURE : FALSE
5777 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5779 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5780 POINT Origin;
5782 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5784 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5786 LISTVIEW_GetOrigin(infoPtr, &Origin);
5787 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5789 if (uView == LVS_ICON)
5791 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5792 lpptPosition->y += ICON_TOP_PADDING;
5794 lpptPosition->x += Origin.x;
5795 lpptPosition->y += Origin.y;
5797 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5798 return TRUE;
5802 /***
5803 * DESCRIPTION:
5804 * Retrieves the bounding rectangle for a listview control item.
5806 * PARAMETER(S):
5807 * [I] infoPtr : valid pointer to the listview structure
5808 * [I] nItem : item index
5809 * [IO] lprc : bounding rectangle coordinates
5810 * lprc->left specifies the portion of the item for which the bounding
5811 * rectangle will be retrieved.
5813 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5814 * including the icon and label.
5816 * * For LVS_ICON
5817 * * Experiment shows that native control returns:
5818 * * width = min (48, length of text line)
5819 * * .left = position.x - (width - iconsize.cx)/2
5820 * * .right = .left + width
5821 * * height = #lines of text * ntmHeight + icon height + 8
5822 * * .top = position.y - 2
5823 * * .bottom = .top + height
5824 * * separation between items .y = itemSpacing.cy - height
5825 * * .x = itemSpacing.cx - width
5826 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5828 * * For LVS_ICON
5829 * * Experiment shows that native control returns:
5830 * * width = iconSize.cx + 16
5831 * * .left = position.x - (width - iconsize.cx)/2
5832 * * .right = .left + width
5833 * * height = iconSize.cy + 4
5834 * * .top = position.y - 2
5835 * * .bottom = .top + height
5836 * * separation between items .y = itemSpacing.cy - height
5837 * * .x = itemSpacing.cx - width
5838 * LVIR_LABEL Returns the bounding rectangle of the item text.
5840 * * For LVS_ICON
5841 * * Experiment shows that native control returns:
5842 * * width = text length
5843 * * .left = position.x - width/2
5844 * * .right = .left + width
5845 * * height = ntmH * linecount + 2
5846 * * .top = position.y + iconSize.cy + 6
5847 * * .bottom = .top + height
5848 * * separation between items .y = itemSpacing.cy - height
5849 * * .x = itemSpacing.cx - width
5850 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5851 * rectangles, but excludes columns in report view.
5853 * RETURN:
5854 * SUCCESS : TRUE
5855 * FAILURE : FALSE
5857 * NOTES
5858 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5859 * upon whether the window has the focus currently and on whether the item
5860 * is the one with the focus. Ensure that the control's record of which
5861 * item has the focus agrees with the items' records.
5863 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5865 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5866 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5867 BOOL doLabel = TRUE, oversizedBox = FALSE;
5868 POINT Position, Origin;
5869 LVITEMW lvItem;
5871 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5873 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5875 LISTVIEW_GetOrigin(infoPtr, &Origin);
5876 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5878 /* Be smart and try to figure out the minimum we have to do */
5879 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5880 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5881 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5882 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5883 oversizedBox = TRUE;
5885 /* get what we need from the item before hand, so we make
5886 * only one request. This can speed up things, if data
5887 * is stored on the app side */
5888 lvItem.mask = 0;
5889 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5890 if (doLabel) lvItem.mask |= LVIF_TEXT;
5891 lvItem.iItem = nItem;
5892 lvItem.iSubItem = 0;
5893 lvItem.pszText = szDispText;
5894 lvItem.cchTextMax = DISP_TEXT_SIZE;
5895 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5896 /* we got the state already up, simulate it here, to avoid a reget */
5897 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5899 lvItem.mask |= LVIF_STATE;
5900 lvItem.stateMask = LVIS_FOCUSED;
5901 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5904 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5905 lprc->left = LVIR_BOUNDS;
5906 switch(lprc->left)
5908 case LVIR_ICON:
5909 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5910 break;
5912 case LVIR_LABEL:
5913 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5914 break;
5916 case LVIR_BOUNDS:
5917 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5918 break;
5920 case LVIR_SELECTBOUNDS:
5921 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5922 break;
5924 default:
5925 WARN("Unknown value: %d\n", lprc->left);
5926 return FALSE;
5929 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5931 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5933 return TRUE;
5936 /***
5937 * DESCRIPTION:
5938 * Retrieves the spacing between listview control items.
5940 * PARAMETER(S):
5941 * [I] infoPtr : valid pointer to the listview structure
5942 * [IO] lprc : rectangle to receive the output
5943 * on input, lprc->top = nSubItem
5944 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5946 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5947 * not only those of the first column.
5948 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5950 * RETURN:
5951 * TRUE: success
5952 * FALSE: failure
5954 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5956 POINT Position;
5957 LVITEMW lvItem;
5958 INT nColumn;
5960 if (!lprc) return FALSE;
5962 nColumn = lprc->top;
5964 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5965 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5966 if (lprc->top == 0)
5967 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5969 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5971 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5973 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5975 lvItem.mask = 0;
5976 lvItem.iItem = nItem;
5977 lvItem.iSubItem = nColumn;
5979 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5980 switch(lprc->left)
5982 case LVIR_ICON:
5983 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5984 break;
5986 case LVIR_LABEL:
5987 case LVIR_BOUNDS:
5988 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5989 break;
5991 default:
5992 ERR("Unknown bounds=%d\n", lprc->left);
5993 return FALSE;
5996 OffsetRect(lprc, Position.x, Position.y);
5997 return TRUE;
6001 /***
6002 * DESCRIPTION:
6003 * Retrieves the width of a label.
6005 * PARAMETER(S):
6006 * [I] infoPtr : valid pointer to the listview structure
6008 * RETURN:
6009 * SUCCESS : string width (in pixels)
6010 * FAILURE : zero
6012 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6014 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6015 LVITEMW lvItem;
6017 TRACE("(nItem=%d)\n", nItem);
6019 lvItem.mask = LVIF_TEXT;
6020 lvItem.iItem = nItem;
6021 lvItem.iSubItem = 0;
6022 lvItem.pszText = szDispText;
6023 lvItem.cchTextMax = DISP_TEXT_SIZE;
6024 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6026 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6029 /***
6030 * DESCRIPTION:
6031 * Retrieves the spacing between listview control items.
6033 * PARAMETER(S):
6034 * [I] infoPtr : valid pointer to the listview structure
6035 * [I] bSmall : flag for small or large icon
6037 * RETURN:
6038 * Horizontal + vertical spacing
6040 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6042 LONG lResult;
6044 if (!bSmall)
6046 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6048 else
6050 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6051 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6052 else
6053 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6055 return lResult;
6058 /***
6059 * DESCRIPTION:
6060 * Retrieves the state of a listview control item.
6062 * PARAMETER(S):
6063 * [I] infoPtr : valid pointer to the listview structure
6064 * [I] nItem : item index
6065 * [I] uMask : state mask
6067 * RETURN:
6068 * State specified by the mask.
6070 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6072 LVITEMW lvItem;
6074 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6076 lvItem.iItem = nItem;
6077 lvItem.iSubItem = 0;
6078 lvItem.mask = LVIF_STATE;
6079 lvItem.stateMask = uMask;
6080 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6082 return lvItem.state & uMask;
6085 /***
6086 * DESCRIPTION:
6087 * Retrieves the text of a listview control item or subitem.
6089 * PARAMETER(S):
6090 * [I] hwnd : window handle
6091 * [I] nItem : item index
6092 * [IO] lpLVItem : item information
6093 * [I] isW : TRUE if lpLVItem is Unicode
6095 * RETURN:
6096 * SUCCESS : string length
6097 * FAILURE : 0
6099 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6101 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6103 lpLVItem->mask = LVIF_TEXT;
6104 lpLVItem->iItem = nItem;
6105 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6107 return textlenT(lpLVItem->pszText, isW);
6110 /***
6111 * DESCRIPTION:
6112 * Searches for an item based on properties + relationships.
6114 * PARAMETER(S):
6115 * [I] infoPtr : valid pointer to the listview structure
6116 * [I] nItem : item index
6117 * [I] uFlags : relationship flag
6119 * RETURN:
6120 * SUCCESS : item index
6121 * FAILURE : -1
6123 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6125 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6126 UINT uMask = 0;
6127 LVFINDINFOW lvFindInfo;
6128 INT nCountPerColumn;
6129 INT nCountPerRow;
6130 INT i;
6132 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6133 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6135 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6137 if (uFlags & LVNI_CUT)
6138 uMask |= LVIS_CUT;
6140 if (uFlags & LVNI_DROPHILITED)
6141 uMask |= LVIS_DROPHILITED;
6143 if (uFlags & LVNI_FOCUSED)
6144 uMask |= LVIS_FOCUSED;
6146 if (uFlags & LVNI_SELECTED)
6147 uMask |= LVIS_SELECTED;
6149 /* if we're asked for the focused item, that's only one,
6150 * so it's worth optimizing */
6151 if (uFlags & LVNI_FOCUSED)
6153 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6154 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6157 if (uFlags & LVNI_ABOVE)
6159 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6161 while (nItem >= 0)
6163 nItem--;
6164 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6165 return nItem;
6168 else
6170 /* Special case for autoarrange - move 'til the top of a list */
6171 if (is_autoarrange(infoPtr))
6173 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6174 while (nItem - nCountPerRow >= 0)
6176 nItem -= nCountPerRow;
6177 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6178 return nItem;
6180 return -1;
6182 lvFindInfo.flags = LVFI_NEARESTXY;
6183 lvFindInfo.vkDirection = VK_UP;
6184 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6185 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6187 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6188 return nItem;
6192 else if (uFlags & LVNI_BELOW)
6194 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6196 while (nItem < infoPtr->nItemCount)
6198 nItem++;
6199 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6200 return nItem;
6203 else
6205 /* Special case for autoarrange - move 'til the bottom of a list */
6206 if (is_autoarrange(infoPtr))
6208 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6209 while (nItem + nCountPerRow < infoPtr->nItemCount )
6211 nItem += nCountPerRow;
6212 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6213 return nItem;
6215 return -1;
6217 lvFindInfo.flags = LVFI_NEARESTXY;
6218 lvFindInfo.vkDirection = VK_DOWN;
6219 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6220 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6222 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6223 return nItem;
6227 else if (uFlags & LVNI_TOLEFT)
6229 if (uView == LVS_LIST)
6231 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6232 while (nItem - nCountPerColumn >= 0)
6234 nItem -= nCountPerColumn;
6235 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6236 return nItem;
6239 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6241 /* Special case for autoarrange - move 'ti the beginning of a row */
6242 if (is_autoarrange(infoPtr))
6244 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6245 while (nItem % nCountPerRow > 0)
6247 nItem --;
6248 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6249 return nItem;
6251 return -1;
6253 lvFindInfo.flags = LVFI_NEARESTXY;
6254 lvFindInfo.vkDirection = VK_LEFT;
6255 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6256 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6258 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6259 return nItem;
6263 else if (uFlags & LVNI_TORIGHT)
6265 if (uView == LVS_LIST)
6267 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6268 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6270 nItem += nCountPerColumn;
6271 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6272 return nItem;
6275 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6277 /* Special case for autoarrange - move 'til the end of a row */
6278 if (is_autoarrange(infoPtr))
6280 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6281 while (nItem % nCountPerRow < nCountPerRow - 1 )
6283 nItem ++;
6284 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6285 return nItem;
6287 return -1;
6289 lvFindInfo.flags = LVFI_NEARESTXY;
6290 lvFindInfo.vkDirection = VK_RIGHT;
6291 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6292 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6294 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6295 return nItem;
6299 else
6301 nItem++;
6303 /* search by index */
6304 for (i = nItem; i < infoPtr->nItemCount; i++)
6306 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6307 return i;
6311 return -1;
6314 /* LISTVIEW_GetNumberOfWorkAreas */
6316 /***
6317 * DESCRIPTION:
6318 * Retrieves the origin coordinates when in icon or small icon display mode.
6320 * PARAMETER(S):
6321 * [I] infoPtr : valid pointer to the listview structure
6322 * [O] lpptOrigin : coordinate information
6324 * RETURN:
6325 * None.
6327 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6329 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6330 INT nHorzPos = 0, nVertPos = 0;
6331 SCROLLINFO scrollInfo;
6333 scrollInfo.cbSize = sizeof(SCROLLINFO);
6334 scrollInfo.fMask = SIF_POS;
6336 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6337 nHorzPos = scrollInfo.nPos;
6338 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6339 nVertPos = scrollInfo.nPos;
6341 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6343 lpptOrigin->x = infoPtr->rcList.left;
6344 lpptOrigin->y = infoPtr->rcList.top;
6345 if (uView == LVS_LIST)
6346 nHorzPos *= infoPtr->nItemWidth;
6347 else if (uView == LVS_REPORT)
6348 nVertPos *= infoPtr->nItemHeight;
6350 lpptOrigin->x -= nHorzPos;
6351 lpptOrigin->y -= nVertPos;
6353 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6356 /***
6357 * DESCRIPTION:
6358 * Retrieves the width of a string.
6360 * PARAMETER(S):
6361 * [I] hwnd : window handle
6362 * [I] lpszText : text string to process
6363 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6365 * RETURN:
6366 * SUCCESS : string width (in pixels)
6367 * FAILURE : zero
6369 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6371 SIZE stringSize;
6373 stringSize.cx = 0;
6374 if (is_textT(lpszText, isW))
6376 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6377 HDC hdc = GetDC(infoPtr->hwndSelf);
6378 HFONT hOldFont = SelectObject(hdc, hFont);
6380 if (isW)
6381 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6382 else
6383 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6384 SelectObject(hdc, hOldFont);
6385 ReleaseDC(infoPtr->hwndSelf, hdc);
6387 return stringSize.cx;
6390 /***
6391 * DESCRIPTION:
6392 * Determines which listview item is located at the specified position.
6394 * PARAMETER(S):
6395 * [I] infoPtr : valid pointer to the listview structure
6396 * [IO] lpht : hit test information
6397 * [I] subitem : fill out iSubItem.
6398 * [I] select : return the index only if the hit selects the item
6400 * NOTE:
6401 * (mm 20001022): We must not allow iSubItem to be touched, for
6402 * an app might pass only a structure with space up to iItem!
6403 * (MS Office 97 does that for instance in the file open dialog)
6405 * RETURN:
6406 * SUCCESS : item index
6407 * FAILURE : -1
6409 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6411 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6412 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6413 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6414 POINT Origin, Position, opt;
6415 LVITEMW lvItem;
6416 ITERATOR i;
6417 INT iItem;
6419 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6421 lpht->flags = 0;
6422 lpht->iItem = -1;
6423 if (subitem) lpht->iSubItem = 0;
6425 if (infoPtr->rcList.left > lpht->pt.x)
6426 lpht->flags |= LVHT_TOLEFT;
6427 else if (infoPtr->rcList.right < lpht->pt.x)
6428 lpht->flags |= LVHT_TORIGHT;
6430 if (infoPtr->rcList.top > lpht->pt.y)
6431 lpht->flags |= LVHT_ABOVE;
6432 else if (infoPtr->rcList.bottom < lpht->pt.y)
6433 lpht->flags |= LVHT_BELOW;
6435 TRACE("lpht->flags=0x%x\n", lpht->flags);
6436 if (lpht->flags) return -1;
6438 lpht->flags |= LVHT_NOWHERE;
6440 LISTVIEW_GetOrigin(infoPtr, &Origin);
6442 /* first deal with the large items */
6443 rcSearch.left = lpht->pt.x;
6444 rcSearch.top = lpht->pt.y;
6445 rcSearch.right = rcSearch.left + 1;
6446 rcSearch.bottom = rcSearch.top + 1;
6448 iterator_frameditems(&i, infoPtr, &rcSearch);
6449 iterator_next(&i); /* go to first item in the sequence */
6450 iItem = i.nItem;
6451 iterator_destroy(&i);
6453 TRACE("lpht->iItem=%d\n", iItem);
6454 if (iItem == -1) return -1;
6456 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6457 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6458 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6459 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6460 lvItem.iItem = iItem;
6461 lvItem.iSubItem = 0;
6462 lvItem.pszText = szDispText;
6463 lvItem.cchTextMax = DISP_TEXT_SIZE;
6464 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6465 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6467 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6468 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6469 opt.x = lpht->pt.x - Position.x - Origin.x;
6470 opt.y = lpht->pt.y - Position.y - Origin.y;
6472 if (uView == LVS_REPORT)
6473 rcBounds = rcBox;
6474 else
6476 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6477 UnionRect(&rcBounds, &rcBounds, &rcState);
6479 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6480 if (!PtInRect(&rcBounds, opt)) return -1;
6482 if (PtInRect(&rcIcon, opt))
6483 lpht->flags |= LVHT_ONITEMICON;
6484 else if (PtInRect(&rcLabel, opt))
6485 lpht->flags |= LVHT_ONITEMLABEL;
6486 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6487 lpht->flags |= LVHT_ONITEMSTATEICON;
6488 if (lpht->flags & LVHT_ONITEM)
6489 lpht->flags &= ~LVHT_NOWHERE;
6491 TRACE("lpht->flags=0x%x\n", lpht->flags);
6492 if (uView == LVS_REPORT && subitem)
6494 INT j;
6496 rcBounds.right = rcBounds.left;
6497 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6499 rcBounds.left = rcBounds.right;
6500 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6501 if (PtInRect(&rcBounds, opt))
6503 lpht->iSubItem = j;
6504 break;
6509 if (select && !(uView == LVS_REPORT &&
6510 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6511 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6513 if (uView == LVS_REPORT)
6515 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6516 UnionRect(&rcBounds, &rcBounds, &rcState);
6518 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6520 return lpht->iItem = iItem;
6524 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6525 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6526 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6527 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6528 their own sort proc. when sending LVM_SORTITEMS.
6530 /* Platform SDK:
6531 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6533 LVS_SORTXXX must be specified,
6534 LVS_OWNERDRAW is not set,
6535 <item>.pszText is not LPSTR_TEXTCALLBACK.
6537 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6538 are sorted based on item text..."
6540 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6542 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
6543 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
6544 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6546 /* if we're sorting descending, negate the return value */
6547 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6550 /***
6551 * DESCRIPTION:
6552 * Inserts a new item in the listview control.
6554 * PARAMETER(S):
6555 * [I] infoPtr : valid pointer to the listview structure
6556 * [I] lpLVItem : item information
6557 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6559 * RETURN:
6560 * SUCCESS : new item index
6561 * FAILURE : -1
6563 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6565 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6566 INT nItem;
6567 HDPA hdpaSubItems;
6568 NMLISTVIEW nmlv;
6569 ITEM_INFO *lpItem;
6570 BOOL is_sorted, has_changed;
6571 LVITEMW item;
6572 HWND hwndSelf = infoPtr->hwndSelf;
6574 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6576 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6578 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6579 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6581 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6583 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6585 /* insert item in listview control data structure */
6586 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6587 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6589 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6590 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6592 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6594 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6595 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6596 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6597 if (nItem == -1) goto fail;
6598 infoPtr->nItemCount++;
6600 /* shift indices first so they don't get tangled */
6601 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6603 /* set the item attributes */
6604 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6606 /* full size structure expected - _WIN32IE >= 0x560 */
6607 item = *lpLVItem;
6609 else if (lpLVItem->mask & LVIF_INDENT)
6611 /* indent member expected - _WIN32IE >= 0x300 */
6612 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6614 else
6616 /* minimal structure expected */
6617 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6619 item.iItem = nItem;
6620 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6622 item.mask |= LVIF_STATE;
6623 item.stateMask |= LVIS_STATEIMAGEMASK;
6624 item.state &= ~LVIS_STATEIMAGEMASK;
6625 item.state |= INDEXTOSTATEIMAGEMASK(1);
6627 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6629 /* if we're sorted, sort the list, and update the index */
6630 if (is_sorted)
6632 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6633 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6634 assert(nItem != -1);
6637 /* make room for the position, if we are in the right mode */
6638 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6640 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6641 goto undo;
6642 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6644 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6645 goto undo;
6649 /* send LVN_INSERTITEM notification */
6650 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6651 nmlv.iItem = nItem;
6652 nmlv.lParam = lpItem->lParam;
6653 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6654 if (!IsWindow(hwndSelf))
6655 return -1;
6657 /* align items (set position of each item) */
6658 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6660 POINT pt;
6662 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6663 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6664 else
6665 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6667 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6670 /* now is the invalidation fun */
6671 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6672 return nItem;
6674 undo:
6675 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6676 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6677 infoPtr->nItemCount--;
6678 fail:
6679 DPA_DeletePtr(hdpaSubItems, 0);
6680 DPA_Destroy (hdpaSubItems);
6681 Free (lpItem);
6682 return -1;
6685 /***
6686 * DESCRIPTION:
6687 * Redraws a range of items.
6689 * PARAMETER(S):
6690 * [I] infoPtr : valid pointer to the listview structure
6691 * [I] nFirst : first item
6692 * [I] nLast : last item
6694 * RETURN:
6695 * SUCCESS : TRUE
6696 * FAILURE : FALSE
6698 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6700 INT i;
6702 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6703 max(nFirst, nLast) >= infoPtr->nItemCount)
6704 return FALSE;
6706 for (i = nFirst; i <= nLast; i++)
6707 LISTVIEW_InvalidateItem(infoPtr, i);
6709 return TRUE;
6712 /***
6713 * DESCRIPTION:
6714 * Scroll the content of a listview.
6716 * PARAMETER(S):
6717 * [I] infoPtr : valid pointer to the listview structure
6718 * [I] dx : horizontal scroll amount in pixels
6719 * [I] dy : vertical scroll amount in pixels
6721 * RETURN:
6722 * SUCCESS : TRUE
6723 * FAILURE : FALSE
6725 * COMMENTS:
6726 * If the control is in report mode (LVS_REPORT) the control can
6727 * be scrolled only in line increments. "dy" will be rounded to the
6728 * nearest number of pixels that are a whole line. Ex: if line height
6729 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6730 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6732 * For: (per experimentation with native control and CSpy ListView)
6733 * LVS_ICON dy=1 = 1 pixel (vertical only)
6734 * dx ignored
6735 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6736 * dx ignored
6737 * LVS_LIST dx=1 = 1 column (horizontal only)
6738 * but will only scroll 1 column per message
6739 * no matter what the value.
6740 * dy must be 0 or FALSE returned.
6741 * LVS_REPORT dx=1 = 1 pixel
6742 * dy= see above
6745 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6747 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6748 case LVS_REPORT:
6749 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6750 dy /= infoPtr->nItemHeight;
6751 break;
6752 case LVS_LIST:
6753 if (dy != 0) return FALSE;
6754 break;
6755 default: /* icon */
6756 dx = 0;
6757 break;
6760 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6761 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6763 return TRUE;
6766 /***
6767 * DESCRIPTION:
6768 * Sets the background color.
6770 * PARAMETER(S):
6771 * [I] infoPtr : valid pointer to the listview structure
6772 * [I] clrBk : background color
6774 * RETURN:
6775 * SUCCESS : TRUE
6776 * FAILURE : FALSE
6778 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6780 TRACE("(clrBk=%x)\n", clrBk);
6782 if(infoPtr->clrBk != clrBk) {
6783 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6784 infoPtr->clrBk = clrBk;
6785 if (clrBk == CLR_NONE)
6786 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6787 else
6788 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6789 LISTVIEW_InvalidateList(infoPtr);
6792 return TRUE;
6795 /* LISTVIEW_SetBkImage */
6797 /*** Helper for {Insert,Set}ColumnT *only* */
6798 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6799 const LVCOLUMNW *lpColumn, BOOL isW)
6801 if (lpColumn->mask & LVCF_FMT)
6803 /* format member is valid */
6804 lphdi->mask |= HDI_FORMAT;
6806 /* set text alignment (leftmost column must be left-aligned) */
6807 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6808 lphdi->fmt |= HDF_LEFT;
6809 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6810 lphdi->fmt |= HDF_RIGHT;
6811 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6812 lphdi->fmt |= HDF_CENTER;
6814 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6815 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6817 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6819 lphdi->fmt |= HDF_IMAGE;
6820 lphdi->iImage = I_IMAGECALLBACK;
6824 if (lpColumn->mask & LVCF_WIDTH)
6826 lphdi->mask |= HDI_WIDTH;
6827 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6829 /* make it fill the remainder of the controls width */
6830 RECT rcHeader;
6831 INT item_index;
6833 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6835 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6836 lphdi->cxy += rcHeader.right - rcHeader.left;
6839 /* retrieve the layout of the header */
6840 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6841 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6843 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6845 else
6846 lphdi->cxy = lpColumn->cx;
6849 if (lpColumn->mask & LVCF_TEXT)
6851 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6852 lphdi->fmt |= HDF_STRING;
6853 lphdi->pszText = lpColumn->pszText;
6854 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6857 if (lpColumn->mask & LVCF_IMAGE)
6859 lphdi->mask |= HDI_IMAGE;
6860 lphdi->iImage = lpColumn->iImage;
6863 if (lpColumn->mask & LVCF_ORDER)
6865 lphdi->mask |= HDI_ORDER;
6866 lphdi->iOrder = lpColumn->iOrder;
6871 /***
6872 * DESCRIPTION:
6873 * Inserts a new column.
6875 * PARAMETER(S):
6876 * [I] infoPtr : valid pointer to the listview structure
6877 * [I] nColumn : column index
6878 * [I] lpColumn : column information
6879 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6881 * RETURN:
6882 * SUCCESS : new column index
6883 * FAILURE : -1
6885 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6886 const LVCOLUMNW *lpColumn, BOOL isW)
6888 COLUMN_INFO *lpColumnInfo;
6889 INT nNewColumn;
6890 HDITEMW hdi;
6891 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6893 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6895 if (!lpColumn || nColumn < 0) return -1;
6896 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6898 ZeroMemory(&hdi, sizeof(HDITEMW));
6899 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6902 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6903 * (can be seen in SPY) otherwise column never gets added.
6905 if (!(lpColumn->mask & LVCF_WIDTH)) {
6906 hdi.mask |= HDI_WIDTH;
6907 hdi.cxy = 10;
6911 * when the iSubItem is available Windows copies it to the header lParam. It seems
6912 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6914 if (lpColumn->mask & LVCF_SUBITEM)
6916 hdi.mask |= HDI_LPARAM;
6917 hdi.lParam = lpColumn->iSubItem;
6920 /* create header if not present */
6921 LISTVIEW_CreateHeader(infoPtr);
6922 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
6923 (LVS_REPORT == uView) && (WS_VISIBLE & infoPtr->dwStyle))
6925 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
6928 /* insert item in header control */
6929 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6930 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6931 (WPARAM)nColumn, (LPARAM)&hdi);
6932 if (nNewColumn == -1) return -1;
6933 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6935 /* create our own column info */
6936 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6937 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6939 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6940 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6942 /* now we have to actually adjust the data */
6943 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6945 SUBITEM_INFO *lpSubItem;
6946 HDPA hdpaSubItems;
6947 INT nItem, i;
6949 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6951 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
6952 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6954 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
6955 if (lpSubItem->iSubItem >= nNewColumn)
6956 lpSubItem->iSubItem++;
6961 /* make space for the new column */
6962 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6963 LISTVIEW_UpdateItemSize(infoPtr);
6965 return nNewColumn;
6967 fail:
6968 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6969 if (lpColumnInfo)
6971 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6972 Free(lpColumnInfo);
6974 return -1;
6977 /***
6978 * DESCRIPTION:
6979 * Sets the attributes of a header item.
6981 * PARAMETER(S):
6982 * [I] infoPtr : valid pointer to the listview structure
6983 * [I] nColumn : column index
6984 * [I] lpColumn : column attributes
6985 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6987 * RETURN:
6988 * SUCCESS : TRUE
6989 * FAILURE : FALSE
6991 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6992 const LVCOLUMNW *lpColumn, BOOL isW)
6994 HDITEMW hdi, hdiget;
6995 BOOL bResult;
6997 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6999 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7001 ZeroMemory(&hdi, sizeof(HDITEMW));
7002 if (lpColumn->mask & LVCF_FMT)
7004 hdi.mask |= HDI_FORMAT;
7005 hdiget.mask = HDI_FORMAT;
7006 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7007 hdi.fmt = hdiget.fmt & HDF_STRING;
7009 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7011 /* set header item attributes */
7012 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7013 if (!bResult) return FALSE;
7015 if (lpColumn->mask & LVCF_FMT)
7017 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7018 int oldFmt = lpColumnInfo->fmt;
7020 lpColumnInfo->fmt = lpColumn->fmt;
7021 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7023 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7024 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7028 return TRUE;
7031 /***
7032 * DESCRIPTION:
7033 * Sets the column order array
7035 * PARAMETERS:
7036 * [I] infoPtr : valid pointer to the listview structure
7037 * [I] iCount : number of elements in column order array
7038 * [I] lpiArray : pointer to column order array
7040 * RETURN:
7041 * SUCCESS : TRUE
7042 * FAILURE : FALSE
7044 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7046 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7048 if (!lpiArray)
7049 return FALSE;
7051 return TRUE;
7055 /***
7056 * DESCRIPTION:
7057 * Sets the width of a column
7059 * PARAMETERS:
7060 * [I] infoPtr : valid pointer to the listview structure
7061 * [I] nColumn : column index
7062 * [I] cx : column width
7064 * RETURN:
7065 * SUCCESS : TRUE
7066 * FAILURE : FALSE
7068 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7070 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7071 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7072 INT max_cx = 0;
7073 HDITEMW hdi;
7075 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7077 /* set column width only if in report or list mode */
7078 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7080 /* take care of invalid cx values */
7081 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7082 else if (uView == LVS_LIST && cx < 1) return FALSE;
7084 /* resize all columns if in LVS_LIST mode */
7085 if(uView == LVS_LIST)
7087 infoPtr->nItemWidth = cx;
7088 LISTVIEW_InvalidateList(infoPtr);
7089 return TRUE;
7092 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7094 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7096 INT nLabelWidth;
7097 LVITEMW lvItem;
7099 lvItem.mask = LVIF_TEXT;
7100 lvItem.iItem = 0;
7101 lvItem.iSubItem = nColumn;
7102 lvItem.pszText = szDispText;
7103 lvItem.cchTextMax = DISP_TEXT_SIZE;
7104 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7106 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7107 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7108 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7110 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7111 max_cx += infoPtr->iconSize.cx;
7112 max_cx += TRAILING_LABEL_PADDING;
7115 /* autosize based on listview items width */
7116 if(cx == LVSCW_AUTOSIZE)
7117 cx = max_cx;
7118 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7120 /* if iCol is the last column make it fill the remainder of the controls width */
7121 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7123 RECT rcHeader;
7124 POINT Origin;
7126 LISTVIEW_GetOrigin(infoPtr, &Origin);
7127 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7129 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7131 else
7133 /* Despite what the MS docs say, if this is not the last
7134 column, then MS resizes the column to the width of the
7135 largest text string in the column, including headers
7136 and items. This is different from LVSCW_AUTOSIZE in that
7137 LVSCW_AUTOSIZE ignores the header string length. */
7138 cx = 0;
7140 /* retrieve header text */
7141 hdi.mask = HDI_TEXT;
7142 hdi.cchTextMax = DISP_TEXT_SIZE;
7143 hdi.pszText = szDispText;
7144 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7146 HDC hdc = GetDC(infoPtr->hwndSelf);
7147 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7148 SIZE size;
7150 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7151 cx = size.cx + TRAILING_HEADER_PADDING;
7152 /* FIXME: Take into account the header image, if one is present */
7153 SelectObject(hdc, old_font);
7154 ReleaseDC(infoPtr->hwndSelf, hdc);
7156 cx = max (cx, max_cx);
7160 if (cx < 0) return FALSE;
7162 /* call header to update the column change */
7163 hdi.mask = HDI_WIDTH;
7164 hdi.cxy = cx;
7165 TRACE("hdi.cxy=%d\n", hdi.cxy);
7166 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7169 /***
7170 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7173 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7175 HDC hdc_wnd, hdc;
7176 HBITMAP hbm_im, hbm_mask, hbm_orig;
7177 RECT rc;
7178 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7179 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7180 HIMAGELIST himl;
7182 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7183 ILC_COLOR | ILC_MASK, 2, 2);
7184 hdc_wnd = GetDC(infoPtr->hwndSelf);
7185 hdc = CreateCompatibleDC(hdc_wnd);
7186 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7187 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7188 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7190 rc.left = rc.top = 0;
7191 rc.right = GetSystemMetrics(SM_CXSMICON);
7192 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7194 hbm_orig = SelectObject(hdc, hbm_mask);
7195 FillRect(hdc, &rc, hbr_white);
7196 InflateRect(&rc, -3, -3);
7197 FillRect(hdc, &rc, hbr_black);
7199 SelectObject(hdc, hbm_im);
7200 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7201 SelectObject(hdc, hbm_orig);
7202 ImageList_Add(himl, hbm_im, hbm_mask);
7204 SelectObject(hdc, hbm_im);
7205 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7206 SelectObject(hdc, hbm_orig);
7207 ImageList_Add(himl, hbm_im, hbm_mask);
7209 DeleteObject(hbm_mask);
7210 DeleteObject(hbm_im);
7211 DeleteDC(hdc);
7213 return himl;
7216 /***
7217 * DESCRIPTION:
7218 * Sets the extended listview style.
7220 * PARAMETERS:
7221 * [I] infoPtr : valid pointer to the listview structure
7222 * [I] dwMask : mask
7223 * [I] dwStyle : style
7225 * RETURN:
7226 * SUCCESS : previous style
7227 * FAILURE : 0
7229 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7231 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7233 /* set new style */
7234 if (dwMask)
7235 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7236 else
7237 infoPtr->dwLvExStyle = dwExStyle;
7239 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7241 HIMAGELIST himl = 0;
7242 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7244 LVITEMW item;
7245 item.mask = LVIF_STATE;
7246 item.stateMask = LVIS_STATEIMAGEMASK;
7247 item.state = INDEXTOSTATEIMAGEMASK(1);
7248 LISTVIEW_SetItemState(infoPtr, -1, &item);
7250 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7252 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7255 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7257 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7258 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7259 dwStyle |= HDS_DRAGDROP;
7260 else
7261 dwStyle &= ~HDS_DRAGDROP;
7262 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7265 /* GRIDLINES adds decoration at top so changes sizes */
7266 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7268 LISTVIEW_UpdateSize(infoPtr);
7272 LISTVIEW_InvalidateList(infoPtr);
7273 return dwOldExStyle;
7276 /***
7277 * DESCRIPTION:
7278 * Sets the new hot cursor used during hot tracking and hover selection.
7280 * PARAMETER(S):
7281 * [I] infoPtr : valid pointer to the listview structure
7282 * [I] hCursor : the new hot cursor handle
7284 * RETURN:
7285 * Returns the previous hot cursor
7287 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7289 HCURSOR oldCursor = infoPtr->hHotCursor;
7291 infoPtr->hHotCursor = hCursor;
7293 return oldCursor;
7297 /***
7298 * DESCRIPTION:
7299 * Sets the hot item index.
7301 * PARAMETERS:
7302 * [I] infoPtr : valid pointer to the listview structure
7303 * [I] iIndex : index
7305 * RETURN:
7306 * SUCCESS : previous hot item index
7307 * FAILURE : -1 (no hot item)
7309 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7311 INT iOldIndex = infoPtr->nHotItem;
7313 infoPtr->nHotItem = iIndex;
7315 return iOldIndex;
7319 /***
7320 * DESCRIPTION:
7321 * Sets the amount of time the cursor must hover over an item before it is selected.
7323 * PARAMETER(S):
7324 * [I] infoPtr : valid pointer to the listview structure
7325 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7327 * RETURN:
7328 * Returns the previous hover time
7330 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7332 DWORD oldHoverTime = infoPtr->dwHoverTime;
7334 infoPtr->dwHoverTime = dwHoverTime;
7336 return oldHoverTime;
7339 /***
7340 * DESCRIPTION:
7341 * Sets spacing for icons of LVS_ICON style.
7343 * PARAMETER(S):
7344 * [I] infoPtr : valid pointer to the listview structure
7345 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7346 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7348 * RETURN:
7349 * MAKELONG(oldcx, oldcy)
7351 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7353 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7354 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7356 TRACE("requested=(%d,%d)\n", cx, cy);
7358 /* this is supported only for LVS_ICON style */
7359 if (uView != LVS_ICON) return oldspacing;
7361 /* set to defaults, if instructed to */
7362 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7363 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7365 /* if 0 then compute width
7366 * FIXME: Should scan each item and determine max width of
7367 * icon or label, then make that the width */
7368 if (cx == 0)
7369 cx = infoPtr->iconSpacing.cx;
7371 /* if 0 then compute height */
7372 if (cy == 0)
7373 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7374 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7377 infoPtr->iconSpacing.cx = cx;
7378 infoPtr->iconSpacing.cy = cy;
7380 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7381 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7382 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7383 infoPtr->ntmHeight);
7385 /* these depend on the iconSpacing */
7386 LISTVIEW_UpdateItemSize(infoPtr);
7388 return oldspacing;
7391 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7393 INT cx, cy;
7395 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7397 size->cx = cx;
7398 size->cy = cy;
7400 else
7402 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7403 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7407 /***
7408 * DESCRIPTION:
7409 * Sets image lists.
7411 * PARAMETER(S):
7412 * [I] infoPtr : valid pointer to the listview structure
7413 * [I] nType : image list type
7414 * [I] himl : image list handle
7416 * RETURN:
7417 * SUCCESS : old image list
7418 * FAILURE : NULL
7420 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7422 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7423 INT oldHeight = infoPtr->nItemHeight;
7424 HIMAGELIST himlOld = 0;
7426 TRACE("(nType=%d, himl=%p\n", nType, himl);
7428 switch (nType)
7430 case LVSIL_NORMAL:
7431 himlOld = infoPtr->himlNormal;
7432 infoPtr->himlNormal = himl;
7433 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7434 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7435 break;
7437 case LVSIL_SMALL:
7438 himlOld = infoPtr->himlSmall;
7439 infoPtr->himlSmall = himl;
7440 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7441 break;
7443 case LVSIL_STATE:
7444 himlOld = infoPtr->himlState;
7445 infoPtr->himlState = himl;
7446 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7447 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7448 break;
7450 default:
7451 ERR("Unknown icon type=%d\n", nType);
7452 return NULL;
7455 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7456 if (infoPtr->nItemHeight != oldHeight)
7457 LISTVIEW_UpdateScroll(infoPtr);
7459 return himlOld;
7462 /***
7463 * DESCRIPTION:
7464 * Preallocates memory (does *not* set the actual count of items !)
7466 * PARAMETER(S):
7467 * [I] infoPtr : valid pointer to the listview structure
7468 * [I] nItems : item count (projected number of items to allocate)
7469 * [I] dwFlags : update flags
7471 * RETURN:
7472 * SUCCESS : TRUE
7473 * FAILURE : FALSE
7475 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7477 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7479 if (infoPtr->dwStyle & LVS_OWNERDATA)
7481 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7482 INT nOldCount = infoPtr->nItemCount;
7484 if (nItems < nOldCount)
7486 RANGE range = { nItems, nOldCount };
7487 ranges_del(infoPtr->selectionRanges, range);
7488 if (infoPtr->nFocusedItem >= nItems)
7490 infoPtr->nFocusedItem = -1;
7491 SetRectEmpty(&infoPtr->rcFocus);
7495 infoPtr->nItemCount = nItems;
7496 LISTVIEW_UpdateScroll(infoPtr);
7498 /* the flags are valid only in ownerdata report and list modes */
7499 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7501 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7502 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7504 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7505 LISTVIEW_InvalidateList(infoPtr);
7506 else
7508 INT nFrom, nTo;
7509 POINT Origin;
7510 RECT rcErase;
7512 LISTVIEW_GetOrigin(infoPtr, &Origin);
7513 nFrom = min(nOldCount, nItems);
7514 nTo = max(nOldCount, nItems);
7516 if (uView == LVS_REPORT)
7518 rcErase.left = 0;
7519 rcErase.top = nFrom * infoPtr->nItemHeight;
7520 rcErase.right = infoPtr->nItemWidth;
7521 rcErase.bottom = nTo * infoPtr->nItemHeight;
7522 OffsetRect(&rcErase, Origin.x, Origin.y);
7523 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7524 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7526 else /* LVS_LIST */
7528 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7530 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7531 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7532 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7533 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7534 OffsetRect(&rcErase, Origin.x, Origin.y);
7535 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7536 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7538 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7539 rcErase.top = 0;
7540 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7541 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7542 OffsetRect(&rcErase, Origin.x, Origin.y);
7543 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7544 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7548 else
7550 /* According to MSDN for non-LVS_OWNERDATA this is just
7551 * a performance issue. The control allocates its internal
7552 * data structures for the number of items specified. It
7553 * cuts down on the number of memory allocations. Therefore
7554 * we will just issue a WARN here
7556 WARN("for non-ownerdata performance option not implemented.\n");
7559 return TRUE;
7562 /***
7563 * DESCRIPTION:
7564 * Sets the position of an item.
7566 * PARAMETER(S):
7567 * [I] infoPtr : valid pointer to the listview structure
7568 * [I] nItem : item index
7569 * [I] pt : coordinate
7571 * RETURN:
7572 * SUCCESS : TRUE
7573 * FAILURE : FALSE
7575 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7577 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7578 POINT Origin;
7580 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7582 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7583 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7585 LISTVIEW_GetOrigin(infoPtr, &Origin);
7587 /* This point value seems to be an undocumented feature.
7588 * The best guess is that it means either at the origin,
7589 * or at true beginning of the list. I will assume the origin. */
7590 if ((pt.x == -1) && (pt.y == -1))
7591 pt = Origin;
7593 if (uView == LVS_ICON)
7595 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7596 pt.y -= ICON_TOP_PADDING;
7598 pt.x -= Origin.x;
7599 pt.y -= Origin.y;
7601 infoPtr->bAutoarrange = FALSE;
7603 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7606 /***
7607 * DESCRIPTION:
7608 * Sets the state of one or many items.
7610 * PARAMETER(S):
7611 * [I] infoPtr : valid pointer to the listview structure
7612 * [I] nItem : item index
7613 * [I] lpLVItem : item or subitem info
7615 * RETURN:
7616 * SUCCESS : TRUE
7617 * FAILURE : FALSE
7619 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7621 BOOL bResult = TRUE;
7622 LVITEMW lvItem;
7624 lvItem.iItem = nItem;
7625 lvItem.iSubItem = 0;
7626 lvItem.mask = LVIF_STATE;
7627 lvItem.state = lpLVItem->state;
7628 lvItem.stateMask = lpLVItem->stateMask;
7629 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7631 if (nItem == -1)
7633 /* apply to all items */
7634 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7635 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7637 else
7638 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7641 * Update selection mark
7643 * Investigation on windows 2k showed that selection mark was updated
7644 * whenever a new selection was made, but if the selected item was
7645 * unselected it was not updated.
7647 * we are probably still not 100% accurate, but this at least sets the
7648 * proper selection mark when it is needed
7651 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7652 (infoPtr->nSelectionMark == -1))
7654 int i;
7655 for (i = 0; i < infoPtr->nItemCount; i++)
7657 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7659 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7661 infoPtr->nSelectionMark = i;
7662 break;
7665 else if (ranges_contain(infoPtr->selectionRanges, i))
7667 infoPtr->nSelectionMark = i;
7668 break;
7673 return bResult;
7676 /***
7677 * DESCRIPTION:
7678 * Sets the text of an item or subitem.
7680 * PARAMETER(S):
7681 * [I] hwnd : window handle
7682 * [I] nItem : item index
7683 * [I] lpLVItem : item or subitem info
7684 * [I] isW : TRUE if input is Unicode
7686 * RETURN:
7687 * SUCCESS : TRUE
7688 * FAILURE : FALSE
7690 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7692 LVITEMW lvItem;
7694 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7696 lvItem.iItem = nItem;
7697 lvItem.iSubItem = lpLVItem->iSubItem;
7698 lvItem.mask = LVIF_TEXT;
7699 lvItem.pszText = lpLVItem->pszText;
7700 lvItem.cchTextMax = lpLVItem->cchTextMax;
7702 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7704 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7707 /***
7708 * DESCRIPTION:
7709 * Set item index that marks the start of a multiple selection.
7711 * PARAMETER(S):
7712 * [I] infoPtr : valid pointer to the listview structure
7713 * [I] nIndex : index
7715 * RETURN:
7716 * Index number or -1 if there is no selection mark.
7718 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7720 INT nOldIndex = infoPtr->nSelectionMark;
7722 TRACE("(nIndex=%d)\n", nIndex);
7724 infoPtr->nSelectionMark = nIndex;
7726 return nOldIndex;
7729 /***
7730 * DESCRIPTION:
7731 * Sets the text background color.
7733 * PARAMETER(S):
7734 * [I] infoPtr : valid pointer to the listview structure
7735 * [I] clrTextBk : text background color
7737 * RETURN:
7738 * SUCCESS : TRUE
7739 * FAILURE : FALSE
7741 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7743 TRACE("(clrTextBk=%x)\n", clrTextBk);
7745 if (infoPtr->clrTextBk != clrTextBk)
7747 infoPtr->clrTextBk = clrTextBk;
7748 LISTVIEW_InvalidateList(infoPtr);
7751 return TRUE;
7754 /***
7755 * DESCRIPTION:
7756 * Sets the text foreground color.
7758 * PARAMETER(S):
7759 * [I] infoPtr : valid pointer to the listview structure
7760 * [I] clrText : text color
7762 * RETURN:
7763 * SUCCESS : TRUE
7764 * FAILURE : FALSE
7766 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7768 TRACE("(clrText=%x)\n", clrText);
7770 if (infoPtr->clrText != clrText)
7772 infoPtr->clrText = clrText;
7773 LISTVIEW_InvalidateList(infoPtr);
7776 return TRUE;
7779 /***
7780 * DESCRIPTION:
7781 * Determines which listview item is located at the specified position.
7783 * PARAMETER(S):
7784 * [I] infoPtr : valid pointer to the listview structure
7785 * [I] hwndNewToolTip : handle to new ToolTip
7787 * RETURN:
7788 * old tool tip
7790 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7792 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7793 infoPtr->hwndToolTip = hwndNewToolTip;
7794 return hwndOldToolTip;
7798 * DESCRIPTION:
7799 * sets the Unicode character format flag for the control
7800 * PARAMETER(S):
7801 * [I] infoPtr :valid pointer to the listview structure
7802 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7804 * RETURN:
7805 * Old Unicode Format
7807 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7809 BOOL rc = infoPtr->notifyFormat;
7810 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7811 return rc;
7814 /* LISTVIEW_SetWorkAreas */
7816 /***
7817 * DESCRIPTION:
7818 * Callback internally used by LISTVIEW_SortItems()
7820 * PARAMETER(S):
7821 * [I] first : pointer to first ITEM_INFO to compare
7822 * [I] second : pointer to second ITEM_INFO to compare
7823 * [I] lParam : HWND of control
7825 * RETURN:
7826 * if first comes before second : negative
7827 * if first comes after second : positive
7828 * if first and second are equivalent : zero
7830 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7832 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7833 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7834 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7836 /* Forward the call to the client defined callback */
7837 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7840 /***
7841 * DESCRIPTION:
7842 * Sorts the listview items.
7844 * PARAMETER(S):
7845 * [I] infoPtr : valid pointer to the listview structure
7846 * [I] pfnCompare : application-defined value
7847 * [I] lParamSort : pointer to comparison callback
7849 * RETURN:
7850 * SUCCESS : TRUE
7851 * FAILURE : FALSE
7853 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7855 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7856 HDPA hdpaSubItems;
7857 ITEM_INFO *lpItem;
7858 LPVOID selectionMarkItem;
7859 LVITEMW item;
7860 int i;
7862 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7864 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7866 if (!pfnCompare) return FALSE;
7867 if (!infoPtr->hdpaItems) return FALSE;
7869 /* if there are 0 or 1 items, there is no need to sort */
7870 if (infoPtr->nItemCount < 2) return TRUE;
7872 if (infoPtr->nFocusedItem >= 0)
7874 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7875 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7876 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7878 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7879 /* clear the lpItem->state for non-selected ones */
7880 /* remove the selection ranges */
7882 infoPtr->pfnCompare = pfnCompare;
7883 infoPtr->lParamSort = lParamSort;
7884 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7886 /* Adjust selections and indices so that they are the way they should
7887 * be after the sort (otherwise, the list items move around, but
7888 * whatever is at the item's previous original position will be
7889 * selected instead)
7891 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7892 for (i=0; i < infoPtr->nItemCount; i++)
7894 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7895 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7897 if (lpItem->state & LVIS_SELECTED)
7899 item.state = LVIS_SELECTED;
7900 item.stateMask = LVIS_SELECTED;
7901 LISTVIEW_SetItemState(infoPtr, i, &item);
7903 if (lpItem->state & LVIS_FOCUSED)
7905 infoPtr->nFocusedItem = i;
7906 lpItem->state &= ~LVIS_FOCUSED;
7909 if (selectionMarkItem != NULL)
7910 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7911 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7913 /* refresh the display */
7914 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7915 LISTVIEW_InvalidateList(infoPtr);
7917 return TRUE;
7920 /***
7921 * DESCRIPTION:
7922 * Update theme handle after a theme change.
7924 * PARAMETER(S):
7925 * [I] infoPtr : valid pointer to the listview structure
7927 * RETURN:
7928 * SUCCESS : 0
7929 * FAILURE : something else
7931 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7933 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7934 CloseThemeData(theme);
7935 OpenThemeData(infoPtr->hwndSelf, themeClass);
7936 return 0;
7939 /***
7940 * DESCRIPTION:
7941 * Updates an items or rearranges the listview control.
7943 * PARAMETER(S):
7944 * [I] infoPtr : valid pointer to the listview structure
7945 * [I] nItem : item index
7947 * RETURN:
7948 * SUCCESS : TRUE
7949 * FAILURE : FALSE
7951 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7953 TRACE("(nItem=%d)\n", nItem);
7955 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7957 /* rearrange with default alignment style */
7958 if (is_autoarrange(infoPtr))
7959 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7960 else
7961 LISTVIEW_InvalidateItem(infoPtr, nItem);
7963 return TRUE;
7966 /***
7967 * DESCRIPTION:
7968 * Draw the track line at the place defined in the infoPtr structure.
7969 * The line is drawn with a XOR pen so drawing the line for the second time
7970 * in the same place erases the line.
7972 * PARAMETER(S):
7973 * [I] infoPtr : valid pointer to the listview structure
7975 * RETURN:
7976 * SUCCESS : TRUE
7977 * FAILURE : FALSE
7979 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7981 HPEN hOldPen;
7982 HDC hdc;
7983 INT oldROP;
7985 if (infoPtr->xTrackLine == -1)
7986 return FALSE;
7988 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7989 return FALSE;
7990 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7991 oldROP = SetROP2(hdc, R2_XORPEN);
7992 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7993 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7994 SetROP2(hdc, oldROP);
7995 SelectObject(hdc, hOldPen);
7996 ReleaseDC(infoPtr->hwndSelf, hdc);
7997 return TRUE;
8000 /***
8001 * DESCRIPTION:
8002 * Called when an edit control should be displayed. This function is called after
8003 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8005 * PARAMETER(S):
8006 * [I] hwnd : Handle to the listview
8007 * [I] uMsg : WM_TIMER (ignored)
8008 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8009 * [I] dwTimer : The elapsed time (ignored)
8011 * RETURN:
8012 * None.
8014 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8016 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8017 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8019 KillTimer(hwnd, idEvent);
8020 editItem->fEnabled = FALSE;
8021 /* check if the item is still selected */
8022 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8023 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8026 /***
8027 * DESCRIPTION:
8028 * Creates the listview control - the WM_NCCREATE phase.
8030 * PARAMETER(S):
8031 * [I] hwnd : window handle
8032 * [I] lpcs : the create parameters
8034 * RETURN:
8035 * Success: TRUE
8036 * Failure: FALSE
8038 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8040 LISTVIEW_INFO *infoPtr;
8041 LOGFONTW logFont;
8043 TRACE("(lpcs=%p)\n", lpcs);
8045 /* initialize info pointer */
8046 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8047 if (!infoPtr) return FALSE;
8049 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8051 infoPtr->hwndSelf = hwnd;
8052 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8053 /* determine the type of structures to use */
8054 infoPtr->hwndNotify = lpcs->hwndParent;
8055 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8057 /* initialize color information */
8058 infoPtr->clrBk = CLR_NONE;
8059 infoPtr->clrText = CLR_DEFAULT;
8060 infoPtr->clrTextBk = CLR_DEFAULT;
8061 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8063 /* set default values */
8064 infoPtr->nFocusedItem = -1;
8065 infoPtr->nSelectionMark = -1;
8066 infoPtr->nHotItem = -1;
8067 infoPtr->bRedraw = TRUE;
8068 infoPtr->bNoItemMetrics = TRUE;
8069 infoPtr->bDoChangeNotify = TRUE;
8070 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8071 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8072 infoPtr->nEditLabelItem = -1;
8073 infoPtr->dwHoverTime = -1; /* default system hover time */
8074 infoPtr->nMeasureItemHeight = 0;
8075 infoPtr->xTrackLine = -1; /* no track line */
8076 infoPtr->itemEdit.fEnabled = FALSE;
8078 /* get default font (icon title) */
8079 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8080 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8081 infoPtr->hFont = infoPtr->hDefaultFont;
8082 LISTVIEW_SaveTextMetrics(infoPtr);
8084 /* allocate memory for the data structure */
8085 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8086 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8087 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8088 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8089 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8090 return TRUE;
8092 fail:
8093 DestroyWindow(infoPtr->hwndHeader);
8094 ranges_destroy(infoPtr->selectionRanges);
8095 DPA_Destroy(infoPtr->hdpaItems);
8096 DPA_Destroy(infoPtr->hdpaPosX);
8097 DPA_Destroy(infoPtr->hdpaPosY);
8098 DPA_Destroy(infoPtr->hdpaColumns);
8099 Free(infoPtr);
8100 return FALSE;
8103 /***
8104 * DESCRIPTION:
8105 * Creates the listview control - the WM_CREATE phase. Most of the data is
8106 * already set up in LISTVIEW_NCCreate
8108 * PARAMETER(S):
8109 * [I] hwnd : window handle
8110 * [I] lpcs : the create parameters
8112 * RETURN:
8113 * Success: 0
8114 * Failure: -1
8116 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8118 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8119 UINT uView = lpcs->style & LVS_TYPEMASK;
8121 TRACE("(lpcs=%p)\n", lpcs);
8123 infoPtr->dwStyle = lpcs->style;
8124 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8125 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8127 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8129 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8131 else
8132 infoPtr->hwndHeader = 0;
8134 /* init item size to avoid division by 0 */
8135 LISTVIEW_UpdateItemSize (infoPtr);
8137 if (uView == LVS_REPORT)
8139 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8141 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8143 LISTVIEW_UpdateSize(infoPtr);
8144 LISTVIEW_UpdateScroll(infoPtr);
8147 OpenThemeData(hwnd, themeClass);
8149 /* initialize the icon sizes */
8150 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8151 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8152 return 0;
8155 /***
8156 * DESCRIPTION:
8157 * Destroys the listview control.
8159 * PARAMETER(S):
8160 * [I] infoPtr : valid pointer to the listview structure
8162 * RETURN:
8163 * Success: 0
8164 * Failure: -1
8166 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8168 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8169 CloseThemeData(theme);
8170 return 0;
8173 /***
8174 * DESCRIPTION:
8175 * Enables the listview control.
8177 * PARAMETER(S):
8178 * [I] infoPtr : valid pointer to the listview structure
8179 * [I] bEnable : specifies whether to enable or disable the window
8181 * RETURN:
8182 * SUCCESS : TRUE
8183 * FAILURE : FALSE
8185 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8187 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8188 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8189 return TRUE;
8192 /***
8193 * DESCRIPTION:
8194 * Erases the background of the listview control.
8196 * PARAMETER(S):
8197 * [I] infoPtr : valid pointer to the listview structure
8198 * [I] hdc : device context handle
8200 * RETURN:
8201 * SUCCESS : TRUE
8202 * FAILURE : FALSE
8204 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8206 RECT rc;
8208 TRACE("(hdc=%p)\n", hdc);
8210 if (!GetClipBox(hdc, &rc)) return FALSE;
8212 /* for double buffered controls we need to do this during refresh */
8213 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8215 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8219 /***
8220 * DESCRIPTION:
8221 * Helper function for LISTVIEW_[HV]Scroll *only*.
8222 * Performs vertical/horizontal scrolling by a give amount.
8224 * PARAMETER(S):
8225 * [I] infoPtr : valid pointer to the listview structure
8226 * [I] dx : amount of horizontal scroll
8227 * [I] dy : amount of vertical scroll
8229 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8231 /* now we can scroll the list */
8232 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8233 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8234 /* if we have focus, adjust rect */
8235 OffsetRect(&infoPtr->rcFocus, dx, dy);
8236 UpdateWindow(infoPtr->hwndSelf);
8239 /***
8240 * DESCRIPTION:
8241 * Performs vertical scrolling.
8243 * PARAMETER(S):
8244 * [I] infoPtr : valid pointer to the listview structure
8245 * [I] nScrollCode : scroll code
8246 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8247 * [I] hScrollWnd : scrollbar control window handle
8249 * RETURN:
8250 * Zero
8252 * NOTES:
8253 * SB_LINEUP/SB_LINEDOWN:
8254 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8255 * for LVS_REPORT is 1 line
8256 * for LVS_LIST cannot occur
8259 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8260 INT nScrollDiff, HWND hScrollWnd)
8262 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8263 INT nOldScrollPos, nNewScrollPos;
8264 SCROLLINFO scrollInfo;
8265 BOOL is_an_icon;
8267 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8268 debugscrollcode(nScrollCode), nScrollDiff);
8270 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8272 scrollInfo.cbSize = sizeof(SCROLLINFO);
8273 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8275 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8277 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8279 nOldScrollPos = scrollInfo.nPos;
8280 switch (nScrollCode)
8282 case SB_INTERNAL:
8283 break;
8285 case SB_LINEUP:
8286 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8287 break;
8289 case SB_LINEDOWN:
8290 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8291 break;
8293 case SB_PAGEUP:
8294 nScrollDiff = -scrollInfo.nPage;
8295 break;
8297 case SB_PAGEDOWN:
8298 nScrollDiff = scrollInfo.nPage;
8299 break;
8301 case SB_THUMBPOSITION:
8302 case SB_THUMBTRACK:
8303 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8304 break;
8306 default:
8307 nScrollDiff = 0;
8310 /* quit right away if pos isn't changing */
8311 if (nScrollDiff == 0) return 0;
8313 /* calculate new position, and handle overflows */
8314 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8315 if (nScrollDiff > 0) {
8316 if (nNewScrollPos < nOldScrollPos ||
8317 nNewScrollPos > scrollInfo.nMax)
8318 nNewScrollPos = scrollInfo.nMax;
8319 } else {
8320 if (nNewScrollPos > nOldScrollPos ||
8321 nNewScrollPos < scrollInfo.nMin)
8322 nNewScrollPos = scrollInfo.nMin;
8325 /* set the new position, and reread in case it changed */
8326 scrollInfo.fMask = SIF_POS;
8327 scrollInfo.nPos = nNewScrollPos;
8328 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8330 /* carry on only if it really changed */
8331 if (nNewScrollPos == nOldScrollPos) return 0;
8333 /* now adjust to client coordinates */
8334 nScrollDiff = nOldScrollPos - nNewScrollPos;
8335 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8337 /* and scroll the window */
8338 scroll_list(infoPtr, 0, nScrollDiff);
8340 return 0;
8343 /***
8344 * DESCRIPTION:
8345 * Performs horizontal scrolling.
8347 * PARAMETER(S):
8348 * [I] infoPtr : valid pointer to the listview structure
8349 * [I] nScrollCode : scroll code
8350 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8351 * [I] hScrollWnd : scrollbar control window handle
8353 * RETURN:
8354 * Zero
8356 * NOTES:
8357 * SB_LINELEFT/SB_LINERIGHT:
8358 * for LVS_ICON, LVS_SMALLICON 1 pixel
8359 * for LVS_REPORT is 1 pixel
8360 * for LVS_LIST is 1 column --> which is a 1 because the
8361 * scroll is based on columns not pixels
8364 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8365 INT nScrollDiff, HWND hScrollWnd)
8367 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8368 INT nOldScrollPos, nNewScrollPos;
8369 SCROLLINFO scrollInfo;
8371 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8372 debugscrollcode(nScrollCode), nScrollDiff);
8374 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8376 scrollInfo.cbSize = sizeof(SCROLLINFO);
8377 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8379 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8381 nOldScrollPos = scrollInfo.nPos;
8383 switch (nScrollCode)
8385 case SB_INTERNAL:
8386 break;
8388 case SB_LINELEFT:
8389 nScrollDiff = -1;
8390 break;
8392 case SB_LINERIGHT:
8393 nScrollDiff = 1;
8394 break;
8396 case SB_PAGELEFT:
8397 nScrollDiff = -scrollInfo.nPage;
8398 break;
8400 case SB_PAGERIGHT:
8401 nScrollDiff = scrollInfo.nPage;
8402 break;
8404 case SB_THUMBPOSITION:
8405 case SB_THUMBTRACK:
8406 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8407 break;
8409 default:
8410 nScrollDiff = 0;
8413 /* quit right away if pos isn't changing */
8414 if (nScrollDiff == 0) return 0;
8416 /* calculate new position, and handle overflows */
8417 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8418 if (nScrollDiff > 0) {
8419 if (nNewScrollPos < nOldScrollPos ||
8420 nNewScrollPos > scrollInfo.nMax)
8421 nNewScrollPos = scrollInfo.nMax;
8422 } else {
8423 if (nNewScrollPos > nOldScrollPos ||
8424 nNewScrollPos < scrollInfo.nMin)
8425 nNewScrollPos = scrollInfo.nMin;
8428 /* set the new position, and reread in case it changed */
8429 scrollInfo.fMask = SIF_POS;
8430 scrollInfo.nPos = nNewScrollPos;
8431 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8433 /* carry on only if it really changed */
8434 if (nNewScrollPos == nOldScrollPos) return 0;
8436 if(uView == LVS_REPORT)
8437 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8439 /* now adjust to client coordinates */
8440 nScrollDiff = nOldScrollPos - nNewScrollPos;
8441 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8443 /* and scroll the window */
8444 scroll_list(infoPtr, nScrollDiff, 0);
8446 return 0;
8449 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8451 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8452 INT gcWheelDelta = 0;
8453 INT pulScrollLines = 3;
8454 SCROLLINFO scrollInfo;
8456 TRACE("(wheelDelta=%d)\n", wheelDelta);
8458 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8459 gcWheelDelta -= wheelDelta;
8461 scrollInfo.cbSize = sizeof(SCROLLINFO);
8462 scrollInfo.fMask = SIF_POS;
8464 switch(uView)
8466 case LVS_ICON:
8467 case LVS_SMALLICON:
8469 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8470 * should be fixed in the future.
8472 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8473 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8474 break;
8476 case LVS_REPORT:
8477 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8479 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8480 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8481 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8483 break;
8485 case LVS_LIST:
8486 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8487 break;
8489 return 0;
8492 /***
8493 * DESCRIPTION:
8494 * ???
8496 * PARAMETER(S):
8497 * [I] infoPtr : valid pointer to the listview structure
8498 * [I] nVirtualKey : virtual key
8499 * [I] lKeyData : key data
8501 * RETURN:
8502 * Zero
8504 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8506 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8507 HWND hwndSelf = infoPtr->hwndSelf;
8508 INT nItem = -1;
8509 NMLVKEYDOWN nmKeyDown;
8511 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8513 /* send LVN_KEYDOWN notification */
8514 nmKeyDown.wVKey = nVirtualKey;
8515 nmKeyDown.flags = 0;
8516 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8517 if (!IsWindow(hwndSelf))
8518 return 0;
8520 switch (nVirtualKey)
8522 case VK_SPACE:
8523 nItem = infoPtr->nFocusedItem;
8524 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8525 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8526 break;
8528 case VK_RETURN:
8529 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8531 if (!notify(infoPtr, NM_RETURN)) return 0;
8532 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8534 break;
8536 case VK_HOME:
8537 if (infoPtr->nItemCount > 0)
8538 nItem = 0;
8539 break;
8541 case VK_END:
8542 if (infoPtr->nItemCount > 0)
8543 nItem = infoPtr->nItemCount - 1;
8544 break;
8546 case VK_LEFT:
8547 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8548 break;
8550 case VK_UP:
8551 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8552 break;
8554 case VK_RIGHT:
8555 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8556 break;
8558 case VK_DOWN:
8559 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8560 break;
8562 case VK_PRIOR:
8563 if (uView == LVS_REPORT)
8565 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8566 if (infoPtr->nFocusedItem == topidx)
8567 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8568 else
8569 nItem = topidx;
8571 else
8572 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8573 * LISTVIEW_GetCountPerRow(infoPtr);
8574 if(nItem < 0) nItem = 0;
8575 break;
8577 case VK_NEXT:
8578 if (uView == LVS_REPORT)
8580 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8581 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8582 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8583 nItem = infoPtr->nFocusedItem + cnt - 1;
8584 else
8585 nItem = topidx + cnt - 1;
8587 else
8588 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8589 * LISTVIEW_GetCountPerRow(infoPtr);
8590 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8591 break;
8594 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8595 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8597 return 0;
8600 /***
8601 * DESCRIPTION:
8602 * Kills the focus.
8604 * PARAMETER(S):
8605 * [I] infoPtr : valid pointer to the listview structure
8607 * RETURN:
8608 * Zero
8610 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8612 TRACE("()\n");
8614 /* if we did not have the focus, there's nothing to do */
8615 if (!infoPtr->bFocus) return 0;
8617 /* send NM_KILLFOCUS notification */
8618 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8620 /* if we have a focus rectagle, get rid of it */
8621 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8623 /* set window focus flag */
8624 infoPtr->bFocus = FALSE;
8626 /* invalidate the selected items before resetting focus flag */
8627 LISTVIEW_InvalidateSelectedItems(infoPtr);
8629 return 0;
8632 /***
8633 * DESCRIPTION:
8634 * Processes double click messages (left mouse button).
8636 * PARAMETER(S):
8637 * [I] infoPtr : valid pointer to the listview structure
8638 * [I] wKey : key flag
8639 * [I] x,y : mouse coordinate
8641 * RETURN:
8642 * Zero
8644 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8646 LVHITTESTINFO htInfo;
8648 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8650 /* Cancel the item edition if any */
8651 if (infoPtr->itemEdit.fEnabled)
8653 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8654 infoPtr->itemEdit.fEnabled = FALSE;
8657 /* send NM_RELEASEDCAPTURE notification */
8658 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8660 htInfo.pt.x = x;
8661 htInfo.pt.y = y;
8663 /* send NM_DBLCLK notification */
8664 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8665 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8667 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8668 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8670 return 0;
8673 /***
8674 * DESCRIPTION:
8675 * Processes mouse down messages (left mouse button).
8677 * PARAMETERS:
8678 * infoPtr [I ] valid pointer to the listview structure
8679 * wKey [I ] key flag
8680 * x,y [I ] mouse coordinate
8682 * RETURN:
8683 * Zero
8685 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8687 LVHITTESTINFO lvHitTestInfo;
8688 static BOOL bGroupSelect = TRUE;
8689 POINT pt = { x, y };
8690 INT nItem;
8692 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8694 /* send NM_RELEASEDCAPTURE notification */
8695 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8697 /* set left button down flag and record the click position */
8698 infoPtr->bLButtonDown = TRUE;
8699 infoPtr->ptClickPos = pt;
8700 infoPtr->bDragging = FALSE;
8702 lvHitTestInfo.pt.x = x;
8703 lvHitTestInfo.pt.y = y;
8705 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8706 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8707 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8709 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8711 toggle_checkbox_state(infoPtr, nItem);
8712 return 0;
8715 if (infoPtr->dwStyle & LVS_SINGLESEL)
8717 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8718 infoPtr->nEditLabelItem = nItem;
8719 else
8720 LISTVIEW_SetSelection(infoPtr, nItem);
8722 else
8724 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8726 if (bGroupSelect)
8728 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8729 LISTVIEW_SetItemFocus(infoPtr, nItem);
8730 infoPtr->nSelectionMark = nItem;
8732 else
8734 LVITEMW item;
8736 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8737 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8739 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8740 infoPtr->nSelectionMark = nItem;
8743 else if (wKey & MK_CONTROL)
8745 LVITEMW item;
8747 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8749 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8750 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8751 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8752 infoPtr->nSelectionMark = nItem;
8754 else if (wKey & MK_SHIFT)
8756 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8758 else
8760 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8761 infoPtr->nEditLabelItem = nItem;
8763 /* set selection (clears other pre-existing selections) */
8764 LISTVIEW_SetSelection(infoPtr, nItem);
8768 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8769 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8771 else
8773 /* remove all selections */
8774 LISTVIEW_DeselectAll(infoPtr);
8775 ReleaseCapture();
8778 return 0;
8781 /***
8782 * DESCRIPTION:
8783 * Processes mouse up messages (left mouse button).
8785 * PARAMETERS:
8786 * infoPtr [I ] valid pointer to the listview structure
8787 * wKey [I ] key flag
8788 * x,y [I ] mouse coordinate
8790 * RETURN:
8791 * Zero
8793 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8795 LVHITTESTINFO lvHitTestInfo;
8797 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8799 if (!infoPtr->bLButtonDown) return 0;
8801 lvHitTestInfo.pt.x = x;
8802 lvHitTestInfo.pt.y = y;
8804 /* send NM_CLICK notification */
8805 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8806 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8808 /* set left button flag */
8809 infoPtr->bLButtonDown = FALSE;
8811 if (infoPtr->bDragging)
8813 infoPtr->bDragging = FALSE;
8814 return 0;
8817 /* if we clicked on a selected item, edit the label */
8818 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8820 /* we want to make sure the user doesn't want to do a double click. So we will
8821 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8823 infoPtr->itemEdit.fEnabled = TRUE;
8824 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8825 SetTimer(infoPtr->hwndSelf,
8826 (UINT_PTR)&infoPtr->itemEdit,
8827 GetDoubleClickTime(),
8828 LISTVIEW_DelayedEditItem);
8831 if (!infoPtr->bFocus)
8832 SetFocus(infoPtr->hwndSelf);
8834 return 0;
8837 /***
8838 * DESCRIPTION:
8839 * Destroys the listview control (called after WM_DESTROY).
8841 * PARAMETER(S):
8842 * [I] infoPtr : valid pointer to the listview structure
8844 * RETURN:
8845 * Zero
8847 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8849 TRACE("()\n");
8851 /* delete all items */
8852 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8854 /* destroy data structure */
8855 DPA_Destroy(infoPtr->hdpaItems);
8856 DPA_Destroy(infoPtr->hdpaPosX);
8857 DPA_Destroy(infoPtr->hdpaPosY);
8858 DPA_Destroy(infoPtr->hdpaColumns);
8859 ranges_destroy(infoPtr->selectionRanges);
8861 /* destroy image lists */
8862 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8864 if (infoPtr->himlNormal)
8865 ImageList_Destroy(infoPtr->himlNormal);
8866 if (infoPtr->himlSmall)
8867 ImageList_Destroy(infoPtr->himlSmall);
8868 if (infoPtr->himlState)
8869 ImageList_Destroy(infoPtr->himlState);
8872 /* destroy font, bkgnd brush */
8873 infoPtr->hFont = 0;
8874 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8875 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8877 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8879 /* free listview info pointer*/
8880 Free(infoPtr);
8882 return 0;
8885 /***
8886 * DESCRIPTION:
8887 * Handles notifications from header.
8889 * PARAMETER(S):
8890 * [I] infoPtr : valid pointer to the listview structure
8891 * [I] nCtrlId : control identifier
8892 * [I] lpnmh : notification information
8894 * RETURN:
8895 * Zero
8897 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8899 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8900 HWND hwndSelf = infoPtr->hwndSelf;
8902 TRACE("(lpnmh=%p)\n", lpnmh);
8904 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8906 switch (lpnmh->hdr.code)
8908 case HDN_TRACKW:
8909 case HDN_TRACKA:
8911 COLUMN_INFO *lpColumnInfo;
8912 POINT ptOrigin;
8913 INT x;
8915 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8916 break;
8918 /* remove the old line (if any) */
8919 LISTVIEW_DrawTrackLine(infoPtr);
8921 /* compute & draw the new line */
8922 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8923 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8924 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8925 infoPtr->xTrackLine = x + ptOrigin.x;
8926 LISTVIEW_DrawTrackLine(infoPtr);
8927 break;
8930 case HDN_ENDTRACKA:
8931 case HDN_ENDTRACKW:
8932 /* remove the track line (if any) */
8933 LISTVIEW_DrawTrackLine(infoPtr);
8934 infoPtr->xTrackLine = -1;
8935 break;
8937 case HDN_ENDDRAG:
8938 FIXME("Changing column order not implemented\n");
8939 return TRUE;
8941 case HDN_ITEMCHANGINGW:
8942 case HDN_ITEMCHANGINGA:
8943 return notify_forward_header(infoPtr, lpnmh);
8945 case HDN_ITEMCHANGEDW:
8946 case HDN_ITEMCHANGEDA:
8948 COLUMN_INFO *lpColumnInfo;
8949 INT dx, cxy;
8951 notify_forward_header(infoPtr, lpnmh);
8952 if (!IsWindow(hwndSelf))
8953 break;
8955 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8957 HDITEMW hdi;
8959 hdi.mask = HDI_WIDTH;
8960 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8961 cxy = hdi.cxy;
8963 else
8964 cxy = lpnmh->pitem->cxy;
8966 /* determine how much we change since the last know position */
8967 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8968 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8969 if (dx != 0)
8971 lpColumnInfo->rcHeader.right += dx;
8972 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8973 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8974 else
8976 /* only needs to update the scrolls */
8977 infoPtr->nItemWidth += dx;
8978 LISTVIEW_UpdateScroll(infoPtr);
8980 LISTVIEW_UpdateItemSize(infoPtr);
8981 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8983 POINT ptOrigin;
8984 RECT rcCol = lpColumnInfo->rcHeader;
8986 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8987 OffsetRect(&rcCol, ptOrigin.x, 0);
8989 rcCol.top = infoPtr->rcList.top;
8990 rcCol.bottom = infoPtr->rcList.bottom;
8992 /* resizing left-aligned columns leaves most of the left side untouched */
8993 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8995 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8996 if (dx > 0)
8997 nMaxDirty += dx;
8998 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9001 /* when shrinking the last column clear the now unused field */
9002 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
9003 rcCol.right -= dx;
9005 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9009 break;
9011 case HDN_ITEMCLICKW:
9012 case HDN_ITEMCLICKA:
9014 /* Handle sorting by Header Column */
9015 NMLISTVIEW nmlv;
9017 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9018 nmlv.iItem = -1;
9019 nmlv.iSubItem = lpnmh->iItem;
9020 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9021 notify_forward_header(infoPtr, lpnmh);
9023 break;
9025 case HDN_DIVIDERDBLCLICKW:
9026 case HDN_DIVIDERDBLCLICKA:
9027 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9028 break;
9031 return 0;
9034 /***
9035 * DESCRIPTION:
9036 * Paint non-client area of control.
9038 * PARAMETER(S):
9039 * [I] infoPtr : valid pointer to the listview structureof the sender
9040 * [I] region : update region
9042 * RETURN:
9043 * TRUE - frame was painted
9044 * FALSE - call default window proc
9046 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9048 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9049 HDC dc;
9050 RECT r;
9051 HRGN cliprgn;
9052 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9053 cyEdge = GetSystemMetrics (SM_CYEDGE);
9055 if (!theme) return FALSE;
9057 GetWindowRect(infoPtr->hwndSelf, &r);
9059 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9060 r.right - cxEdge, r.bottom - cyEdge);
9061 if (region != (HRGN)1)
9062 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9063 OffsetRect(&r, -r.left, -r.top);
9065 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9066 OffsetRect(&r, -r.left, -r.top);
9068 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9069 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9070 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9071 ReleaseDC(infoPtr->hwndSelf, dc);
9073 /* Call default proc to get the scrollbars etc. painted */
9074 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9076 return TRUE;
9079 /***
9080 * DESCRIPTION:
9081 * Determines the type of structure to use.
9083 * PARAMETER(S):
9084 * [I] infoPtr : valid pointer to the listview structureof the sender
9085 * [I] hwndFrom : listview window handle
9086 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9088 * RETURN:
9089 * Zero
9091 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9093 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9095 if (nCommand == NF_REQUERY)
9096 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9098 return infoPtr->notifyFormat;
9101 /***
9102 * DESCRIPTION:
9103 * Paints/Repaints the listview control.
9105 * PARAMETER(S):
9106 * [I] infoPtr : valid pointer to the listview structure
9107 * [I] hdc : device context handle
9109 * RETURN:
9110 * Zero
9112 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9114 TRACE("(hdc=%p)\n", hdc);
9116 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9118 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9120 infoPtr->bNoItemMetrics = FALSE;
9121 LISTVIEW_UpdateItemSize(infoPtr);
9122 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9123 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9124 LISTVIEW_UpdateScroll(infoPtr);
9127 UpdateWindow(infoPtr->hwndHeader);
9129 if (hdc)
9130 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9131 else
9133 PAINTSTRUCT ps;
9135 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9136 if (!hdc) return 1;
9137 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9138 EndPaint(infoPtr->hwndSelf, &ps);
9141 return 0;
9145 /***
9146 * DESCRIPTION:
9147 * Paints/Repaints the listview control.
9149 * PARAMETER(S):
9150 * [I] infoPtr : valid pointer to the listview structure
9151 * [I] hdc : device context handle
9152 * [I] options : drawing options
9154 * RETURN:
9155 * Zero
9157 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9159 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9161 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9162 return 0;
9164 if (options & PRF_ERASEBKGND)
9165 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9167 if (options & PRF_CLIENT)
9168 LISTVIEW_Paint(infoPtr, hdc);
9170 return 0;
9174 /***
9175 * DESCRIPTION:
9176 * Processes double click messages (right mouse button).
9178 * PARAMETER(S):
9179 * [I] infoPtr : valid pointer to the listview structure
9180 * [I] wKey : key flag
9181 * [I] x,y : mouse coordinate
9183 * RETURN:
9184 * Zero
9186 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9188 LVHITTESTINFO lvHitTestInfo;
9190 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9192 /* send NM_RELEASEDCAPTURE notification */
9193 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9195 /* send NM_RDBLCLK notification */
9196 lvHitTestInfo.pt.x = x;
9197 lvHitTestInfo.pt.y = y;
9198 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9199 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9201 return 0;
9204 /***
9205 * DESCRIPTION:
9206 * Processes mouse down messages (right mouse button).
9208 * PARAMETER(S):
9209 * [I] infoPtr : valid pointer to the listview structure
9210 * [I] wKey : key flag
9211 * [I] x,y : mouse coordinate
9213 * RETURN:
9214 * Zero
9216 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9218 LVHITTESTINFO lvHitTestInfo;
9219 INT nItem;
9221 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9223 /* send NM_RELEASEDCAPTURE notification */
9224 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9226 /* make sure the listview control window has the focus */
9227 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9229 /* set right button down flag */
9230 infoPtr->bRButtonDown = TRUE;
9232 /* determine the index of the selected item */
9233 lvHitTestInfo.pt.x = x;
9234 lvHitTestInfo.pt.y = y;
9235 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9237 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9239 LISTVIEW_SetItemFocus(infoPtr, nItem);
9240 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9241 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9242 LISTVIEW_SetSelection(infoPtr, nItem);
9244 else
9246 LISTVIEW_DeselectAll(infoPtr);
9249 return 0;
9252 /***
9253 * DESCRIPTION:
9254 * Processes mouse up messages (right mouse button).
9256 * PARAMETER(S):
9257 * [I] infoPtr : valid pointer to the listview structure
9258 * [I] wKey : key flag
9259 * [I] x,y : mouse coordinate
9261 * RETURN:
9262 * Zero
9264 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9266 LVHITTESTINFO lvHitTestInfo;
9267 POINT pt;
9269 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9271 if (!infoPtr->bRButtonDown) return 0;
9273 /* set button flag */
9274 infoPtr->bRButtonDown = FALSE;
9276 /* Send NM_RClICK notification */
9277 lvHitTestInfo.pt.x = x;
9278 lvHitTestInfo.pt.y = y;
9279 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9280 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9282 /* Change to screen coordinate for WM_CONTEXTMENU */
9283 pt = lvHitTestInfo.pt;
9284 ClientToScreen(infoPtr->hwndSelf, &pt);
9286 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9287 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9288 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9290 return 0;
9294 /***
9295 * DESCRIPTION:
9296 * Sets the cursor.
9298 * PARAMETER(S):
9299 * [I] infoPtr : valid pointer to the listview structure
9300 * [I] hwnd : window handle of window containing the cursor
9301 * [I] nHittest : hit-test code
9302 * [I] wMouseMsg : ideintifier of the mouse message
9304 * RETURN:
9305 * TRUE if cursor is set
9306 * FALSE otherwise
9308 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9310 LVHITTESTINFO lvHitTestInfo;
9312 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9314 if(!infoPtr->hHotCursor) return FALSE;
9316 GetCursorPos(&lvHitTestInfo.pt);
9317 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9319 SetCursor(infoPtr->hHotCursor);
9321 return TRUE;
9324 /***
9325 * DESCRIPTION:
9326 * Sets the focus.
9328 * PARAMETER(S):
9329 * [I] infoPtr : valid pointer to the listview structure
9330 * [I] hwndLoseFocus : handle of previously focused window
9332 * RETURN:
9333 * Zero
9335 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9337 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9339 /* if we have the focus already, there's nothing to do */
9340 if (infoPtr->bFocus) return 0;
9342 /* send NM_SETFOCUS notification */
9343 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9345 /* set window focus flag */
9346 infoPtr->bFocus = TRUE;
9348 /* put the focus rect back on */
9349 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9351 /* redraw all visible selected items */
9352 LISTVIEW_InvalidateSelectedItems(infoPtr);
9354 return 0;
9357 /***
9358 * DESCRIPTION:
9359 * Sets the font.
9361 * PARAMETER(S):
9362 * [I] infoPtr : valid pointer to the listview structure
9363 * [I] fRedraw : font handle
9364 * [I] fRedraw : redraw flag
9366 * RETURN:
9367 * Zero
9369 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9371 HFONT oldFont = infoPtr->hFont;
9373 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9375 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9376 if (infoPtr->hFont == oldFont) return 0;
9378 LISTVIEW_SaveTextMetrics(infoPtr);
9380 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9382 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9383 LISTVIEW_UpdateSize(infoPtr);
9384 LISTVIEW_UpdateScroll(infoPtr);
9387 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9389 return 0;
9392 /***
9393 * DESCRIPTION:
9394 * Message handling for WM_SETREDRAW.
9395 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9397 * PARAMETER(S):
9398 * [I] infoPtr : valid pointer to the listview structure
9399 * [I] bRedraw: state of redraw flag
9401 * RETURN:
9402 * DefWinProc return value
9404 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9406 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9408 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9409 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9411 infoPtr->bRedraw = bRedraw;
9413 if(!bRedraw) return 0;
9415 if (is_autoarrange(infoPtr))
9416 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9417 LISTVIEW_UpdateScroll(infoPtr);
9419 /* despite what the WM_SETREDRAW docs says, apps expect us
9420 * to invalidate the listview here... stupid! */
9421 LISTVIEW_InvalidateList(infoPtr);
9423 return 0;
9426 /***
9427 * DESCRIPTION:
9428 * Resizes the listview control. This function processes WM_SIZE
9429 * messages. At this time, the width and height are not used.
9431 * PARAMETER(S):
9432 * [I] infoPtr : valid pointer to the listview structure
9433 * [I] Width : new width
9434 * [I] Height : new height
9436 * RETURN:
9437 * Zero
9439 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9441 RECT rcOld = infoPtr->rcList;
9443 TRACE("(width=%d, height=%d)\n", Width, Height);
9445 LISTVIEW_UpdateSize(infoPtr);
9446 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9448 /* do not bother with display related stuff if we're not redrawing */
9449 if (!is_redrawing(infoPtr)) return 0;
9451 if (is_autoarrange(infoPtr))
9452 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9454 LISTVIEW_UpdateScroll(infoPtr);
9456 /* refresh all only for lists whose height changed significantly */
9457 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9458 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9459 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9460 LISTVIEW_InvalidateList(infoPtr);
9462 return 0;
9465 /***
9466 * DESCRIPTION:
9467 * Sets the size information.
9469 * PARAMETER(S):
9470 * [I] infoPtr : valid pointer to the listview structure
9472 * RETURN:
9473 * None
9475 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9477 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9479 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9481 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9483 if (uView == LVS_LIST)
9485 /* Apparently the "LIST" style is supposed to have the same
9486 * number of items in a column even if there is no scroll bar.
9487 * Since if a scroll bar already exists then the bottom is already
9488 * reduced, only reduce if the scroll bar does not currently exist.
9489 * The "2" is there to mimic the native control. I think it may be
9490 * related to either padding or edges. (GLA 7/2002)
9492 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9493 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9494 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9496 else if (uView == LVS_REPORT)
9498 HDLAYOUT hl;
9499 WINDOWPOS wp;
9501 hl.prc = &infoPtr->rcList;
9502 hl.pwpos = &wp;
9503 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9504 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9505 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9506 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9507 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9508 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9510 infoPtr->rcList.top = max(wp.cy, 0);
9511 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9514 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9517 /***
9518 * DESCRIPTION:
9519 * Processes WM_STYLECHANGED messages.
9521 * PARAMETER(S):
9522 * [I] infoPtr : valid pointer to the listview structure
9523 * [I] wStyleType : window style type (normal or extended)
9524 * [I] lpss : window style information
9526 * RETURN:
9527 * Zero
9529 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9530 const STYLESTRUCT *lpss)
9532 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9533 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9534 UINT style;
9536 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9537 wStyleType, lpss->styleOld, lpss->styleNew);
9539 if (wStyleType != GWL_STYLE) return 0;
9541 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9542 /* what if LVS_OWNERDATA changed? */
9543 /* or LVS_SINGLESEL */
9544 /* or LVS_SORT{AS,DES}CENDING */
9546 infoPtr->dwStyle = lpss->styleNew;
9548 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9549 ((lpss->styleNew & WS_HSCROLL) == 0))
9550 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9552 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9553 ((lpss->styleNew & WS_VSCROLL) == 0))
9554 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9556 if (uNewView != uOldView)
9558 SIZE oldIconSize = infoPtr->iconSize;
9559 HIMAGELIST himl;
9561 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9562 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9564 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9565 SetRectEmpty(&infoPtr->rcFocus);
9567 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9568 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9570 if (uNewView == LVS_ICON)
9572 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9574 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9575 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9576 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9579 else if (uNewView == LVS_REPORT)
9581 HDLAYOUT hl;
9582 WINDOWPOS wp;
9584 LISTVIEW_CreateHeader( infoPtr );
9586 hl.prc = &infoPtr->rcList;
9587 hl.pwpos = &wp;
9588 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9589 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9590 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9591 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9594 LISTVIEW_UpdateItemSize(infoPtr);
9597 if (uNewView == LVS_REPORT)
9599 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9601 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9603 /* Turn off the header control */
9604 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9605 TRACE("Hide header control, was 0x%08x\n", style);
9606 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9607 } else {
9608 /* Turn on the header control */
9609 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9611 TRACE("Show header control, was 0x%08x\n", style);
9612 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9618 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9619 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9620 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9622 /* update the size of the client area */
9623 LISTVIEW_UpdateSize(infoPtr);
9625 /* add scrollbars if needed */
9626 LISTVIEW_UpdateScroll(infoPtr);
9628 /* invalidate client area + erase background */
9629 LISTVIEW_InvalidateList(infoPtr);
9631 return 0;
9634 /***
9635 * DESCRIPTION:
9636 * Processes WM_SHOWWINDOW messages.
9638 * PARAMETER(S):
9639 * [I] infoPtr : valid pointer to the listview structure
9640 * [I] bShown : window is being shown (FALSE when hidden)
9641 * [I] iStatus : window show status
9643 * RETURN:
9644 * Zero
9646 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9648 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9650 /* header delayed creation */
9651 if ((uView == LVS_REPORT) && bShown)
9653 LISTVIEW_CreateHeader(infoPtr);
9655 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9656 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9659 return 0;
9662 /***
9663 * DESCRIPTION:
9664 * Window procedure of the listview control.
9667 static LRESULT WINAPI
9668 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9670 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9672 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9674 if (!infoPtr && (uMsg != WM_NCCREATE))
9675 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9677 switch (uMsg)
9679 case LVM_APPROXIMATEVIEWRECT:
9680 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9681 LOWORD(lParam), HIWORD(lParam));
9682 case LVM_ARRANGE:
9683 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9685 /* case LVM_CANCELEDITLABEL: */
9687 case LVM_CREATEDRAGIMAGE:
9688 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9690 case LVM_DELETEALLITEMS:
9691 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9693 case LVM_DELETECOLUMN:
9694 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9696 case LVM_DELETEITEM:
9697 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9699 case LVM_EDITLABELW:
9700 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9702 case LVM_EDITLABELA:
9703 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9705 /* case LVM_ENABLEGROUPVIEW: */
9707 case LVM_ENSUREVISIBLE:
9708 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9710 case LVM_FINDITEMW:
9711 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9713 case LVM_FINDITEMA:
9714 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9716 case LVM_GETBKCOLOR:
9717 return infoPtr->clrBk;
9719 /* case LVM_GETBKIMAGE: */
9721 case LVM_GETCALLBACKMASK:
9722 return infoPtr->uCallbackMask;
9724 case LVM_GETCOLUMNA:
9725 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9727 case LVM_GETCOLUMNW:
9728 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9730 case LVM_GETCOLUMNORDERARRAY:
9731 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9733 case LVM_GETCOLUMNWIDTH:
9734 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9736 case LVM_GETCOUNTPERPAGE:
9737 return LISTVIEW_GetCountPerPage(infoPtr);
9739 case LVM_GETEDITCONTROL:
9740 return (LRESULT)infoPtr->hwndEdit;
9742 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9743 return infoPtr->dwLvExStyle;
9745 /* case LVM_GETGROUPINFO: */
9747 /* case LVM_GETGROUPMETRICS: */
9749 case LVM_GETHEADER:
9750 return (LRESULT)infoPtr->hwndHeader;
9752 case LVM_GETHOTCURSOR:
9753 return (LRESULT)infoPtr->hHotCursor;
9755 case LVM_GETHOTITEM:
9756 return infoPtr->nHotItem;
9758 case LVM_GETHOVERTIME:
9759 return infoPtr->dwHoverTime;
9761 case LVM_GETIMAGELIST:
9762 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9764 /* case LVM_GETINSERTMARK: */
9766 /* case LVM_GETINSERTMARKCOLOR: */
9768 /* case LVM_GETINSERTMARKRECT: */
9770 case LVM_GETISEARCHSTRINGA:
9771 case LVM_GETISEARCHSTRINGW:
9772 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9773 return FALSE;
9775 case LVM_GETITEMA:
9776 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9778 case LVM_GETITEMW:
9779 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9781 case LVM_GETITEMCOUNT:
9782 return infoPtr->nItemCount;
9784 case LVM_GETITEMPOSITION:
9785 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9787 case LVM_GETITEMRECT:
9788 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9790 case LVM_GETITEMSPACING:
9791 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9793 case LVM_GETITEMSTATE:
9794 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9796 case LVM_GETITEMTEXTA:
9797 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9799 case LVM_GETITEMTEXTW:
9800 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9802 case LVM_GETNEXTITEM:
9803 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9805 case LVM_GETNUMBEROFWORKAREAS:
9806 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9807 return 1;
9809 case LVM_GETORIGIN:
9810 if (!lParam) return FALSE;
9811 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9812 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9813 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9814 return TRUE;
9816 /* case LVM_GETOUTLINECOLOR: */
9818 /* case LVM_GETSELECTEDCOLUMN: */
9820 case LVM_GETSELECTEDCOUNT:
9821 return LISTVIEW_GetSelectedCount(infoPtr);
9823 case LVM_GETSELECTIONMARK:
9824 return infoPtr->nSelectionMark;
9826 case LVM_GETSTRINGWIDTHA:
9827 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9829 case LVM_GETSTRINGWIDTHW:
9830 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9832 case LVM_GETSUBITEMRECT:
9833 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9835 case LVM_GETTEXTBKCOLOR:
9836 return infoPtr->clrTextBk;
9838 case LVM_GETTEXTCOLOR:
9839 return infoPtr->clrText;
9841 /* case LVM_GETTILEINFO: */
9843 /* case LVM_GETTILEVIEWINFO: */
9845 case LVM_GETTOOLTIPS:
9846 if( !infoPtr->hwndToolTip )
9847 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9848 return (LRESULT)infoPtr->hwndToolTip;
9850 case LVM_GETTOPINDEX:
9851 return LISTVIEW_GetTopIndex(infoPtr);
9853 case LVM_GETUNICODEFORMAT:
9854 return (infoPtr->notifyFormat == NFR_UNICODE);
9856 /* case LVM_GETVIEW: */
9858 case LVM_GETVIEWRECT:
9859 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9861 case LVM_GETWORKAREAS:
9862 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9863 return FALSE;
9865 /* case LVM_HASGROUP: */
9867 case LVM_HITTEST:
9868 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9870 case LVM_INSERTCOLUMNA:
9871 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9873 case LVM_INSERTCOLUMNW:
9874 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9876 /* case LVM_INSERTGROUP: */
9878 /* case LVM_INSERTGROUPSORTED: */
9880 case LVM_INSERTITEMA:
9881 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9883 case LVM_INSERTITEMW:
9884 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9886 /* case LVM_INSERTMARKHITTEST: */
9888 /* case LVM_ISGROUPVIEWENABLED: */
9890 /* case LVM_MAPIDTOINDEX: */
9892 /* case LVM_MAPINDEXTOID: */
9894 /* case LVM_MOVEGROUP: */
9896 /* case LVM_MOVEITEMTOGROUP: */
9898 case LVM_REDRAWITEMS:
9899 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9901 /* case LVM_REMOVEALLGROUPS: */
9903 /* case LVM_REMOVEGROUP: */
9905 case LVM_SCROLL:
9906 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9908 case LVM_SETBKCOLOR:
9909 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9911 /* case LVM_SETBKIMAGE: */
9913 case LVM_SETCALLBACKMASK:
9914 infoPtr->uCallbackMask = (UINT)wParam;
9915 return TRUE;
9917 case LVM_SETCOLUMNA:
9918 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9920 case LVM_SETCOLUMNW:
9921 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9923 case LVM_SETCOLUMNORDERARRAY:
9924 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9926 case LVM_SETCOLUMNWIDTH:
9927 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9929 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9930 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9932 /* case LVM_SETGROUPINFO: */
9934 /* case LVM_SETGROUPMETRICS: */
9936 case LVM_SETHOTCURSOR:
9937 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9939 case LVM_SETHOTITEM:
9940 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9942 case LVM_SETHOVERTIME:
9943 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9945 case LVM_SETICONSPACING:
9946 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9948 case LVM_SETIMAGELIST:
9949 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9951 /* case LVM_SETINFOTIP: */
9953 /* case LVM_SETINSERTMARK: */
9955 /* case LVM_SETINSERTMARKCOLOR: */
9957 case LVM_SETITEMA:
9958 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9960 case LVM_SETITEMW:
9961 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9963 case LVM_SETITEMCOUNT:
9964 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9966 case LVM_SETITEMPOSITION:
9968 POINT pt;
9969 pt.x = (short)LOWORD(lParam);
9970 pt.y = (short)HIWORD(lParam);
9971 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9974 case LVM_SETITEMPOSITION32:
9975 if (lParam == 0) return FALSE;
9976 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9978 case LVM_SETITEMSTATE:
9979 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9981 case LVM_SETITEMTEXTA:
9982 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9984 case LVM_SETITEMTEXTW:
9985 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9987 /* case LVM_SETOUTLINECOLOR: */
9989 /* case LVM_SETSELECTEDCOLUMN: */
9991 case LVM_SETSELECTIONMARK:
9992 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9994 case LVM_SETTEXTBKCOLOR:
9995 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9997 case LVM_SETTEXTCOLOR:
9998 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10000 /* case LVM_SETTILEINFO: */
10002 /* case LVM_SETTILEVIEWINFO: */
10004 /* case LVM_SETTILEWIDTH: */
10006 case LVM_SETTOOLTIPS:
10007 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10009 case LVM_SETUNICODEFORMAT:
10010 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10012 /* case LVM_SETVIEW: */
10014 /* case LVM_SETWORKAREAS: */
10016 /* case LVM_SORTGROUPS: */
10018 case LVM_SORTITEMS:
10019 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
10021 /* LVM_SORTITEMSEX: */
10023 case LVM_SUBITEMHITTEST:
10024 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10026 case LVM_UPDATE:
10027 return LISTVIEW_Update(infoPtr, (INT)wParam);
10029 case WM_CHAR:
10030 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10032 case WM_COMMAND:
10033 return LISTVIEW_Command(infoPtr, wParam, lParam);
10035 case WM_NCCREATE:
10036 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10038 case WM_CREATE:
10039 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10041 case WM_DESTROY:
10042 return LISTVIEW_Destroy(infoPtr);
10044 case WM_ENABLE:
10045 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10047 case WM_ERASEBKGND:
10048 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10050 case WM_GETDLGCODE:
10051 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10053 case WM_GETFONT:
10054 return (LRESULT)infoPtr->hFont;
10056 case WM_HSCROLL:
10057 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10059 case WM_KEYDOWN:
10060 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10062 case WM_KILLFOCUS:
10063 return LISTVIEW_KillFocus(infoPtr);
10065 case WM_LBUTTONDBLCLK:
10066 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10068 case WM_LBUTTONDOWN:
10069 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10071 case WM_LBUTTONUP:
10072 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10074 case WM_MOUSEMOVE:
10075 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10077 case WM_MOUSEHOVER:
10078 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10080 case WM_NCDESTROY:
10081 return LISTVIEW_NCDestroy(infoPtr);
10083 case WM_NCPAINT:
10084 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10085 return 0;
10086 goto fwd_msg;
10088 case WM_NOTIFY:
10089 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10090 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10091 else return 0;
10093 case WM_NOTIFYFORMAT:
10094 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10096 case WM_PRINTCLIENT:
10097 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10099 case WM_PAINT:
10100 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10102 case WM_RBUTTONDBLCLK:
10103 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10105 case WM_RBUTTONDOWN:
10106 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10108 case WM_RBUTTONUP:
10109 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10111 case WM_SETCURSOR:
10112 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10113 return TRUE;
10114 goto fwd_msg;
10116 case WM_SETFOCUS:
10117 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10119 case WM_SETFONT:
10120 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10122 case WM_SETREDRAW:
10123 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10125 case WM_SHOWWINDOW:
10126 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10127 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10129 case WM_SIZE:
10130 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10132 case WM_STYLECHANGED:
10133 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10135 case WM_SYSCOLORCHANGE:
10136 COMCTL32_RefreshSysColors();
10137 return 0;
10139 /* case WM_TIMER: */
10140 case WM_THEMECHANGED:
10141 return LISTVIEW_ThemeChanged(infoPtr);
10143 case WM_VSCROLL:
10144 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10146 case WM_MOUSEWHEEL:
10147 if (wParam & (MK_SHIFT | MK_CONTROL))
10148 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10149 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10151 case WM_WINDOWPOSCHANGED:
10152 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10154 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10155 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10156 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10158 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10160 MEASUREITEMSTRUCT mis;
10161 mis.CtlType = ODT_LISTVIEW;
10162 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10163 mis.itemID = -1;
10164 mis.itemWidth = 0;
10165 mis.itemData = 0;
10166 mis.itemHeight= infoPtr->nItemHeight;
10167 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10168 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10169 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10172 LISTVIEW_UpdateSize(infoPtr);
10173 LISTVIEW_UpdateScroll(infoPtr);
10175 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10177 /* case WM_WININICHANGE: */
10179 default:
10180 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10181 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10183 fwd_msg:
10184 /* call default window procedure */
10185 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10190 /***
10191 * DESCRIPTION:
10192 * Registers the window class.
10194 * PARAMETER(S):
10195 * None
10197 * RETURN:
10198 * None
10200 void LISTVIEW_Register(void)
10202 WNDCLASSW wndClass;
10204 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10205 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10206 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10207 wndClass.cbClsExtra = 0;
10208 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10209 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10210 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10211 wndClass.lpszClassName = WC_LISTVIEWW;
10212 RegisterClassW(&wndClass);
10215 /***
10216 * DESCRIPTION:
10217 * Unregisters the window class.
10219 * PARAMETER(S):
10220 * None
10222 * RETURN:
10223 * None
10225 void LISTVIEW_Unregister(void)
10227 UnregisterClassW(WC_LISTVIEWW, NULL);
10230 /***
10231 * DESCRIPTION:
10232 * Handle any WM_COMMAND messages
10234 * PARAMETER(S):
10235 * [I] infoPtr : valid pointer to the listview structure
10236 * [I] wParam : the first message parameter
10237 * [I] lParam : the second message parameter
10239 * RETURN:
10240 * Zero.
10242 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10244 switch (HIWORD(wParam))
10246 case EN_UPDATE:
10249 * Adjust the edit window size
10251 WCHAR buffer[1024];
10252 HDC hdc = GetDC(infoPtr->hwndEdit);
10253 HFONT hFont, hOldFont = 0;
10254 RECT rect;
10255 SIZE sz;
10257 if (!infoPtr->hwndEdit || !hdc) return 0;
10258 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10259 GetWindowRect(infoPtr->hwndEdit, &rect);
10261 /* Select font to get the right dimension of the string */
10262 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10263 if(hFont != 0)
10265 hOldFont = SelectObject(hdc, hFont);
10268 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10270 TEXTMETRICW textMetric;
10272 /* Add Extra spacing for the next character */
10273 GetTextMetricsW(hdc, &textMetric);
10274 sz.cx += (textMetric.tmMaxCharWidth * 2);
10276 SetWindowPos (
10277 infoPtr->hwndEdit,
10278 HWND_TOP,
10281 sz.cx,
10282 rect.bottom - rect.top,
10283 SWP_DRAWFRAME|SWP_NOMOVE);
10285 if(hFont != 0)
10286 SelectObject(hdc, hOldFont);
10288 ReleaseDC(infoPtr->hwndEdit, hdc);
10290 break;
10293 default:
10294 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10297 return 0;
10301 /***
10302 * DESCRIPTION:
10303 * Subclassed edit control windproc function
10305 * PARAMETER(S):
10306 * [I] hwnd : the edit window handle
10307 * [I] uMsg : the message that is to be processed
10308 * [I] wParam : first message parameter
10309 * [I] lParam : second message parameter
10310 * [I] isW : TRUE if input is Unicode
10312 * RETURN:
10313 * Zero.
10315 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10317 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10318 BOOL cancel = FALSE;
10320 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10321 hwnd, uMsg, wParam, lParam, isW);
10323 switch (uMsg)
10325 case WM_GETDLGCODE:
10326 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10328 case WM_KILLFOCUS:
10329 break;
10331 case WM_DESTROY:
10333 WNDPROC editProc = infoPtr->EditWndProc;
10334 infoPtr->EditWndProc = 0;
10335 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10336 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10339 case WM_KEYDOWN:
10340 if (VK_ESCAPE == (INT)wParam)
10342 cancel = TRUE;
10343 break;
10345 else if (VK_RETURN == (INT)wParam)
10346 break;
10348 default:
10349 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10352 /* kill the edit */
10353 if (infoPtr->hwndEdit)
10355 LPWSTR buffer = NULL;
10357 infoPtr->hwndEdit = 0;
10358 if (!cancel)
10360 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10362 if (len)
10364 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10366 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10367 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10371 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10373 Free(buffer);
10376 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10377 return 0;
10380 /***
10381 * DESCRIPTION:
10382 * Subclassed edit control Unicode windproc function
10384 * PARAMETER(S):
10385 * [I] hwnd : the edit window handle
10386 * [I] uMsg : the message that is to be processed
10387 * [I] wParam : first message parameter
10388 * [I] lParam : second message parameter
10390 * RETURN:
10392 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10394 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10397 /***
10398 * DESCRIPTION:
10399 * Subclassed edit control ANSI windproc function
10401 * PARAMETER(S):
10402 * [I] hwnd : the edit window handle
10403 * [I] uMsg : the message that is to be processed
10404 * [I] wParam : first message parameter
10405 * [I] lParam : second message parameter
10407 * RETURN:
10409 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10411 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10414 /***
10415 * DESCRIPTION:
10416 * Creates a subclassed edit control
10418 * PARAMETER(S):
10419 * [I] infoPtr : valid pointer to the listview structure
10420 * [I] text : initial text for the edit
10421 * [I] style : the window style
10422 * [I] isW : TRUE if input is Unicode
10424 * RETURN:
10426 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10427 INT x, INT y, INT width, INT height, BOOL isW)
10429 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10430 HWND hedit;
10431 SIZE sz;
10432 HDC hdc;
10433 HDC hOldFont=0;
10434 TEXTMETRICW textMetric;
10435 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10437 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10439 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10440 hdc = GetDC(infoPtr->hwndSelf);
10442 /* Select the font to get appropriate metric dimensions */
10443 if(infoPtr->hFont != 0)
10444 hOldFont = SelectObject(hdc, infoPtr->hFont);
10446 /*Get String Length in pixels */
10447 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10449 /*Add Extra spacing for the next character */
10450 GetTextMetricsW(hdc, &textMetric);
10451 sz.cx += (textMetric.tmMaxCharWidth * 2);
10453 if(infoPtr->hFont != 0)
10454 SelectObject(hdc, hOldFont);
10456 ReleaseDC(infoPtr->hwndSelf, hdc);
10457 if (isW)
10458 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10459 else
10460 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10462 if (!hedit) return 0;
10464 infoPtr->EditWndProc = (WNDPROC)
10465 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10466 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10468 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10470 return hedit;