ntdll: move relocations from mapping into loader
[wine/kumbayo.git] / dlls / comctl32 / listview.c
blob3abdd234b1181ca412e547c0256279ac2bf30328
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
63 * Speedups
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
73 * Flags
74 * -- LVIF_COLUMNS
75 * -- LVIF_GROUPID
76 * -- LVIF_NORECOMPUTE
78 * States
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVIS_CUT
81 * -- LVIS_DROPHILITED
82 * -- LVIS_OVERLAYMASK
84 * Styles
85 * -- LVS_NOLABELWRAP
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
88 * -- LVS_ALIGNTOP
89 * -- LVS_TYPESTYLEMASK
91 * Extended Styles
92 * -- LVS_EX_BORDERSELECT
93 * -- LVS_EX_FLATSB
94 * -- LVS_EX_GRIDLINES
95 * -- LVS_EX_HEADERDRAGDROP
96 * -- LVS_EX_INFOTIP
97 * -- LVS_EX_LABELTIP
98 * -- LVS_EX_MULTIWORKAREAS
99 * -- LVS_EX_ONECLICKACTIVATE
100 * -- LVS_EX_REGIONAL
101 * -- LVS_EX_SIMPLESELECT
102 * -- LVS_EX_TRACKSELECT
103 * -- LVS_EX_TWOCLICKACTIVATE
104 * -- LVS_EX_UNDERLINECOLD
105 * -- LVS_EX_UNDERLINEHOT
107 * Notifications:
108 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
109 * -- LVN_GETINFOTIP
110 * -- LVN_HOTTRACK
111 * -- LVN_MARQUEEBEGIN
112 * -- LVN_ODFINDITEM
113 * -- LVN_SETDISPINFO
114 * -- NM_HOVER
115 * -- LVN_BEGINRDRAG
117 * Messages:
118 * -- LVM_CANCELEDITLABEL
119 * -- LVM_ENABLEGROUPVIEW
120 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
121 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
122 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
123 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
124 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
125 * -- LVM_GETINSERTMARKRECT
126 * -- LVM_GETNUMBEROFWORKAREAS
127 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
128 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
129 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
130 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
131 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
132 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
133 * -- LVM_GETVIEW, LVM_SETVIEW
134 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
135 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
136 * -- LVM_INSERTGROUPSORTED
137 * -- LVM_INSERTMARKHITTEST
138 * -- LVM_ISGROUPVIEWENABLED
139 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
140 * -- LVM_MOVEGROUP
141 * -- LVM_MOVEITEMTOGROUP
142 * -- LVM_SETINFOTIP
143 * -- LVM_SETTILEWIDTH
144 * -- LVM_SORTGROUPS
145 * -- LVM_SORTITEMSEX
147 * Macros:
148 * -- ListView_GetCheckSate, ListView_SetCheckState
149 * -- ListView_GetHoverTime, ListView_SetHoverTime
150 * -- ListView_GetISearchString
151 * -- ListView_GetNumberOfWorkAreas
152 * -- ListView_GetOrigin
153 * -- ListView_GetTextBkColor
154 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
155 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
156 * -- ListView_SortItemsEx
158 * Functions:
159 * -- LVGroupComparE
161 * Known differences in message stream from native control (not known if
162 * these differences cause problems):
163 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
164 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
165 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
166 * processing for "USEDOUBLECLICKTIME".
169 #include "config.h"
170 #include "wine/port.h"
172 #include <assert.h>
173 #include <ctype.h>
174 #include <string.h>
175 #include <stdlib.h>
176 #include <stdarg.h>
177 #include <stdio.h>
179 #include "windef.h"
180 #include "winbase.h"
181 #include "winnt.h"
182 #include "wingdi.h"
183 #include "winuser.h"
184 #include "winnls.h"
185 #include "commctrl.h"
186 #include "comctl32.h"
187 #include "uxtheme.h"
189 #include "wine/debug.h"
190 #include "wine/unicode.h"
192 WINE_DEFAULT_DEBUG_CHANNEL(listview);
194 /* make sure you set this to 0 for production use! */
195 #define DEBUG_RANGES 1
197 typedef struct tagCOLUMN_INFO
199 RECT rcHeader; /* tracks the header's rectangle */
200 int fmt; /* same as LVCOLUMN.fmt */
201 } COLUMN_INFO;
203 typedef struct tagITEMHDR
205 LPWSTR pszText;
206 INT iImage;
207 } ITEMHDR, *LPITEMHDR;
209 typedef struct tagSUBITEM_INFO
211 ITEMHDR hdr;
212 INT iSubItem;
213 } SUBITEM_INFO;
215 typedef struct tagITEM_INFO
217 ITEMHDR hdr;
218 UINT state;
219 LPARAM lParam;
220 INT iIndent;
221 } ITEM_INFO;
223 typedef struct tagRANGE
225 INT lower;
226 INT upper;
227 } RANGE;
229 typedef struct tagRANGES
231 HDPA hdpa;
232 } *RANGES;
234 typedef struct tagITERATOR
236 INT nItem;
237 INT nSpecial;
238 RANGE range;
239 RANGES ranges;
240 INT index;
241 } ITERATOR;
243 typedef struct tagDELAYED_ITEM_EDIT
245 BOOL fEnabled;
246 INT iItem;
247 } DELAYED_ITEM_EDIT;
249 typedef struct tagLISTVIEW_INFO
251 HWND hwndSelf;
252 HBRUSH hBkBrush;
253 COLORREF clrBk;
254 COLORREF clrText;
255 COLORREF clrTextBk;
256 HIMAGELIST himlNormal;
257 HIMAGELIST himlSmall;
258 HIMAGELIST himlState;
259 BOOL bLButtonDown;
260 BOOL bRButtonDown;
261 POINT ptClickPos; /* point where the user clicked */
262 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
263 INT nItemHeight;
264 INT nItemWidth;
265 RANGES selectionRanges;
266 INT nSelectionMark;
267 INT nHotItem;
268 SHORT notifyFormat;
269 HWND hwndNotify;
270 RECT rcList; /* This rectangle is really the window
271 * client rectangle possibly reduced by the
272 * horizontal scroll bar and/or header - see
273 * LISTVIEW_UpdateSize. This rectangle offset
274 * by the LISTVIEW_GetOrigin value is in
275 * client coordinates */
276 SIZE iconSize;
277 SIZE iconSpacing;
278 SIZE iconStateSize;
279 UINT uCallbackMask;
280 HWND hwndHeader;
281 HCURSOR hHotCursor;
282 HFONT hDefaultFont;
283 HFONT hFont;
284 INT ntmHeight; /* Some cached metrics of the font used */
285 INT ntmMaxCharWidth; /* by the listview to draw items */
286 INT nEllipsisWidth;
287 BOOL bRedraw; /* Turns on/off repaints & invalidations */
288 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
289 BOOL bFocus;
290 BOOL bDoChangeNotify; /* send change notification messages? */
291 INT nFocusedItem;
292 RECT rcFocus;
293 DWORD dwStyle; /* the cached window GWL_STYLE */
294 DWORD dwLvExStyle; /* extended listview style */
295 INT nItemCount; /* the number of items in the list */
296 HDPA hdpaItems; /* array ITEM_INFO pointers */
297 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
298 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
299 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
300 POINT currIconPos; /* this is the position next icon will be placed */
301 PFNLVCOMPARE pfnCompare;
302 LPARAM lParamSort;
303 HWND hwndEdit;
304 WNDPROC EditWndProc;
305 INT nEditLabelItem;
306 DWORD dwHoverTime;
307 HWND hwndToolTip;
309 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
311 DWORD lastKeyPressTimestamp;
312 WPARAM charCode;
313 INT nSearchParamLength;
314 WCHAR szSearchParam[ MAX_PATH ];
315 BOOL bIsDrawing;
316 INT nMeasureItemHeight;
317 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
318 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
319 } LISTVIEW_INFO;
322 * constants
324 /* How many we debug buffer to allocate */
325 #define DEBUG_BUFFERS 20
326 /* The size of a single debug bbuffer */
327 #define DEBUG_BUFFER_SIZE 256
329 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
330 #define SB_INTERNAL -1
332 /* maximum size of a label */
333 #define DISP_TEXT_SIZE 512
335 /* padding for items in list and small icon display modes */
336 #define WIDTH_PADDING 12
338 /* padding for items in list, report and small icon display modes */
339 #define HEIGHT_PADDING 1
341 /* offset of items in report display mode */
342 #define REPORT_MARGINX 2
344 /* padding for icon in large icon display mode
345 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
346 * that HITTEST will see.
347 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
348 * ICON_TOP_PADDING - sum of the two above.
349 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
350 * LABEL_HOR_PADDING - between text and sides of box
351 * LABEL_VERT_PADDING - between bottom of text and end of box
353 * ICON_LR_PADDING - additional width above icon size.
354 * ICON_LR_HALF - half of the above value
356 #define ICON_TOP_PADDING_NOTHITABLE 2
357 #define ICON_TOP_PADDING_HITABLE 2
358 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
359 #define ICON_BOTTOM_PADDING 4
360 #define LABEL_HOR_PADDING 5
361 #define LABEL_VERT_PADDING 7
362 #define ICON_LR_PADDING 16
363 #define ICON_LR_HALF (ICON_LR_PADDING/2)
365 /* default label width for items in list and small icon display modes */
366 #define DEFAULT_LABEL_WIDTH 40
368 /* default column width for items in list display mode */
369 #define DEFAULT_COLUMN_WIDTH 128
371 /* Size of "line" scroll for V & H scrolls */
372 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
374 /* Padding betwen image and label */
375 #define IMAGE_PADDING 2
377 /* Padding behind the label */
378 #define TRAILING_LABEL_PADDING 12
379 #define TRAILING_HEADER_PADDING 11
381 /* Border for the icon caption */
382 #define CAPTION_BORDER 2
384 /* Standard DrawText flags */
385 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
387 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
389 /* Image index from state */
390 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
392 /* The time in milliseconds to reset the search in the list */
393 #define KEY_DELAY 450
395 /* Dump the LISTVIEW_INFO structure to the debug channel */
396 #define LISTVIEW_DUMP(iP) do { \
397 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
398 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
399 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
400 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
401 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
402 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
403 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
404 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
405 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
406 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
407 } while(0)
409 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
412 * forward declarations
414 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
415 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
416 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
417 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
418 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
419 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
420 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
421 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
422 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
423 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
424 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
425 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
426 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
427 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
428 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
429 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
430 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
431 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
432 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
433 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
434 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
435 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
436 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
437 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
438 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
439 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
440 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
442 /******** Text handling functions *************************************/
444 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
445 * text string. The string may be ANSI or Unicode, in which case
446 * the boolean isW tells us the type of the string.
448 * The name of the function tell what type of strings it expects:
449 * W: Unicode, T: ANSI/Unicode - function of isW
452 static inline BOOL is_textW(LPCWSTR text)
454 return text != NULL && text != LPSTR_TEXTCALLBACKW;
457 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
459 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
460 return is_textW(text);
463 static inline int textlenT(LPCWSTR text, BOOL isW)
465 return !is_textT(text, isW) ? 0 :
466 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
469 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
471 if (isDestW)
472 if (isSrcW) lstrcpynW(dest, src, max);
473 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
474 else
475 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
476 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
479 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
481 LPWSTR wstr = (LPWSTR)text;
483 if (!isW && is_textT(text, isW))
485 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
486 wstr = Alloc(len * sizeof(WCHAR));
487 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
489 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
490 return wstr;
493 static inline void textfreeT(LPWSTR wstr, BOOL isW)
495 if (!isW && is_textT(wstr, isW)) Free (wstr);
499 * dest is a pointer to a Unicode string
500 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
502 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
504 BOOL bResult = TRUE;
506 if (src == LPSTR_TEXTCALLBACKW)
508 if (is_textW(*dest)) Free(*dest);
509 *dest = LPSTR_TEXTCALLBACKW;
511 else
513 LPWSTR pszText = textdupTtoW(src, isW);
514 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
515 bResult = Str_SetPtrW(dest, pszText);
516 textfreeT(pszText, isW);
518 return bResult;
522 * compares a Unicode to a Unicode/ANSI text string
524 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
526 if (!aw) return bt ? -1 : 0;
527 if (!bt) return aw ? 1 : 0;
528 if (aw == LPSTR_TEXTCALLBACKW)
529 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
530 if (bt != LPSTR_TEXTCALLBACKW)
532 LPWSTR bw = textdupTtoW(bt, isW);
533 int r = bw ? lstrcmpW(aw, bw) : 1;
534 textfreeT(bw, isW);
535 return r;
538 return 1;
541 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
543 int res;
545 n = min(min(n, strlenW(s1)), strlenW(s2));
546 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
547 return res ? res - sizeof(WCHAR) : res;
550 /******** Debugging functions *****************************************/
552 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
554 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
555 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
558 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
560 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
561 n = min(textlenT(text, isW), n);
562 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
565 static char* debug_getbuf(void)
567 static int index = 0;
568 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
569 return buffers[index++ % DEBUG_BUFFERS];
572 static inline const char* debugrange(const RANGE *lprng)
574 if (!lprng) return "(null)";
575 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
578 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
580 char* buf = debug_getbuf(), *text = buf;
581 int len, size = DEBUG_BUFFER_SIZE;
583 if (pScrollInfo == NULL) return "(null)";
584 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
585 if (len == -1) goto end; buf += len; size -= len;
586 if (pScrollInfo->fMask & SIF_RANGE)
587 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
588 else len = 0;
589 if (len == -1) goto end; buf += len; size -= len;
590 if (pScrollInfo->fMask & SIF_PAGE)
591 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
592 else len = 0;
593 if (len == -1) goto end; buf += len; size -= len;
594 if (pScrollInfo->fMask & SIF_POS)
595 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
596 else len = 0;
597 if (len == -1) goto end; buf += len; size -= len;
598 if (pScrollInfo->fMask & SIF_TRACKPOS)
599 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
600 else len = 0;
601 if (len == -1) goto end; buf += len; size -= len;
602 goto undo;
603 end:
604 buf = text + strlen(text);
605 undo:
606 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
607 return text;
610 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
612 if (!plvnm) return "(null)";
613 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
614 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
615 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
616 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
619 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
621 char* buf = debug_getbuf(), *text = buf;
622 int len, size = DEBUG_BUFFER_SIZE;
624 if (lpLVItem == NULL) return "(null)";
625 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_STATE)
628 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
629 else len = 0;
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_TEXT)
632 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
633 else len = 0;
634 if (len == -1) goto end; buf += len; size -= len;
635 if (lpLVItem->mask & LVIF_IMAGE)
636 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
637 else len = 0;
638 if (len == -1) goto end; buf += len; size -= len;
639 if (lpLVItem->mask & LVIF_PARAM)
640 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
641 else len = 0;
642 if (len == -1) goto end; buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_INDENT)
644 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
645 else len = 0;
646 if (len == -1) goto end; buf += len; size -= len;
647 goto undo;
648 end:
649 buf = text + strlen(text);
650 undo:
651 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
652 return text;
655 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
657 char* buf = debug_getbuf(), *text = buf;
658 int len, size = DEBUG_BUFFER_SIZE;
660 if (lpColumn == NULL) return "(null)";
661 len = snprintf(buf, size, "{");
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_SUBITEM)
664 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
665 else len = 0;
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_FMT)
668 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
669 else len = 0;
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_WIDTH)
672 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
673 else len = 0;
674 if (len == -1) goto end; buf += len; size -= len;
675 if (lpColumn->mask & LVCF_TEXT)
676 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
677 else len = 0;
678 if (len == -1) goto end; buf += len; size -= len;
679 if (lpColumn->mask & LVCF_IMAGE)
680 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
681 else len = 0;
682 if (len == -1) goto end; buf += len; size -= len;
683 if (lpColumn->mask & LVCF_ORDER)
684 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
685 else len = 0;
686 if (len == -1) goto end; buf += len; size -= len;
687 goto undo;
688 end:
689 buf = text + strlen(text);
690 undo:
691 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
692 return text;
695 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
697 if (!lpht) return "(null)";
699 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
700 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
703 /* Return the corresponding text for a given scroll value */
704 static inline LPCSTR debugscrollcode(int nScrollCode)
706 switch(nScrollCode)
708 case SB_LINELEFT: return "SB_LINELEFT";
709 case SB_LINERIGHT: return "SB_LINERIGHT";
710 case SB_PAGELEFT: return "SB_PAGELEFT";
711 case SB_PAGERIGHT: return "SB_PAGERIGHT";
712 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
713 case SB_THUMBTRACK: return "SB_THUMBTRACK";
714 case SB_ENDSCROLL: return "SB_ENDSCROLL";
715 case SB_INTERNAL: return "SB_INTERNAL";
716 default: return "unknown";
721 /******** Notification functions i************************************/
723 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
725 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
726 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
729 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
731 LRESULT result;
733 TRACE("(code=%d)\n", code);
735 pnmh->hwndFrom = infoPtr->hwndSelf;
736 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
737 pnmh->code = code;
738 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
740 TRACE(" <= %ld\n", result);
742 return result;
745 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
747 NMHDR nmh;
748 HWND hwnd = infoPtr->hwndSelf;
749 notify_hdr(infoPtr, code, &nmh);
750 return IsWindow(hwnd);
753 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
755 NMITEMACTIVATE nmia;
756 LVITEMW item;
758 if (htInfo) {
759 nmia.uNewState = 0;
760 nmia.uOldState = 0;
761 nmia.uChanged = 0;
762 nmia.uKeyFlags = 0;
764 item.mask = LVIF_PARAM|LVIF_STATE;
765 item.iItem = htInfo->iItem;
766 item.iSubItem = 0;
767 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
768 nmia.lParam = item.lParam;
769 nmia.uOldState = item.state;
770 nmia.uNewState = item.state | LVIS_ACTIVATING;
771 nmia.uChanged = LVIF_STATE;
774 nmia.iItem = htInfo->iItem;
775 nmia.iSubItem = htInfo->iSubItem;
776 nmia.ptAction = htInfo->pt;
778 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
779 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
780 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
782 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
785 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
787 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
788 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
791 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
793 NMLISTVIEW nmlv;
794 LVITEMW item;
795 HWND hwnd = infoPtr->hwndSelf;
797 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
798 ZeroMemory(&nmlv, sizeof(nmlv));
799 nmlv.iItem = lvht->iItem;
800 nmlv.iSubItem = lvht->iSubItem;
801 nmlv.ptAction = lvht->pt;
802 item.mask = LVIF_PARAM;
803 item.iItem = lvht->iItem;
804 item.iSubItem = 0;
805 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
806 notify_listview(infoPtr, code, &nmlv);
807 return IsWindow(hwnd);
810 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
812 NMLISTVIEW nmlv;
813 LVITEMW item;
814 HWND hwnd = infoPtr->hwndSelf;
816 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
817 nmlv.iItem = nItem;
818 item.mask = LVIF_PARAM;
819 item.iItem = nItem;
820 item.iSubItem = 0;
821 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
822 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
823 return IsWindow(hwnd);
826 static int get_ansi_notification(INT unicodeNotificationCode)
828 switch (unicodeNotificationCode)
830 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
831 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
832 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
833 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
834 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
835 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
837 ERR("unknown notification %x\n", unicodeNotificationCode);
838 assert(FALSE);
839 return 0;
843 Send notification. depends on dispinfoW having same
844 structure as dispinfoA.
845 infoPtr : listview struct
846 notificationCode : *Unicode* notification code
847 pdi : dispinfo structure (can be unicode or ansi)
848 isW : TRUE if dispinfo is Unicode
850 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
852 BOOL bResult = FALSE;
853 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
854 INT cchTempBufMax = 0, savCchTextMax = 0, realNotifCode;
855 LPWSTR pszTempBuf = NULL, savPszText = NULL;
857 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
859 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
860 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
863 if (convertToAnsi || convertToUnicode)
865 if (notificationCode != LVN_GETDISPINFOW)
867 cchTempBufMax = convertToUnicode ?
868 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
869 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
871 else
873 cchTempBufMax = pdi->item.cchTextMax;
874 *pdi->item.pszText = 0; /* make sure we don't process garbage */
877 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
878 if (!pszTempBuf) return FALSE;
880 if (convertToUnicode)
881 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
882 pszTempBuf, cchTempBufMax);
883 else
884 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
885 cchTempBufMax, NULL, NULL);
887 savCchTextMax = pdi->item.cchTextMax;
888 savPszText = pdi->item.pszText;
889 pdi->item.pszText = pszTempBuf;
890 pdi->item.cchTextMax = cchTempBufMax;
893 if (infoPtr->notifyFormat == NFR_ANSI)
894 realNotifCode = get_ansi_notification(notificationCode);
895 else
896 realNotifCode = notificationCode;
897 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
898 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
900 if (convertToUnicode || convertToAnsi)
902 if (convertToUnicode) /* note : pointer can be changed by app ! */
903 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
904 savCchTextMax, NULL, NULL);
905 else
906 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
907 savPszText, savCchTextMax);
908 pdi->item.pszText = savPszText; /* restores our buffer */
909 pdi->item.cchTextMax = savCchTextMax;
910 Free (pszTempBuf);
912 return bResult;
915 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
916 const RECT *rcBounds, const LVITEMW *lplvItem)
918 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
919 lpnmlvcd->nmcd.hdc = hdc;
920 lpnmlvcd->nmcd.rc = *rcBounds;
921 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
922 lpnmlvcd->clrText = infoPtr->clrText;
923 if (!lplvItem) return;
924 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
925 lpnmlvcd->iSubItem = lplvItem->iSubItem;
926 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
927 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
928 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
929 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
932 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
934 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
935 DWORD result;
937 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
938 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
939 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
940 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
941 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
942 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
943 return result;
946 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
948 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
949 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
950 if (lpnmlvcd->clrText == CLR_DEFAULT)
951 lpnmlvcd->clrText = comctl32_color.clrWindowText;
953 /* apprently, for selected items, we have to override the returned values */
954 if (!SubItem)
956 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
958 if (infoPtr->bFocus)
960 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
961 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
963 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
965 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
966 lpnmlvcd->clrText = comctl32_color.clrBtnText;
971 /* Set the text attributes */
972 if (lpnmlvcd->clrTextBk != CLR_NONE)
974 SetBkMode(hdc, OPAQUE);
975 SetBkColor(hdc,lpnmlvcd->clrTextBk);
977 else
978 SetBkMode(hdc, TRANSPARENT);
979 SetTextColor(hdc, lpnmlvcd->clrText);
982 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
984 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
987 /******** Item iterator functions **********************************/
989 static RANGES ranges_create(int count);
990 static void ranges_destroy(RANGES ranges);
991 static BOOL ranges_add(RANGES ranges, RANGE range);
992 static BOOL ranges_del(RANGES ranges, RANGE range);
993 static void ranges_dump(RANGES ranges);
995 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
997 RANGE range = { nItem, nItem + 1 };
999 return ranges_add(ranges, range);
1002 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1004 RANGE range = { nItem, nItem + 1 };
1006 return ranges_del(ranges, range);
1009 /***
1010 * ITERATOR DOCUMENTATION
1012 * The iterator functions allow for easy, and convenient iteration
1013 * over items of iterest in the list. Typically, you create a
1014 * iterator, use it, and destroy it, as such:
1015 * ITERATOR i;
1017 * iterator_xxxitems(&i, ...);
1018 * while (iterator_{prev,next}(&i)
1020 * //code which uses i.nItem
1022 * iterator_destroy(&i);
1024 * where xxx is either: framed, or visible.
1025 * Note that it is important that the code destroys the iterator
1026 * after it's done with it, as the creation of the iterator may
1027 * allocate memory, which thus needs to be freed.
1029 * You can iterate both forwards, and backwards through the list,
1030 * by using iterator_next or iterator_prev respectively.
1032 * Lower numbered items are draw on top of higher number items in
1033 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1034 * items may overlap). So, to test items, you should use
1035 * iterator_next
1036 * which lists the items top to bottom (in Z-order).
1037 * For drawing items, you should use
1038 * iterator_prev
1039 * which lists the items bottom to top (in Z-order).
1040 * If you keep iterating over the items after the end-of-items
1041 * marker (-1) is returned, the iterator will start from the
1042 * beginning. Typically, you don't need to test for -1,
1043 * because iterator_{next,prev} will return TRUE if more items
1044 * are to be iterated over, or FALSE otherwise.
1046 * Note: the iterator is defined to be bidirectional. That is,
1047 * any number of prev followed by any number of next, or
1048 * five versa, should leave the iterator at the same item:
1049 * prev * n, next * n = next * n, prev * n
1051 * The iterator has a notion of an out-of-order, special item,
1052 * which sits at the start of the list. This is used in
1053 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1054 * which needs to be first, as it may overlap other items.
1056 * The code is a bit messy because we have:
1057 * - a special item to deal with
1058 * - simple range, or composite range
1059 * - empty range.
1060 * If you find bugs, or want to add features, please make sure you
1061 * always check/modify *both* iterator_prev, and iterator_next.
1064 /****
1065 * This function iterates through the items in increasing order,
1066 * but prefixed by the special item, then -1. That is:
1067 * special, 1, 2, 3, ..., n, -1.
1068 * Each item is listed only once.
1070 static inline BOOL iterator_next(ITERATOR* i)
1072 if (i->nItem == -1)
1074 i->nItem = i->nSpecial;
1075 if (i->nItem != -1) return TRUE;
1077 if (i->nItem == i->nSpecial)
1079 if (i->ranges) i->index = 0;
1080 goto pickarange;
1083 i->nItem++;
1084 testitem:
1085 if (i->nItem == i->nSpecial) i->nItem++;
1086 if (i->nItem < i->range.upper) return TRUE;
1088 pickarange:
1089 if (i->ranges)
1091 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1092 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1093 else goto end;
1095 else if (i->nItem >= i->range.upper) goto end;
1097 i->nItem = i->range.lower;
1098 if (i->nItem >= 0) goto testitem;
1099 end:
1100 i->nItem = -1;
1101 return FALSE;
1104 /****
1105 * This function iterates through the items in decreasing order,
1106 * followed by the special item, then -1. That is:
1107 * n, n-1, ..., 3, 2, 1, special, -1.
1108 * Each item is listed only once.
1110 static inline BOOL iterator_prev(ITERATOR* i)
1112 BOOL start = FALSE;
1114 if (i->nItem == -1)
1116 start = TRUE;
1117 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1118 goto pickarange;
1120 if (i->nItem == i->nSpecial)
1122 i->nItem = -1;
1123 return FALSE;
1126 testitem:
1127 i->nItem--;
1128 if (i->nItem == i->nSpecial) i->nItem--;
1129 if (i->nItem >= i->range.lower) return TRUE;
1131 pickarange:
1132 if (i->ranges)
1134 if (i->index > 0)
1135 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1136 else goto end;
1138 else if (!start && i->nItem < i->range.lower) goto end;
1140 i->nItem = i->range.upper;
1141 if (i->nItem > 0) goto testitem;
1142 end:
1143 return (i->nItem = i->nSpecial) != -1;
1146 static RANGE iterator_range(const ITERATOR *i)
1148 RANGE range;
1150 if (!i->ranges) return i->range;
1152 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1154 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1155 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1157 else range.lower = range.upper = 0;
1159 return range;
1162 /***
1163 * Releases resources associated with this ierator.
1165 static inline void iterator_destroy(const ITERATOR *i)
1167 ranges_destroy(i->ranges);
1170 /***
1171 * Create an empty iterator.
1173 static inline BOOL iterator_empty(ITERATOR* i)
1175 ZeroMemory(i, sizeof(*i));
1176 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1177 return TRUE;
1180 /***
1181 * Create an iterator over a range.
1183 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1185 iterator_empty(i);
1186 i->range = range;
1187 return TRUE;
1190 /***
1191 * Create an iterator over a bunch of ranges.
1192 * Please note that the iterator will take ownership of the ranges,
1193 * and will free them upon destruction.
1195 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1197 iterator_empty(i);
1198 i->ranges = ranges;
1199 return TRUE;
1202 /***
1203 * Creates an iterator over the items which intersect lprc.
1205 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1207 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1208 RECT frame = *lprc, rcItem, rcTemp;
1209 POINT Origin;
1211 /* in case we fail, we want to return an empty iterator */
1212 if (!iterator_empty(i)) return FALSE;
1214 LISTVIEW_GetOrigin(infoPtr, &Origin);
1216 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1217 OffsetRect(&frame, -Origin.x, -Origin.y);
1219 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1221 INT nItem;
1223 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1225 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1226 if (IntersectRect(&rcTemp, &rcItem, lprc))
1227 i->nSpecial = infoPtr->nFocusedItem;
1229 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1230 /* to do better here, we need to have PosX, and PosY sorted */
1231 TRACE("building icon ranges:\n");
1232 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1234 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1235 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1236 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1237 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1238 if (IntersectRect(&rcTemp, &rcItem, &frame))
1239 ranges_additem(i->ranges, nItem);
1241 return TRUE;
1243 else if (uView == LVS_REPORT)
1245 RANGE range;
1247 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1248 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1250 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1251 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1252 if (range.upper <= range.lower) return TRUE;
1253 if (!iterator_rangeitems(i, range)) return FALSE;
1254 TRACE(" report=%s\n", debugrange(&i->range));
1256 else
1258 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1259 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1260 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1261 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1262 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1263 INT lower = nFirstCol * nPerCol + nFirstRow;
1264 RANGE item_range;
1265 INT nCol;
1267 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1268 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1270 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1272 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1273 TRACE("building list ranges:\n");
1274 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1276 item_range.lower = nCol * nPerCol + nFirstRow;
1277 if(item_range.lower >= infoPtr->nItemCount) break;
1278 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1279 TRACE(" list=%s\n", debugrange(&item_range));
1280 ranges_add(i->ranges, item_range);
1284 return TRUE;
1287 /***
1288 * Creates an iterator over the items which intersect the visible region of hdc.
1290 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1292 POINT Origin, Position;
1293 RECT rcItem, rcClip;
1294 INT rgntype;
1296 rgntype = GetClipBox(hdc, &rcClip);
1297 if (rgntype == NULLREGION) return iterator_empty(i);
1298 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1299 if (rgntype == SIMPLEREGION) return TRUE;
1301 /* first deal with the special item */
1302 if (i->nSpecial != -1)
1304 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1305 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1308 /* if we can't deal with the region, we'll just go with the simple range */
1309 LISTVIEW_GetOrigin(infoPtr, &Origin);
1310 TRACE("building visible range:\n");
1311 if (!i->ranges && i->range.lower < i->range.upper)
1313 if (!(i->ranges = ranges_create(50))) return TRUE;
1314 if (!ranges_add(i->ranges, i->range))
1316 ranges_destroy(i->ranges);
1317 i->ranges = 0;
1318 return TRUE;
1322 /* now delete the invisible items from the list */
1323 while(iterator_next(i))
1325 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1326 rcItem.left = Position.x + Origin.x;
1327 rcItem.top = Position.y + Origin.y;
1328 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1329 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1330 if (!RectVisible(hdc, &rcItem))
1331 ranges_delitem(i->ranges, i->nItem);
1333 /* the iterator should restart on the next iterator_next */
1334 TRACE("done\n");
1336 return TRUE;
1339 /******** Misc helper functions ************************************/
1341 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1342 WPARAM wParam, LPARAM lParam, BOOL isW)
1344 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1345 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1348 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1350 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1352 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1353 (uView == LVS_ICON || uView == LVS_SMALLICON);
1356 /******** Internal API functions ************************************/
1358 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1360 static COLUMN_INFO mainItem;
1362 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1363 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1364 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1367 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1369 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1372 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1374 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1377 /* Listview invalidation functions: use _only_ these functions to invalidate */
1379 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1381 return infoPtr->bRedraw;
1384 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1386 if(!is_redrawing(infoPtr)) return;
1387 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1388 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1391 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1393 RECT rcBox;
1395 if(!is_redrawing(infoPtr)) return;
1396 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1397 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1400 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1402 POINT Origin, Position;
1403 RECT rcBox;
1405 if(!is_redrawing(infoPtr)) return;
1406 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1407 LISTVIEW_GetOrigin(infoPtr, &Origin);
1408 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1409 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1410 rcBox.top = 0;
1411 rcBox.bottom = infoPtr->nItemHeight;
1412 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1413 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1416 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1418 LISTVIEW_InvalidateRect(infoPtr, NULL);
1421 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1423 RECT rcCol;
1425 if(!is_redrawing(infoPtr)) return;
1426 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1427 rcCol.top = infoPtr->rcList.top;
1428 rcCol.bottom = infoPtr->rcList.bottom;
1429 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1432 /***
1433 * DESCRIPTION:
1434 * Retrieves the number of items that can fit vertically in the client area.
1436 * PARAMETER(S):
1437 * [I] infoPtr : valid pointer to the listview structure
1439 * RETURN:
1440 * Number of items per row.
1442 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1444 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1446 return max(nListWidth/infoPtr->nItemWidth, 1);
1449 /***
1450 * DESCRIPTION:
1451 * Retrieves the number of items that can fit horizontally in the client
1452 * area.
1454 * PARAMETER(S):
1455 * [I] infoPtr : valid pointer to the listview structure
1457 * RETURN:
1458 * Number of items per column.
1460 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1462 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1464 return max(nListHeight / infoPtr->nItemHeight, 1);
1468 /*************************************************************************
1469 * LISTVIEW_ProcessLetterKeys
1471 * Processes keyboard messages generated by pressing the letter keys
1472 * on the keyboard.
1473 * What this does is perform a case insensitive search from the
1474 * current position with the following quirks:
1475 * - If two chars or more are pressed in quick succession we search
1476 * for the corresponding string (e.g. 'abc').
1477 * - If there is a delay we wipe away the current search string and
1478 * restart with just that char.
1479 * - If the user keeps pressing the same character, whether slowly or
1480 * fast, so that the search string is entirely composed of this
1481 * character ('aaaaa' for instance), then we search for first item
1482 * that starting with that character.
1483 * - If the user types the above character in quick succession, then
1484 * we must also search for the corresponding string ('aaaaa'), and
1485 * go to that string if there is a match.
1487 * PARAMETERS
1488 * [I] hwnd : handle to the window
1489 * [I] charCode : the character code, the actual character
1490 * [I] keyData : key data
1492 * RETURNS
1494 * Zero.
1496 * BUGS
1498 * - The current implementation has a list of characters it will
1499 * accept and it ignores averything else. In particular it will
1500 * ignore accentuated characters which seems to match what
1501 * Windows does. But I'm not sure it makes sense to follow
1502 * Windows there.
1503 * - We don't sound a beep when the search fails.
1505 * SEE ALSO
1507 * TREEVIEW_ProcessLetterKeys
1509 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1511 INT nItem;
1512 INT endidx,idx;
1513 LVITEMW item;
1514 WCHAR buffer[MAX_PATH];
1515 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1517 /* simple parameter checking */
1518 if (!charCode || !keyData) return 0;
1520 /* only allow the valid WM_CHARs through */
1521 if (!isalnum(charCode) &&
1522 charCode != '.' && charCode != '`' && charCode != '!' &&
1523 charCode != '@' && charCode != '#' && charCode != '$' &&
1524 charCode != '%' && charCode != '^' && charCode != '&' &&
1525 charCode != '*' && charCode != '(' && charCode != ')' &&
1526 charCode != '-' && charCode != '_' && charCode != '+' &&
1527 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1528 charCode != '}' && charCode != '[' && charCode != '{' &&
1529 charCode != '/' && charCode != '?' && charCode != '>' &&
1530 charCode != '<' && charCode != ',' && charCode != '~')
1531 return 0;
1533 /* if there's one item or less, there is no where to go */
1534 if (infoPtr->nItemCount <= 1) return 0;
1536 /* update the search parameters */
1537 infoPtr->lastKeyPressTimestamp = GetTickCount();
1538 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1539 if (infoPtr->nSearchParamLength < MAX_PATH)
1540 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1541 if (infoPtr->charCode != charCode)
1542 infoPtr->charCode = charCode = 0;
1543 } else {
1544 infoPtr->charCode=charCode;
1545 infoPtr->szSearchParam[0]=charCode;
1546 infoPtr->nSearchParamLength=1;
1547 /* Redundant with the 1 char string */
1548 charCode=0;
1551 /* and search from the current position */
1552 nItem=-1;
1553 if (infoPtr->nFocusedItem >= 0) {
1554 endidx=infoPtr->nFocusedItem;
1555 idx=endidx;
1556 /* if looking for single character match,
1557 * then we must always move forward
1559 if (infoPtr->nSearchParamLength == 1)
1560 idx++;
1561 } else {
1562 endidx=infoPtr->nItemCount;
1563 idx=0;
1565 do {
1566 if (idx == infoPtr->nItemCount) {
1567 if (endidx == infoPtr->nItemCount || endidx == 0)
1568 break;
1569 idx=0;
1572 /* get item */
1573 item.mask = LVIF_TEXT;
1574 item.iItem = idx;
1575 item.iSubItem = 0;
1576 item.pszText = buffer;
1577 item.cchTextMax = MAX_PATH;
1578 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1580 /* check for a match */
1581 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1582 nItem=idx;
1583 break;
1584 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1585 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1586 /* This would work but we must keep looking for a longer match */
1587 nItem=idx;
1589 idx++;
1590 } while (idx != endidx);
1592 if (nItem != -1)
1593 LISTVIEW_KeySelection(infoPtr, nItem);
1595 return 0;
1598 /*************************************************************************
1599 * LISTVIEW_UpdateHeaderSize [Internal]
1601 * Function to resize the header control
1603 * PARAMS
1604 * [I] hwnd : handle to a window
1605 * [I] nNewScrollPos : scroll pos to set
1607 * RETURNS
1608 * None.
1610 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1612 RECT winRect;
1613 POINT point[2];
1615 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1617 GetWindowRect(infoPtr->hwndHeader, &winRect);
1618 point[0].x = winRect.left;
1619 point[0].y = winRect.top;
1620 point[1].x = winRect.right;
1621 point[1].y = winRect.bottom;
1623 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1624 point[0].x = -nNewScrollPos;
1625 point[1].x += nNewScrollPos;
1627 SetWindowPos(infoPtr->hwndHeader,0,
1628 point[0].x,point[0].y,point[1].x,point[1].y,
1629 SWP_NOZORDER | SWP_NOACTIVATE);
1632 /***
1633 * DESCRIPTION:
1634 * Update the scrollbars. This functions should be called whenever
1635 * the content, size or view changes.
1637 * PARAMETER(S):
1638 * [I] infoPtr : valid pointer to the listview structure
1640 * RETURN:
1641 * None
1643 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1645 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1646 SCROLLINFO horzInfo, vertInfo;
1647 INT dx, dy;
1649 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1651 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1652 horzInfo.cbSize = sizeof(SCROLLINFO);
1653 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1655 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1656 if (uView == LVS_LIST)
1658 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1659 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1661 /* scroll by at least one column per page */
1662 if(horzInfo.nPage < infoPtr->nItemWidth)
1663 horzInfo.nPage = infoPtr->nItemWidth;
1665 horzInfo.nPage /= infoPtr->nItemWidth;
1667 else if (uView == LVS_REPORT)
1669 horzInfo.nMax = infoPtr->nItemWidth;
1671 else /* LVS_ICON, or LVS_SMALLICON */
1673 RECT rcView;
1675 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1678 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1679 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1680 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1681 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1682 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1684 /* Setting the horizontal scroll can change the listview size
1685 * (and potentially everything else) so we need to recompute
1686 * everything again for the vertical scroll
1689 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1690 vertInfo.cbSize = sizeof(SCROLLINFO);
1691 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1693 if (uView == LVS_REPORT)
1695 vertInfo.nMax = infoPtr->nItemCount;
1697 /* scroll by at least one page */
1698 if(vertInfo.nPage < infoPtr->nItemHeight)
1699 vertInfo.nPage = infoPtr->nItemHeight;
1701 vertInfo.nPage /= infoPtr->nItemHeight;
1703 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1705 RECT rcView;
1707 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1710 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1711 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1712 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1713 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1714 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1716 /* Change of the range may have changed the scroll pos. If so move the content */
1717 if (dx != 0 || dy != 0)
1719 RECT listRect;
1720 listRect = infoPtr->rcList;
1721 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1722 SW_ERASE | SW_INVALIDATE);
1725 /* Update the Header Control */
1726 if (uView == LVS_REPORT)
1728 horzInfo.fMask = SIF_POS;
1729 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1730 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1735 /***
1736 * DESCRIPTION:
1737 * Shows/hides the focus rectangle.
1739 * PARAMETER(S):
1740 * [I] infoPtr : valid pointer to the listview structure
1741 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1743 * RETURN:
1744 * None
1746 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1748 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1749 HDC hdc;
1751 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1753 if (infoPtr->nFocusedItem < 0) return;
1755 /* we need some gymnastics in ICON mode to handle large items */
1756 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1758 RECT rcBox;
1760 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1761 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1763 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1764 return;
1768 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1770 /* for some reason, owner draw should work only in report mode */
1771 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1773 DRAWITEMSTRUCT dis;
1774 LVITEMW item;
1776 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1777 HFONT hOldFont = SelectObject(hdc, hFont);
1779 item.iItem = infoPtr->nFocusedItem;
1780 item.iSubItem = 0;
1781 item.mask = LVIF_PARAM;
1782 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1784 ZeroMemory(&dis, sizeof(dis));
1785 dis.CtlType = ODT_LISTVIEW;
1786 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1787 dis.itemID = item.iItem;
1788 dis.itemAction = ODA_FOCUS;
1789 if (fShow) dis.itemState |= ODS_FOCUS;
1790 dis.hwndItem = infoPtr->hwndSelf;
1791 dis.hDC = hdc;
1792 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1793 dis.itemData = item.lParam;
1795 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1797 SelectObject(hdc, hOldFont);
1799 else
1801 DrawFocusRect(hdc, &infoPtr->rcFocus);
1803 done:
1804 ReleaseDC(infoPtr->hwndSelf, hdc);
1807 /***
1808 * Invalidates all visible selected items.
1810 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1812 ITERATOR i;
1814 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1815 while(iterator_next(&i))
1817 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1818 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1820 iterator_destroy(&i);
1824 /***
1825 * DESCRIPTION: [INTERNAL]
1826 * Computes an item's (left,top) corner, relative to rcView.
1827 * That is, the position has NOT been made relative to the Origin.
1828 * This is deliberate, to avoid computing the Origin over, and
1829 * over again, when this function is call in a loop. Instead,
1830 * one ca factor the computation of the Origin before the loop,
1831 * and offset the value retured by this function, on every iteration.
1833 * PARAMETER(S):
1834 * [I] infoPtr : valid pointer to the listview structure
1835 * [I] nItem : item number
1836 * [O] lpptOrig : item top, left corner
1838 * RETURN:
1839 * None.
1841 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1843 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1845 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1847 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1849 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1850 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1852 else if (uView == LVS_LIST)
1854 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1855 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1856 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1858 else /* LVS_REPORT */
1860 lpptPosition->x = 0;
1861 lpptPosition->y = nItem * infoPtr->nItemHeight;
1865 /***
1866 * DESCRIPTION: [INTERNAL]
1867 * Compute the rectangles of an item. This is to localize all
1868 * the computations in one place. If you are not interested in some
1869 * of these values, simply pass in a NULL -- the fucntion is smart
1870 * enough to compute only what's necessary. The function computes
1871 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1872 * one, the BOX rectangle. This rectangle is very cheap to compute,
1873 * and is guaranteed to contain all the other rectangles. Computing
1874 * the ICON rect is also cheap, but all the others are potentaily
1875 * expensive. This gives an easy and effective optimization when
1876 * searching (like point inclusion, or rectangle intersection):
1877 * first test against the BOX, and if TRUE, test agains the desired
1878 * rectangle.
1879 * If the function does not have all the necessary information
1880 * to computed the requested rectangles, will crash with a
1881 * failed assertion. This is done so we catch all programming
1882 * errors, given that the function is called only from our code.
1884 * We have the following 'special' meanings for a few fields:
1885 * * If LVIS_FOCUSED is set, we assume the item has the focus
1886 * This is important in ICON mode, where it might get a larger
1887 * then usual rectange
1889 * Please note that subitem support works only in REPORT mode.
1891 * PARAMETER(S):
1892 * [I] infoPtr : valid pointer to the listview structure
1893 * [I] lpLVItem : item to compute the measures for
1894 * [O] lprcBox : ptr to Box rectangle
1895 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1896 * [0] lprcSelectBox : ptr to select box rectangle
1897 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1898 * [O] lprcIcon : ptr to Icon rectangle
1899 * Same as LVM_GETITEMRECT with LVIR_ICON
1900 * [O] lprcStateIcon: ptr to State Icon rectangle
1901 * [O] lprcLabel : ptr to Label rectangle
1902 * Same as LVM_GETITEMRECT with LVIR_LABEL
1904 * RETURN:
1905 * None.
1907 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1908 LPRECT lprcBox, LPRECT lprcSelectBox,
1909 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1911 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1912 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1913 RECT Box, SelectBox, Icon, Label;
1914 COLUMN_INFO *lpColumnInfo = NULL;
1915 SIZE labelSize = { 0, 0 };
1917 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1919 /* Be smart and try to figure out the minimum we have to do */
1920 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1921 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1923 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1924 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1926 if (lprcSelectBox) doSelectBox = TRUE;
1927 if (lprcLabel) doLabel = TRUE;
1928 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1929 if (doSelectBox)
1931 doIcon = TRUE;
1932 doLabel = TRUE;
1935 /************************************************************/
1936 /* compute the box rectangle (it should be cheap to do) */
1937 /************************************************************/
1938 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1939 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1941 if (lpLVItem->iSubItem)
1943 Box = lpColumnInfo->rcHeader;
1945 else
1947 Box.left = 0;
1948 Box.right = infoPtr->nItemWidth;
1950 Box.top = 0;
1951 Box.bottom = infoPtr->nItemHeight;
1953 /******************************************************************/
1954 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1955 /******************************************************************/
1956 if (doIcon)
1958 LONG state_width = 0;
1960 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1961 state_width = infoPtr->iconStateSize.cx;
1963 if (uView == LVS_ICON)
1965 Icon.left = Box.left + state_width;
1966 if (infoPtr->himlNormal)
1967 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1968 Icon.top = Box.top + ICON_TOP_PADDING;
1969 Icon.right = Icon.left;
1970 Icon.bottom = Icon.top;
1971 if (infoPtr->himlNormal)
1973 Icon.right += infoPtr->iconSize.cx;
1974 Icon.bottom += infoPtr->iconSize.cy;
1977 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1979 Icon.left = Box.left + state_width;
1981 if (uView == LVS_REPORT)
1982 Icon.left += REPORT_MARGINX;
1984 Icon.top = Box.top;
1985 Icon.right = Icon.left;
1986 if (infoPtr->himlSmall &&
1987 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1988 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1989 Icon.right += infoPtr->iconSize.cx;
1990 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
1992 if(lprcIcon) *lprcIcon = Icon;
1993 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
1995 /* TODO: is this correct? */
1996 if (lprcStateIcon)
1998 lprcStateIcon->left = Icon.left - state_width;
1999 lprcStateIcon->right = Icon.left;
2000 lprcStateIcon->top = Icon.top;
2001 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2002 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2005 else Icon.right = 0;
2007 /************************************************************/
2008 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2009 /************************************************************/
2010 if (doLabel)
2012 /* calculate how far to the right can the label strech */
2013 Label.right = Box.right;
2014 if (uView == LVS_REPORT)
2016 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2019 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2021 labelSize.cx = infoPtr->nItemWidth;
2022 labelSize.cy = infoPtr->nItemHeight;
2023 goto calc_label;
2026 /* we need the text in non owner draw mode */
2027 assert(lpLVItem->mask & LVIF_TEXT);
2028 if (is_textT(lpLVItem->pszText, TRUE))
2030 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2031 HDC hdc = GetDC(infoPtr->hwndSelf);
2032 HFONT hOldFont = SelectObject(hdc, hFont);
2033 UINT uFormat;
2034 RECT rcText;
2036 /* compute rough rectangle where the label will go */
2037 SetRectEmpty(&rcText);
2038 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2039 rcText.bottom = infoPtr->nItemHeight;
2040 if (uView == LVS_ICON)
2041 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2043 /* now figure out the flags */
2044 if (uView == LVS_ICON)
2045 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2046 else
2047 uFormat = LV_SL_DT_FLAGS;
2049 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2051 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2052 labelSize.cy = rcText.bottom - rcText.top;
2054 SelectObject(hdc, hOldFont);
2055 ReleaseDC(infoPtr->hwndSelf, hdc);
2058 calc_label:
2059 if (uView == LVS_ICON)
2061 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2062 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2063 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2064 Label.right = Label.left + labelSize.cx;
2065 Label.bottom = Label.top + infoPtr->nItemHeight;
2066 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2068 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2069 labelSize.cy /= infoPtr->ntmHeight;
2070 labelSize.cy = max(labelSize.cy, 1);
2071 labelSize.cy *= infoPtr->ntmHeight;
2073 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2075 else if (uView == LVS_REPORT)
2077 Label.left = Icon.right;
2078 Label.top = Box.top;
2079 Label.right = lpColumnInfo->rcHeader.right;
2080 Label.bottom = Label.top + infoPtr->nItemHeight;
2082 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2084 Label.left = Icon.right;
2085 Label.top = Box.top;
2086 Label.right = min(Label.left + labelSize.cx, Label.right);
2087 Label.bottom = Label.top + infoPtr->nItemHeight;
2090 if (lprcLabel) *lprcLabel = Label;
2091 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2094 /************************************************************/
2095 /* compute STATEICON bounding box */
2096 /************************************************************/
2097 if (doSelectBox)
2099 if (uView == LVS_REPORT)
2101 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2102 SelectBox.top = Box.top;
2103 SelectBox.bottom = Box.bottom;
2104 if (lpLVItem->iSubItem == 0)
2106 /* we need the indent in report mode */
2107 assert(lpLVItem->mask & LVIF_INDENT);
2108 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2110 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2112 else
2114 UnionRect(&SelectBox, &Icon, &Label);
2116 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2117 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2120 /* Fix the Box if necessary */
2121 if (lprcBox)
2123 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2124 else *lprcBox = Box;
2126 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2129 /***
2130 * DESCRIPTION: [INTERNAL]
2132 * PARAMETER(S):
2133 * [I] infoPtr : valid pointer to the listview structure
2134 * [I] nItem : item number
2135 * [O] lprcBox : ptr to Box rectangle
2137 * RETURN:
2138 * None.
2140 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2142 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2143 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2144 POINT Position, Origin;
2145 LVITEMW lvItem;
2147 LISTVIEW_GetOrigin(infoPtr, &Origin);
2148 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2150 /* Be smart and try to figure out the minimum we have to do */
2151 lvItem.mask = 0;
2152 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2153 lvItem.mask |= LVIF_TEXT;
2154 lvItem.iItem = nItem;
2155 lvItem.iSubItem = 0;
2156 lvItem.pszText = szDispText;
2157 lvItem.cchTextMax = DISP_TEXT_SIZE;
2158 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2159 if (uView == LVS_ICON)
2161 lvItem.mask |= LVIF_STATE;
2162 lvItem.stateMask = LVIS_FOCUSED;
2163 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2165 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2167 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2171 /***
2172 * DESCRIPTION:
2173 * Returns the current icon position, and advances it along the top.
2174 * The returned position is not offset by Origin.
2176 * PARAMETER(S):
2177 * [I] infoPtr : valid pointer to the listview structure
2178 * [O] lpPos : will get the current icon position
2180 * RETURN:
2181 * None
2183 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2185 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2187 *lpPos = infoPtr->currIconPos;
2189 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2190 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2192 infoPtr->currIconPos.x = 0;
2193 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2197 /***
2198 * DESCRIPTION:
2199 * Returns the current icon position, and advances it down the left edge.
2200 * The returned position is not offset by Origin.
2202 * PARAMETER(S):
2203 * [I] infoPtr : valid pointer to the listview structure
2204 * [O] lpPos : will get the current icon position
2206 * RETURN:
2207 * None
2209 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2211 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2213 *lpPos = infoPtr->currIconPos;
2215 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2216 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2218 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2219 infoPtr->currIconPos.y = 0;
2223 /***
2224 * DESCRIPTION:
2225 * Moves an icon to the specified position.
2226 * It takes care of invalidating the item, etc.
2228 * PARAMETER(S):
2229 * [I] infoPtr : valid pointer to the listview structure
2230 * [I] nItem : the item to move
2231 * [I] lpPos : the new icon position
2232 * [I] isNew : flags the item as being new
2234 * RETURN:
2235 * Success: TRUE
2236 * Failure: FALSE
2238 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2240 POINT old;
2242 if (!isNew)
2244 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2245 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2247 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2248 LISTVIEW_InvalidateItem(infoPtr, nItem);
2251 /* Allocating a POINTER for every item is too resource intensive,
2252 * so we'll keep the (x,y) in different arrays */
2253 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2254 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2256 LISTVIEW_InvalidateItem(infoPtr, nItem);
2258 return TRUE;
2261 /***
2262 * DESCRIPTION:
2263 * Arranges listview items in icon display mode.
2265 * PARAMETER(S):
2266 * [I] infoPtr : valid pointer to the listview structure
2267 * [I] nAlignCode : alignment code
2269 * RETURN:
2270 * SUCCESS : TRUE
2271 * FAILURE : FALSE
2273 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2275 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2276 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2277 POINT pos;
2278 INT i;
2280 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2282 TRACE("nAlignCode=%d\n", nAlignCode);
2284 if (nAlignCode == LVA_DEFAULT)
2286 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2287 else nAlignCode = LVA_ALIGNTOP;
2290 switch (nAlignCode)
2292 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2293 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2294 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2295 default: return FALSE;
2298 infoPtr->bAutoarrange = TRUE;
2299 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2300 for (i = 0; i < infoPtr->nItemCount; i++)
2302 next_pos(infoPtr, &pos);
2303 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2306 return TRUE;
2309 /***
2310 * DESCRIPTION:
2311 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2313 * PARAMETER(S):
2314 * [I] infoPtr : valid pointer to the listview structure
2315 * [O] lprcView : bounding rectangle
2317 * RETURN:
2318 * SUCCESS : TRUE
2319 * FAILURE : FALSE
2321 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2323 INT i, x, y;
2325 SetRectEmpty(lprcView);
2327 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2329 case LVS_ICON:
2330 case LVS_SMALLICON:
2331 for (i = 0; i < infoPtr->nItemCount; i++)
2333 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2334 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2335 lprcView->right = max(lprcView->right, x);
2336 lprcView->bottom = max(lprcView->bottom, y);
2338 if (infoPtr->nItemCount > 0)
2340 lprcView->right += infoPtr->nItemWidth;
2341 lprcView->bottom += infoPtr->nItemHeight;
2343 break;
2345 case LVS_LIST:
2346 y = LISTVIEW_GetCountPerColumn(infoPtr);
2347 x = infoPtr->nItemCount / y;
2348 if (infoPtr->nItemCount % y) x++;
2349 lprcView->right = x * infoPtr->nItemWidth;
2350 lprcView->bottom = y * infoPtr->nItemHeight;
2351 break;
2353 case LVS_REPORT:
2354 lprcView->right = infoPtr->nItemWidth;
2355 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2356 break;
2360 /***
2361 * DESCRIPTION:
2362 * Retrieves the bounding rectangle of all the items.
2364 * PARAMETER(S):
2365 * [I] infoPtr : valid pointer to the listview structure
2366 * [O] lprcView : bounding rectangle
2368 * RETURN:
2369 * SUCCESS : TRUE
2370 * FAILURE : FALSE
2372 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2374 POINT ptOrigin;
2376 TRACE("(lprcView=%p)\n", lprcView);
2378 if (!lprcView) return FALSE;
2380 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2381 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2382 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2384 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2386 return TRUE;
2389 /***
2390 * DESCRIPTION:
2391 * Retrieves the subitem pointer associated with the subitem index.
2393 * PARAMETER(S):
2394 * [I] hdpaSubItems : DPA handle for a specific item
2395 * [I] nSubItem : index of subitem
2397 * RETURN:
2398 * SUCCESS : subitem pointer
2399 * FAILURE : NULL
2401 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2403 SUBITEM_INFO *lpSubItem;
2404 INT i;
2406 /* we should binary search here if need be */
2407 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2409 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2410 if (lpSubItem->iSubItem == nSubItem)
2411 return lpSubItem;
2414 return NULL;
2418 /***
2419 * DESCRIPTION:
2420 * Calculates the desired item width.
2422 * PARAMETER(S):
2423 * [I] infoPtr : valid pointer to the listview structure
2425 * RETURN:
2426 * The desired item width.
2428 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2430 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2431 INT nItemWidth = 0;
2433 TRACE("uView=%d\n", uView);
2435 if (uView == LVS_ICON)
2436 nItemWidth = infoPtr->iconSpacing.cx;
2437 else if (uView == LVS_REPORT)
2439 RECT rcHeader;
2441 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2443 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2444 nItemWidth = rcHeader.right;
2447 else /* LVS_SMALLICON, or LVS_LIST */
2449 INT i;
2451 for (i = 0; i < infoPtr->nItemCount; i++)
2452 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2454 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2455 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2457 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2460 return max(nItemWidth, 1);
2463 /***
2464 * DESCRIPTION:
2465 * Calculates the desired item height.
2467 * PARAMETER(S):
2468 * [I] infoPtr : valid pointer to the listview structure
2470 * RETURN:
2471 * The desired item height.
2473 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2475 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2476 INT nItemHeight;
2478 TRACE("uView=%d\n", uView);
2480 if (uView == LVS_ICON)
2481 nItemHeight = infoPtr->iconSpacing.cy;
2482 else
2484 nItemHeight = infoPtr->ntmHeight;
2485 if (infoPtr->himlState)
2486 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2487 if (infoPtr->himlSmall)
2488 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2489 if (infoPtr->himlState || infoPtr->himlSmall)
2490 nItemHeight += HEIGHT_PADDING;
2491 if (infoPtr->nMeasureItemHeight > 0)
2492 nItemHeight = infoPtr->nMeasureItemHeight;
2495 return max(nItemHeight, 1);
2498 /***
2499 * DESCRIPTION:
2500 * Updates the width, and height of an item.
2502 * PARAMETER(S):
2503 * [I] infoPtr : valid pointer to the listview structure
2505 * RETURN:
2506 * None.
2508 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2510 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2511 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2515 /***
2516 * DESCRIPTION:
2517 * Retrieves and saves important text metrics info for the current
2518 * Listview font.
2520 * PARAMETER(S):
2521 * [I] infoPtr : valid pointer to the listview structure
2524 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2526 HDC hdc = GetDC(infoPtr->hwndSelf);
2527 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2528 HFONT hOldFont = SelectObject(hdc, hFont);
2529 TEXTMETRICW tm;
2530 SIZE sz;
2532 if (GetTextMetricsW(hdc, &tm))
2534 infoPtr->ntmHeight = tm.tmHeight;
2535 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2538 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2539 infoPtr->nEllipsisWidth = sz.cx;
2541 SelectObject(hdc, hOldFont);
2542 ReleaseDC(infoPtr->hwndSelf, hdc);
2544 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2547 /***
2548 * DESCRIPTION:
2549 * A compare function for ranges
2551 * PARAMETER(S)
2552 * [I] range1 : pointer to range 1;
2553 * [I] range2 : pointer to range 2;
2554 * [I] flags : flags
2556 * RETURNS:
2557 * > 0 : if range 1 > range 2
2558 * < 0 : if range 2 > range 1
2559 * = 0 : if range intersects range 2
2561 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2563 INT cmp;
2565 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2566 cmp = -1;
2567 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2568 cmp = 1;
2569 else
2570 cmp = 0;
2572 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2574 return cmp;
2577 #if DEBUG_RANGES
2578 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2579 #else
2580 #define ranges_check(ranges, desc) do { } while(0)
2581 #endif
2583 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2585 INT i;
2586 RANGE *prev, *curr;
2588 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2589 assert (ranges);
2590 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2591 ranges_dump(ranges);
2592 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2593 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2594 assert (prev->lower >= 0 && prev->lower < prev->upper);
2595 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2597 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2598 assert (prev->upper <= curr->lower);
2599 assert (curr->lower < curr->upper);
2600 prev = curr;
2602 TRACE("--- Done checking---\n");
2605 static RANGES ranges_create(int count)
2607 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2608 if (!ranges) return NULL;
2609 ranges->hdpa = DPA_Create(count);
2610 if (ranges->hdpa) return ranges;
2611 Free(ranges);
2612 return NULL;
2615 static void ranges_clear(RANGES ranges)
2617 INT i;
2619 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2620 Free(DPA_GetPtr(ranges->hdpa, i));
2621 DPA_DeleteAllPtrs(ranges->hdpa);
2625 static void ranges_destroy(RANGES ranges)
2627 if (!ranges) return;
2628 ranges_clear(ranges);
2629 DPA_Destroy(ranges->hdpa);
2630 Free(ranges);
2633 static RANGES ranges_clone(RANGES ranges)
2635 RANGES clone;
2636 INT i;
2638 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2640 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2642 RANGE *newrng = Alloc(sizeof(RANGE));
2643 if (!newrng) goto fail;
2644 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2645 DPA_SetPtr(clone->hdpa, i, newrng);
2647 return clone;
2649 fail:
2650 TRACE ("clone failed\n");
2651 ranges_destroy(clone);
2652 return NULL;
2655 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2657 INT i;
2659 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2660 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2662 return ranges;
2665 static void ranges_dump(RANGES ranges)
2667 INT i;
2669 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2670 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2673 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2675 RANGE srchrng = { nItem, nItem + 1 };
2677 TRACE("(nItem=%d)\n", nItem);
2678 ranges_check(ranges, "before contain");
2679 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2682 static INT ranges_itemcount(RANGES ranges)
2684 INT i, count = 0;
2686 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2688 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2689 count += sel->upper - sel->lower;
2692 return count;
2695 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2697 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2698 INT index;
2700 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2701 if (index == -1) return TRUE;
2703 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2705 chkrng = DPA_GetPtr(ranges->hdpa, index);
2706 if (chkrng->lower >= nItem)
2707 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2708 if (chkrng->upper > nItem)
2709 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2711 return TRUE;
2714 static BOOL ranges_add(RANGES ranges, RANGE range)
2716 RANGE srchrgn;
2717 INT index;
2719 TRACE("(%s)\n", debugrange(&range));
2720 ranges_check(ranges, "before add");
2722 /* try find overlapping regions first */
2723 srchrgn.lower = range.lower - 1;
2724 srchrgn.upper = range.upper + 1;
2725 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2727 if (index == -1)
2729 RANGE *newrgn;
2731 TRACE("Adding new range\n");
2733 /* create the brand new range to insert */
2734 newrgn = Alloc(sizeof(RANGE));
2735 if(!newrgn) goto fail;
2736 *newrgn = range;
2738 /* figure out where to insert it */
2739 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2740 TRACE("index=%d\n", index);
2741 if (index == -1) index = 0;
2743 /* and get it over with */
2744 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2746 Free(newrgn);
2747 goto fail;
2750 else
2752 RANGE *chkrgn, *mrgrgn;
2753 INT fromindex, mergeindex;
2755 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2756 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2758 chkrgn->lower = min(range.lower, chkrgn->lower);
2759 chkrgn->upper = max(range.upper, chkrgn->upper);
2761 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2763 /* merge now common anges */
2764 fromindex = 0;
2765 srchrgn.lower = chkrgn->lower - 1;
2766 srchrgn.upper = chkrgn->upper + 1;
2770 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2771 if (mergeindex == -1) break;
2772 if (mergeindex == index)
2774 fromindex = index + 1;
2775 continue;
2778 TRACE("Merge with index %i\n", mergeindex);
2780 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2781 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2782 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2783 Free(mrgrgn);
2784 DPA_DeletePtr(ranges->hdpa, mergeindex);
2785 if (mergeindex < index) index --;
2786 } while(1);
2789 ranges_check(ranges, "after add");
2790 return TRUE;
2792 fail:
2793 ranges_check(ranges, "failed add");
2794 return FALSE;
2797 static BOOL ranges_del(RANGES ranges, RANGE range)
2799 RANGE *chkrgn;
2800 INT index;
2802 TRACE("(%s)\n", debugrange(&range));
2803 ranges_check(ranges, "before del");
2805 /* we don't use DPAS_SORTED here, since we need *
2806 * to find the first overlapping range */
2807 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2808 while(index != -1)
2810 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2812 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2814 /* case 1: Same range */
2815 if ( (chkrgn->upper == range.upper) &&
2816 (chkrgn->lower == range.lower) )
2818 DPA_DeletePtr(ranges->hdpa, index);
2819 break;
2821 /* case 2: engulf */
2822 else if ( (chkrgn->upper <= range.upper) &&
2823 (chkrgn->lower >= range.lower) )
2825 DPA_DeletePtr(ranges->hdpa, index);
2827 /* case 3: overlap upper */
2828 else if ( (chkrgn->upper <= range.upper) &&
2829 (chkrgn->lower < range.lower) )
2831 chkrgn->upper = range.lower;
2833 /* case 4: overlap lower */
2834 else if ( (chkrgn->upper > range.upper) &&
2835 (chkrgn->lower >= range.lower) )
2837 chkrgn->lower = range.upper;
2838 break;
2840 /* case 5: fully internal */
2841 else
2843 RANGE tmprgn = *chkrgn, *newrgn;
2845 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2846 newrgn->lower = chkrgn->lower;
2847 newrgn->upper = range.lower;
2848 chkrgn->lower = range.upper;
2849 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2851 Free(newrgn);
2852 goto fail;
2854 chkrgn = &tmprgn;
2855 break;
2858 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2861 ranges_check(ranges, "after del");
2862 return TRUE;
2864 fail:
2865 ranges_check(ranges, "failed del");
2866 return FALSE;
2869 /***
2870 * DESCRIPTION:
2871 * Removes all selection ranges
2873 * Parameters(s):
2874 * [I] infoPtr : valid pointer to the listview structure
2875 * [I] toSkip : item range to skip removing the selection
2877 * RETURNS:
2878 * SUCCESS : TRUE
2879 * FAILURE : TRUE
2881 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2883 LVITEMW lvItem;
2884 ITERATOR i;
2885 RANGES clone;
2887 TRACE("()\n");
2889 lvItem.state = 0;
2890 lvItem.stateMask = LVIS_SELECTED;
2892 /* need to clone the DPA because callbacks can change it */
2893 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2894 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2895 while(iterator_next(&i))
2896 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2897 /* note that the iterator destructor will free the cloned range */
2898 iterator_destroy(&i);
2900 return TRUE;
2903 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2905 RANGES toSkip;
2907 if (!(toSkip = ranges_create(1))) return FALSE;
2908 if (nItem != -1) ranges_additem(toSkip, nItem);
2909 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2910 ranges_destroy(toSkip);
2911 return TRUE;
2914 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2916 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2919 /***
2920 * DESCRIPTION:
2921 * Retrieves the number of items that are marked as selected.
2923 * PARAMETER(S):
2924 * [I] infoPtr : valid pointer to the listview structure
2926 * RETURN:
2927 * Number of items selected.
2929 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2931 INT nSelectedCount = 0;
2933 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2935 INT i;
2936 for (i = 0; i < infoPtr->nItemCount; i++)
2938 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2939 nSelectedCount++;
2942 else
2943 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2945 TRACE("nSelectedCount=%d\n", nSelectedCount);
2946 return nSelectedCount;
2949 /***
2950 * DESCRIPTION:
2951 * Manages the item focus.
2953 * PARAMETER(S):
2954 * [I] infoPtr : valid pointer to the listview structure
2955 * [I] nItem : item index
2957 * RETURN:
2958 * TRUE : focused item changed
2959 * FALSE : focused item has NOT changed
2961 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2963 INT oldFocus = infoPtr->nFocusedItem;
2964 LVITEMW lvItem;
2966 if (nItem == infoPtr->nFocusedItem) return FALSE;
2968 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2969 lvItem.stateMask = LVIS_FOCUSED;
2970 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2972 return oldFocus != infoPtr->nFocusedItem;
2975 /* Helper function for LISTVIEW_ShiftIndices *only* */
2976 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2978 if (nShiftItem < nItem) return nShiftItem;
2980 if (nShiftItem > nItem) return nShiftItem + direction;
2982 if (direction > 0) return nShiftItem + direction;
2984 return min(nShiftItem, infoPtr->nItemCount - 1);
2988 * DESCRIPTION:
2989 * Updates the various indices after an item has been inserted or deleted.
2991 * PARAMETER(S):
2992 * [I] infoPtr : valid pointer to the listview structure
2993 * [I] nItem : item index
2994 * [I] direction : Direction of shift, +1 or -1.
2996 * RETURN:
2997 * None
2999 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3001 INT nNewFocus;
3002 BOOL bOldChange;
3004 /* temporarily disable change notification while shifting items */
3005 bOldChange = infoPtr->bDoChangeNotify;
3006 infoPtr->bDoChangeNotify = FALSE;
3008 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3010 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3012 assert(abs(direction) == 1);
3014 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3016 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3017 if (nNewFocus != infoPtr->nFocusedItem)
3018 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3020 /* But we are not supposed to modify nHotItem! */
3022 infoPtr->bDoChangeNotify = bOldChange;
3027 * DESCRIPTION:
3028 * Adds a block of selections.
3030 * PARAMETER(S):
3031 * [I] infoPtr : valid pointer to the listview structure
3032 * [I] nItem : item index
3034 * RETURN:
3035 * Whether the window is still valid.
3037 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3039 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3040 INT nLast = max(infoPtr->nSelectionMark, nItem);
3041 HWND hwndSelf = infoPtr->hwndSelf;
3042 NMLVODSTATECHANGE nmlv;
3043 LVITEMW item;
3044 BOOL bOldChange;
3045 INT i;
3047 /* Temporarily disable change notification
3048 * If the control is LVS_OWNERDATA, we need to send
3049 * only one LVN_ODSTATECHANGED notification.
3050 * See MSDN documentation for LVN_ITEMCHANGED.
3052 bOldChange = infoPtr->bDoChangeNotify;
3053 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3055 if (nFirst == -1) nFirst = nItem;
3057 item.state = LVIS_SELECTED;
3058 item.stateMask = LVIS_SELECTED;
3060 for (i = nFirst; i <= nLast; i++)
3061 LISTVIEW_SetItemState(infoPtr,i,&item);
3063 ZeroMemory(&nmlv, sizeof(nmlv));
3064 nmlv.iFrom = nFirst;
3065 nmlv.iTo = nLast;
3066 nmlv.uNewState = 0;
3067 nmlv.uOldState = item.state;
3069 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3070 if (!IsWindow(hwndSelf))
3071 return FALSE;
3072 infoPtr->bDoChangeNotify = bOldChange;
3073 return TRUE;
3077 /***
3078 * DESCRIPTION:
3079 * Sets a single group selection.
3081 * PARAMETER(S):
3082 * [I] infoPtr : valid pointer to the listview structure
3083 * [I] nItem : item index
3085 * RETURN:
3086 * None
3088 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3090 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3091 RANGES selection;
3092 LVITEMW item;
3093 ITERATOR i;
3094 BOOL bOldChange;
3096 if (!(selection = ranges_create(100))) return;
3098 item.state = LVIS_SELECTED;
3099 item.stateMask = LVIS_SELECTED;
3101 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3103 if (infoPtr->nSelectionMark == -1)
3105 infoPtr->nSelectionMark = nItem;
3106 ranges_additem(selection, nItem);
3108 else
3110 RANGE sel;
3112 sel.lower = min(infoPtr->nSelectionMark, nItem);
3113 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3114 ranges_add(selection, sel);
3117 else
3119 RECT rcItem, rcSel, rcSelMark;
3120 POINT ptItem;
3122 rcItem.left = LVIR_BOUNDS;
3123 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3124 rcSelMark.left = LVIR_BOUNDS;
3125 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3126 UnionRect(&rcSel, &rcItem, &rcSelMark);
3127 iterator_frameditems(&i, infoPtr, &rcSel);
3128 while(iterator_next(&i))
3130 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3131 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3133 iterator_destroy(&i);
3136 bOldChange = infoPtr->bDoChangeNotify;
3137 infoPtr->bDoChangeNotify = FALSE;
3139 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3142 iterator_rangesitems(&i, selection);
3143 while(iterator_next(&i))
3144 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3145 /* this will also destroy the selection */
3146 iterator_destroy(&i);
3148 infoPtr->bDoChangeNotify = bOldChange;
3150 LISTVIEW_SetItemFocus(infoPtr, nItem);
3153 /***
3154 * DESCRIPTION:
3155 * Sets a single selection.
3157 * PARAMETER(S):
3158 * [I] infoPtr : valid pointer to the listview structure
3159 * [I] nItem : item index
3161 * RETURN:
3162 * None
3164 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3166 LVITEMW lvItem;
3168 TRACE("nItem=%d\n", nItem);
3170 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3172 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3173 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3174 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3176 infoPtr->nSelectionMark = nItem;
3179 /***
3180 * DESCRIPTION:
3181 * Set selection(s) with keyboard.
3183 * PARAMETER(S):
3184 * [I] infoPtr : valid pointer to the listview structure
3185 * [I] nItem : item index
3187 * RETURN:
3188 * SUCCESS : TRUE (needs to be repainted)
3189 * FAILURE : FALSE (nothing has changed)
3191 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3193 /* FIXME: pass in the state */
3194 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3195 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3196 BOOL bResult = FALSE;
3198 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3199 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3201 if (infoPtr->dwStyle & LVS_SINGLESEL)
3203 bResult = TRUE;
3204 LISTVIEW_SetSelection(infoPtr, nItem);
3206 else
3208 if (wShift)
3210 bResult = TRUE;
3211 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3213 else if (wCtrl)
3215 LVITEMW lvItem;
3216 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3217 lvItem.stateMask = LVIS_SELECTED;
3218 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3220 if (lvItem.state & LVIS_SELECTED)
3221 infoPtr->nSelectionMark = nItem;
3223 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3225 else
3227 bResult = TRUE;
3228 LISTVIEW_SetSelection(infoPtr, nItem);
3231 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3234 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3235 return bResult;
3238 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3240 LVHITTESTINFO lvHitTestInfo;
3242 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3243 lvHitTestInfo.pt.x = pt.x;
3244 lvHitTestInfo.pt.y = pt.y;
3246 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3248 lpLVItem->mask = LVIF_PARAM;
3249 lpLVItem->iItem = lvHitTestInfo.iItem;
3250 lpLVItem->iSubItem = 0;
3252 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3255 /***
3256 * DESCRIPTION:
3257 * Called when the mouse is being actively tracked and has hovered for a specified
3258 * amount of time
3260 * PARAMETER(S):
3261 * [I] infoPtr : valid pointer to the listview structure
3262 * [I] fwKeys : key indicator
3263 * [I] x,y : mouse position
3265 * RETURN:
3266 * 0 if the message was processed, non-zero if there was an error
3268 * INFO:
3269 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3270 * over the item for a certain period of time.
3273 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3275 if (infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3277 LVITEMW item;
3278 POINT pt;
3280 pt.x = x;
3281 pt.y = y;
3283 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3284 LISTVIEW_SetSelection(infoPtr, item.iItem);
3287 return 0;
3290 /***
3291 * DESCRIPTION:
3292 * Called whenever WM_MOUSEMOVE is received.
3294 * PARAMETER(S):
3295 * [I] infoPtr : valid pointer to the listview structure
3296 * [I] fwKeys : key indicator
3297 * [I] x,y : mouse position
3299 * RETURN:
3300 * 0 if the message is processed, non-zero if there was an error
3302 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3304 TRACKMOUSEEVENT trackinfo;
3306 if (!(fwKeys & MK_LBUTTON))
3307 infoPtr->bLButtonDown = FALSE;
3309 if (infoPtr->bLButtonDown && DragDetect(infoPtr->hwndSelf, infoPtr->ptClickPos))
3311 LVHITTESTINFO lvHitTestInfo;
3312 NMLISTVIEW nmlv;
3314 lvHitTestInfo.pt = infoPtr->ptClickPos;
3315 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3317 ZeroMemory(&nmlv, sizeof(nmlv));
3318 nmlv.iItem = lvHitTestInfo.iItem;
3319 nmlv.ptAction = infoPtr->ptClickPos;
3321 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3323 return 0;
3325 else
3326 infoPtr->bLButtonDown = FALSE;
3328 /* see if we are supposed to be tracking mouse hovering */
3329 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3330 /* fill in the trackinfo struct */
3331 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3332 trackinfo.dwFlags = TME_QUERY;
3333 trackinfo.hwndTrack = infoPtr->hwndSelf;
3334 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3336 /* see if we are already tracking this hwnd */
3337 _TrackMouseEvent(&trackinfo);
3339 if(!(trackinfo.dwFlags & TME_HOVER)) {
3340 trackinfo.dwFlags = TME_HOVER;
3342 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3343 _TrackMouseEvent(&trackinfo);
3347 return 0;
3351 /***
3352 * Tests wheather the item is assignable to a list with style lStyle
3354 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3356 if ( (lpLVItem->mask & LVIF_TEXT) &&
3357 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3358 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3360 return TRUE;
3364 /***
3365 * DESCRIPTION:
3366 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3368 * PARAMETER(S):
3369 * [I] infoPtr : valid pointer to the listview structure
3370 * [I] lpLVItem : valid pointer to new item atttributes
3371 * [I] isNew : the item being set is being inserted
3372 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3373 * [O] bChanged : will be set to TRUE if the item really changed
3375 * RETURN:
3376 * SUCCESS : TRUE
3377 * FAILURE : FALSE
3379 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3381 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3382 ITEM_INFO *lpItem;
3383 NMLISTVIEW nmlv;
3384 UINT uChanged = 0;
3385 LVITEMW item;
3387 TRACE("()\n");
3389 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3391 if (lpLVItem->mask == 0) return TRUE;
3393 if (infoPtr->dwStyle & LVS_OWNERDATA)
3395 /* a virtual listview we stores only selection and focus */
3396 if (lpLVItem->mask & ~LVIF_STATE)
3397 return FALSE;
3398 lpItem = NULL;
3400 else
3402 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3403 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3404 assert (lpItem);
3407 /* we need to get the lParam and state of the item */
3408 item.iItem = lpLVItem->iItem;
3409 item.iSubItem = lpLVItem->iSubItem;
3410 item.mask = LVIF_STATE | LVIF_PARAM;
3411 item.stateMask = ~0;
3412 item.state = 0;
3413 item.lParam = 0;
3414 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3416 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3417 /* determine what fields will change */
3418 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3419 uChanged |= LVIF_STATE;
3421 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3422 uChanged |= LVIF_IMAGE;
3424 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3425 uChanged |= LVIF_PARAM;
3427 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3428 uChanged |= LVIF_INDENT;
3430 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3431 uChanged |= LVIF_TEXT;
3433 TRACE("uChanged=0x%x\n", uChanged);
3434 if (!uChanged) return TRUE;
3435 *bChanged = TRUE;
3437 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3438 nmlv.iItem = lpLVItem->iItem;
3439 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3440 nmlv.uOldState = item.state;
3441 nmlv.uChanged = uChanged;
3442 nmlv.lParam = item.lParam;
3444 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3445 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3446 /* are enabled */
3447 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3449 HWND hwndSelf = infoPtr->hwndSelf;
3451 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3452 return FALSE;
3453 if (!IsWindow(hwndSelf))
3454 return FALSE;
3457 /* copy information */
3458 if (lpLVItem->mask & LVIF_TEXT)
3459 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3461 if (lpLVItem->mask & LVIF_IMAGE)
3462 lpItem->hdr.iImage = lpLVItem->iImage;
3464 if (lpLVItem->mask & LVIF_PARAM)
3465 lpItem->lParam = lpLVItem->lParam;
3467 if (lpLVItem->mask & LVIF_INDENT)
3468 lpItem->iIndent = lpLVItem->iIndent;
3470 if (uChanged & LVIF_STATE)
3472 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3474 lpItem->state &= ~lpLVItem->stateMask;
3475 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3477 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3479 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3480 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3482 else if (lpLVItem->stateMask & LVIS_SELECTED)
3483 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3485 /* if we are asked to change focus, and we manage it, do it */
3486 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3488 if (lpLVItem->state & LVIS_FOCUSED)
3490 LISTVIEW_SetItemFocus(infoPtr, -1);
3491 infoPtr->nFocusedItem = lpLVItem->iItem;
3492 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3494 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3495 infoPtr->nFocusedItem = -1;
3499 /* if we're inserting the item, we're done */
3500 if (isNew) return TRUE;
3502 /* send LVN_ITEMCHANGED notification */
3503 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3504 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3506 return TRUE;
3509 /***
3510 * DESCRIPTION:
3511 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3513 * PARAMETER(S):
3514 * [I] infoPtr : valid pointer to the listview structure
3515 * [I] lpLVItem : valid pointer to new subitem atttributes
3516 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3517 * [O] bChanged : will be set to TRUE if the item really changed
3519 * RETURN:
3520 * SUCCESS : TRUE
3521 * FAILURE : FALSE
3523 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3525 HDPA hdpaSubItems;
3526 SUBITEM_INFO *lpSubItem;
3528 /* we do not support subitems for virtual listviews */
3529 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3531 /* set subitem only if column is present */
3532 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3534 /* First do some sanity checks */
3535 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3536 particularly useful. We currently do not actually do anything with
3537 the flag on subitems.
3539 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3540 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3542 /* get the subitem structure, and create it if not there */
3543 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3544 assert (hdpaSubItems);
3546 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3547 if (!lpSubItem)
3549 SUBITEM_INFO *tmpSubItem;
3550 INT i;
3552 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3553 if (!lpSubItem) return FALSE;
3554 /* we could binary search here, if need be...*/
3555 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3557 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3558 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3560 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3562 Free(lpSubItem);
3563 return FALSE;
3565 lpSubItem->iSubItem = lpLVItem->iSubItem;
3566 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3567 *bChanged = TRUE;
3570 if (lpLVItem->mask & LVIF_IMAGE)
3571 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3573 lpSubItem->hdr.iImage = lpLVItem->iImage;
3574 *bChanged = TRUE;
3577 if (lpLVItem->mask & LVIF_TEXT)
3578 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3580 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3581 *bChanged = TRUE;
3584 return TRUE;
3587 /***
3588 * DESCRIPTION:
3589 * Sets item attributes.
3591 * PARAMETER(S):
3592 * [I] infoPtr : valid pointer to the listview structure
3593 * [I] lpLVItem : new item atttributes
3594 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3596 * RETURN:
3597 * SUCCESS : TRUE
3598 * FAILURE : FALSE
3600 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3602 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3603 HWND hwndSelf = infoPtr->hwndSelf;
3604 LPWSTR pszText = NULL;
3605 BOOL bResult, bChanged = FALSE;
3607 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3609 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3610 return FALSE;
3612 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3613 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3615 pszText = lpLVItem->pszText;
3616 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3619 /* actually set the fields */
3620 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3622 if (lpLVItem->iSubItem)
3623 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3624 else
3625 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3626 if (!IsWindow(hwndSelf))
3627 return FALSE;
3629 /* redraw item, if necessary */
3630 if (bChanged && !infoPtr->bIsDrawing)
3632 /* this little optimization eliminates some nasty flicker */
3633 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3634 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3635 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3636 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3637 else
3638 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3640 /* restore text */
3641 if (pszText)
3643 textfreeT(lpLVItem->pszText, isW);
3644 lpLVItem->pszText = pszText;
3647 return bResult;
3650 /***
3651 * DESCRIPTION:
3652 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3654 * PARAMETER(S):
3655 * [I] infoPtr : valid pointer to the listview structure
3657 * RETURN:
3658 * item index
3660 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3662 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3663 INT nItem = 0;
3664 SCROLLINFO scrollInfo;
3666 scrollInfo.cbSize = sizeof(SCROLLINFO);
3667 scrollInfo.fMask = SIF_POS;
3669 if (uView == LVS_LIST)
3671 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3672 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3674 else if (uView == LVS_REPORT)
3676 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3677 nItem = scrollInfo.nPos;
3679 else
3681 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3682 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3685 TRACE("nItem=%d\n", nItem);
3687 return nItem;
3691 /***
3692 * DESCRIPTION:
3693 * Erases the background of the given rectangle
3695 * PARAMETER(S):
3696 * [I] infoPtr : valid pointer to the listview structure
3697 * [I] hdc : device context handle
3698 * [I] lprcBox : clipping rectangle
3700 * RETURN:
3701 * Success: TRUE
3702 * Failure: FALSE
3704 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3706 if (!infoPtr->hBkBrush) return FALSE;
3708 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3710 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3713 /***
3714 * DESCRIPTION:
3715 * Draws an item.
3717 * PARAMETER(S):
3718 * [I] infoPtr : valid pointer to the listview structure
3719 * [I] hdc : device context handle
3720 * [I] nItem : item index
3721 * [I] nSubItem : subitem index
3722 * [I] pos : item position in client coordinates
3723 * [I] cdmode : custom draw mode
3725 * RETURN:
3726 * Success: TRUE
3727 * Failure: FALSE
3729 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3731 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3732 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3733 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3734 DWORD cdsubitemmode = CDRF_DODEFAULT;
3735 LPRECT lprcFocus;
3736 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3737 NMLVCUSTOMDRAW nmlvcd;
3738 HIMAGELIST himl;
3739 LVITEMW lvItem;
3740 HFONT hOldFont;
3742 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3744 /* get information needed for drawing the item */
3745 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3746 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3747 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3748 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3749 lvItem.iItem = nItem;
3750 lvItem.iSubItem = nSubItem;
3751 lvItem.state = 0;
3752 lvItem.lParam = 0;
3753 lvItem.cchTextMax = DISP_TEXT_SIZE;
3754 lvItem.pszText = szDispText;
3755 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3756 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3757 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3758 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3759 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3761 /* now check if we need to update the focus rectangle */
3762 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3764 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3765 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3766 OffsetRect(&rcBox, pos.x, pos.y);
3767 OffsetRect(&rcSelect, pos.x, pos.y);
3768 OffsetRect(&rcIcon, pos.x, pos.y);
3769 OffsetRect(&rcStateIcon, pos.x, pos.y);
3770 OffsetRect(&rcLabel, pos.x, pos.y);
3771 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3772 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3773 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3775 /* fill in the custom draw structure */
3776 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3778 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3779 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3780 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3781 if (cdmode & CDRF_NOTIFYITEMDRAW)
3782 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3783 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3784 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3785 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3786 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3788 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3789 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3791 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3792 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3793 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3794 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3796 /* in full row select, subitems, will just use main item's colors */
3797 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3798 nmlvcd.clrTextBk = CLR_NONE;
3800 /* state icons */
3801 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3803 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3804 if (uStateImage)
3806 TRACE("uStateImage=%d\n", uStateImage);
3807 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3808 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3812 /* small icons */
3813 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3814 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3816 TRACE("iImage=%d\n", lvItem.iImage);
3817 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3818 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3819 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3822 /* Don't bother painting item being edited */
3823 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3825 /* FIXME: temporary hack */
3826 rcSelect.left = rcLabel.left;
3828 /* draw the selection background, if we're drawing the main item */
3829 if (nSubItem == 0)
3831 /* in icon mode, the label rect is really what we want to draw the
3832 * background for */
3833 if (uView == LVS_ICON)
3834 rcSelect = rcLabel;
3836 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3837 rcSelect.right = rcBox.right;
3839 if (nmlvcd.clrTextBk != CLR_NONE)
3840 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3841 if(lprcFocus) *lprcFocus = rcSelect;
3844 /* figure out the text drawing flags */
3845 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3846 if (uView == LVS_ICON)
3847 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3848 else if (nSubItem)
3850 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3852 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3853 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3854 default: uFormat |= DT_LEFT;
3857 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3859 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3860 else rcLabel.left += LABEL_HOR_PADDING;
3862 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3863 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3865 postpaint:
3866 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3867 notify_postpaint(infoPtr, &nmlvcd);
3868 if (cdsubitemmode & CDRF_NEWFONT)
3869 SelectObject(hdc, hOldFont);
3870 return TRUE;
3873 /***
3874 * DESCRIPTION:
3875 * Draws listview items when in owner draw mode.
3877 * PARAMETER(S):
3878 * [I] infoPtr : valid pointer to the listview structure
3879 * [I] hdc : device context handle
3881 * RETURN:
3882 * None
3884 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3886 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3887 DWORD cditemmode = CDRF_DODEFAULT;
3888 NMLVCUSTOMDRAW nmlvcd;
3889 POINT Origin, Position;
3890 DRAWITEMSTRUCT dis;
3891 LVITEMW item;
3893 TRACE("()\n");
3895 ZeroMemory(&dis, sizeof(dis));
3897 /* Get scroll info once before loop */
3898 LISTVIEW_GetOrigin(infoPtr, &Origin);
3900 /* iterate through the invalidated rows */
3901 while(iterator_next(i))
3903 item.iItem = i->nItem;
3904 item.iSubItem = 0;
3905 item.mask = LVIF_PARAM | LVIF_STATE;
3906 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3907 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3909 dis.CtlType = ODT_LISTVIEW;
3910 dis.CtlID = uID;
3911 dis.itemID = item.iItem;
3912 dis.itemAction = ODA_DRAWENTIRE;
3913 dis.itemState = 0;
3914 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3915 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3916 dis.hwndItem = infoPtr->hwndSelf;
3917 dis.hDC = hdc;
3918 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3919 dis.rcItem.left = Position.x + Origin.x;
3920 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3921 dis.rcItem.top = Position.y + Origin.y;
3922 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3923 dis.itemData = item.lParam;
3925 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3928 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3929 * structure for the rest. of the paint cycle
3931 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3932 if (cdmode & CDRF_NOTIFYITEMDRAW)
3933 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3935 if (!(cditemmode & CDRF_SKIPDEFAULT))
3937 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3938 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3941 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3942 notify_postpaint(infoPtr, &nmlvcd);
3946 /***
3947 * DESCRIPTION:
3948 * Draws listview items when in report display mode.
3950 * PARAMETER(S):
3951 * [I] infoPtr : valid pointer to the listview structure
3952 * [I] hdc : device context handle
3953 * [I] cdmode : custom draw mode
3955 * RETURN:
3956 * None
3958 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3960 INT rgntype;
3961 RECT rcClip, rcItem;
3962 POINT Origin, Position;
3963 RANGE colRange;
3964 ITERATOR j;
3966 TRACE("()\n");
3968 /* figure out what to draw */
3969 rgntype = GetClipBox(hdc, &rcClip);
3970 if (rgntype == NULLREGION) return;
3972 /* Get scroll info once before loop */
3973 LISTVIEW_GetOrigin(infoPtr, &Origin);
3975 /* narrow down the columns we need to paint */
3976 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3978 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3979 if (rcItem.right + Origin.x >= rcClip.left) break;
3981 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3983 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3984 if (rcItem.left + Origin.x < rcClip.right) break;
3986 iterator_rangeitems(&j, colRange);
3988 /* in full row select, we _have_ to draw the main item */
3989 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3990 j.nSpecial = 0;
3992 /* iterate through the invalidated rows */
3993 while(iterator_next(i))
3995 /* iterate through the invalidated columns */
3996 while(iterator_next(&j))
3998 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3999 Position.x += Origin.x;
4000 Position.y += Origin.y;
4002 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4004 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4005 rcItem.top = 0;
4006 rcItem.bottom = infoPtr->nItemHeight;
4007 OffsetRect(&rcItem, Position.x, Position.y);
4008 if (!RectVisible(hdc, &rcItem)) continue;
4011 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4014 iterator_destroy(&j);
4017 /***
4018 * DESCRIPTION:
4019 * Draws listview items when in list display mode.
4021 * PARAMETER(S):
4022 * [I] infoPtr : valid pointer to the listview structure
4023 * [I] hdc : device context handle
4024 * [I] cdmode : custom draw mode
4026 * RETURN:
4027 * None
4029 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4031 POINT Origin, Position;
4033 /* Get scroll info once before loop */
4034 LISTVIEW_GetOrigin(infoPtr, &Origin);
4036 while(iterator_prev(i))
4038 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4039 Position.x += Origin.x;
4040 Position.y += Origin.y;
4042 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4047 /***
4048 * DESCRIPTION:
4049 * Draws listview items.
4051 * PARAMETER(S):
4052 * [I] infoPtr : valid pointer to the listview structure
4053 * [I] hdc : device context handle
4054 * [I] prcErase : rect to be erased before refresh (may be NULL)
4056 * RETURN:
4057 * NoneX
4059 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4061 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4062 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4063 NMLVCUSTOMDRAW nmlvcd;
4064 HFONT hOldFont = 0;
4065 DWORD cdmode;
4066 INT oldBkMode = 0;
4067 RECT rcClient;
4068 ITERATOR i;
4069 HDC hdcOrig = hdc;
4070 HBITMAP hbmp = NULL;
4072 LISTVIEW_DUMP(infoPtr);
4074 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4075 TRACE("double buffering\n");
4077 hdc = CreateCompatibleDC(hdcOrig);
4078 if (!hdc) {
4079 ERR("Failed to create DC for backbuffer\n");
4080 return;
4082 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4083 infoPtr->rcList.bottom);
4084 if (!hbmp) {
4085 ERR("Failed to create bitmap for backbuffer\n");
4086 DeleteDC(hdc);
4087 return;
4090 SelectObject(hdc, hbmp);
4091 SelectObject(hdc, infoPtr->hFont);
4092 } else {
4093 /* Save dc values we're gonna trash while drawing
4094 * FIXME: Should be done in LISTVIEW_DrawItem() */
4095 hOldFont = SelectObject(hdc, infoPtr->hFont);
4096 oldBkMode = GetBkMode(hdc);
4097 oldBkColor = GetBkColor(hdc);
4098 oldTextColor = GetTextColor(hdc);
4101 infoPtr->bIsDrawing = TRUE;
4103 if (prcErase) {
4104 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4105 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4106 /* If no erasing was done (usually because RedrawWindow was called
4107 * with RDW_INVALIDATE only) we need to copy the old contents into
4108 * the backbuffer before continuing. */
4109 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4110 infoPtr->rcList.right - infoPtr->rcList.left,
4111 infoPtr->rcList.bottom - infoPtr->rcList.top,
4112 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4115 /* FIXME: Shouldn't need to do this */
4116 oldClrTextBk = infoPtr->clrTextBk;
4117 oldClrText = infoPtr->clrText;
4119 infoPtr->cditemmode = CDRF_DODEFAULT;
4121 GetClientRect(infoPtr->hwndSelf, &rcClient);
4122 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4123 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4124 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4125 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4127 /* Use these colors to draw the items */
4128 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4129 infoPtr->clrText = nmlvcd.clrText;
4131 /* nothing to draw */
4132 if(infoPtr->nItemCount == 0) goto enddraw;
4134 /* figure out what we need to draw */
4135 iterator_visibleitems(&i, infoPtr, hdc);
4137 /* send cache hint notification */
4138 if (infoPtr->dwStyle & LVS_OWNERDATA)
4140 RANGE range = iterator_range(&i);
4141 NMLVCACHEHINT nmlv;
4143 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4144 nmlv.iFrom = range.lower;
4145 nmlv.iTo = range.upper - 1;
4146 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4149 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4150 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4151 else
4153 if (uView == LVS_REPORT)
4154 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4155 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4156 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4158 /* if we have a focus rect, draw it */
4159 if (infoPtr->bFocus)
4160 DrawFocusRect(hdc, &infoPtr->rcFocus);
4162 iterator_destroy(&i);
4164 enddraw:
4165 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4166 notify_postpaint(infoPtr, &nmlvcd);
4168 infoPtr->clrTextBk = oldClrTextBk;
4169 infoPtr->clrText = oldClrText;
4171 if(hbmp) {
4172 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4173 infoPtr->rcList.right - infoPtr->rcList.left,
4174 infoPtr->rcList.bottom - infoPtr->rcList.top,
4175 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4177 DeleteObject(hbmp);
4178 DeleteDC(hdc);
4179 } else {
4180 SelectObject(hdc, hOldFont);
4181 SetBkMode(hdc, oldBkMode);
4182 SetBkColor(hdc, oldBkColor);
4183 SetTextColor(hdc, oldTextColor);
4186 infoPtr->bIsDrawing = FALSE;
4190 /***
4191 * DESCRIPTION:
4192 * Calculates the approximate width and height of a given number of items.
4194 * PARAMETER(S):
4195 * [I] infoPtr : valid pointer to the listview structure
4196 * [I] nItemCount : number of items
4197 * [I] wWidth : width
4198 * [I] wHeight : height
4200 * RETURN:
4201 * Returns a DWORD. The width in the low word and the height in high word.
4203 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4204 WORD wWidth, WORD wHeight)
4206 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4207 INT nItemCountPerColumn = 1;
4208 INT nColumnCount = 0;
4209 DWORD dwViewRect = 0;
4211 if (nItemCount == -1)
4212 nItemCount = infoPtr->nItemCount;
4214 if (uView == LVS_LIST)
4216 if (wHeight == 0xFFFF)
4218 /* use current height */
4219 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4222 if (wHeight < infoPtr->nItemHeight)
4223 wHeight = infoPtr->nItemHeight;
4225 if (nItemCount > 0)
4227 if (infoPtr->nItemHeight > 0)
4229 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4230 if (nItemCountPerColumn == 0)
4231 nItemCountPerColumn = 1;
4233 if (nItemCount % nItemCountPerColumn != 0)
4234 nColumnCount = nItemCount / nItemCountPerColumn;
4235 else
4236 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4240 /* Microsoft padding magic */
4241 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4242 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4244 dwViewRect = MAKELONG(wWidth, wHeight);
4246 else if (uView == LVS_REPORT)
4248 RECT rcBox;
4250 if (infoPtr->nItemCount > 0)
4252 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4253 wWidth = rcBox.right - rcBox.left;
4254 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4256 else
4258 /* use current height and width */
4259 if (wHeight == 0xffff)
4260 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4261 if (wWidth == 0xffff)
4262 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4265 dwViewRect = MAKELONG(wWidth, wHeight);
4267 else if (uView == LVS_SMALLICON)
4268 FIXME("uView == LVS_SMALLICON: not implemented\n");
4269 else if (uView == LVS_ICON)
4270 FIXME("uView == LVS_ICON: not implemented\n");
4272 return dwViewRect;
4276 /***
4277 * DESCRIPTION:
4278 * Create a drag image list for the specified item.
4280 * PARAMETER(S):
4281 * [I] infoPtr : valid pointer to the listview structure
4282 * [I] iItem : index of item
4283 * [O] lppt : Upperr-left corner of the image
4285 * RETURN:
4286 * Returns a handle to the image list if successful, NULL otherwise.
4288 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4290 RECT rcItem;
4291 SIZE size;
4292 POINT pos;
4293 HDC hdc, hdcOrig;
4294 HBITMAP hbmp, hOldbmp;
4295 HIMAGELIST dragList = 0;
4296 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4298 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4299 return 0;
4301 rcItem.left = LVIR_BOUNDS;
4302 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4303 return 0;
4305 lppt->x = rcItem.left;
4306 lppt->y = rcItem.top;
4308 size.cx = rcItem.right - rcItem.left;
4309 size.cy = rcItem.bottom - rcItem.top;
4311 hdcOrig = GetDC(infoPtr->hwndSelf);
4312 hdc = CreateCompatibleDC(hdcOrig);
4313 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4314 hOldbmp = SelectObject(hdc, hbmp);
4316 rcItem.left = rcItem.top = 0;
4317 rcItem.right = size.cx;
4318 rcItem.bottom = size.cy;
4319 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4321 pos.x = pos.y = 0;
4322 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4324 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4325 SelectObject(hdc, hOldbmp);
4326 ImageList_Add(dragList, hbmp, 0);
4328 else
4329 SelectObject(hdc, hOldbmp);
4331 DeleteObject(hbmp);
4332 DeleteDC(hdc);
4333 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4335 TRACE("ret=%p\n", dragList);
4337 return dragList;
4341 /***
4342 * DESCRIPTION:
4343 * Removes all listview items and subitems.
4345 * PARAMETER(S):
4346 * [I] infoPtr : valid pointer to the listview structure
4348 * RETURN:
4349 * SUCCESS : TRUE
4350 * FAILURE : FALSE
4352 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4354 NMLISTVIEW nmlv;
4355 HDPA hdpaSubItems = NULL;
4356 BOOL bSuppress;
4357 ITEMHDR *hdrItem;
4358 INT i, j;
4360 TRACE("()\n");
4362 /* we do it directly, to avoid notifications */
4363 ranges_clear(infoPtr->selectionRanges);
4364 infoPtr->nSelectionMark = -1;
4365 infoPtr->nFocusedItem = -1;
4366 SetRectEmpty(&infoPtr->rcFocus);
4367 /* But we are supposed to leave nHotItem as is! */
4370 /* send LVN_DELETEALLITEMS notification */
4371 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4372 nmlv.iItem = -1;
4373 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4375 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4377 /* send LVN_DELETEITEM notification, if not suppressed */
4378 if (!bSuppress) notify_deleteitem(infoPtr, i);
4379 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4381 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4382 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4384 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4385 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4386 Free(hdrItem);
4388 DPA_Destroy(hdpaSubItems);
4389 DPA_DeletePtr(infoPtr->hdpaItems, i);
4391 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4392 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4393 infoPtr->nItemCount --;
4396 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4397 LISTVIEW_UpdateScroll(infoPtr);
4398 LISTVIEW_InvalidateList(infoPtr);
4400 return TRUE;
4403 /***
4404 * DESCRIPTION:
4405 * Scrolls, and updates the columns, when a column is changing width.
4407 * PARAMETER(S):
4408 * [I] infoPtr : valid pointer to the listview structure
4409 * [I] nColumn : column to scroll
4410 * [I] dx : amount of scroll, in pixels
4412 * RETURN:
4413 * None.
4415 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4417 COLUMN_INFO *lpColumnInfo;
4418 RECT rcOld, rcCol;
4419 POINT ptOrigin;
4420 INT nCol;
4422 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4423 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4424 rcCol = lpColumnInfo->rcHeader;
4425 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4426 rcCol.left = rcCol.right;
4428 /* ajust the other columns */
4429 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4431 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4432 lpColumnInfo->rcHeader.left += dx;
4433 lpColumnInfo->rcHeader.right += dx;
4436 /* do not update screen if not in report mode */
4437 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4439 /* if we have a focus, must first erase the focus rect */
4440 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4442 /* Need to reset the item width when inserting a new column */
4443 infoPtr->nItemWidth += dx;
4445 LISTVIEW_UpdateScroll(infoPtr);
4446 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4448 /* scroll to cover the deleted column, and invalidate for redraw */
4449 rcOld = infoPtr->rcList;
4450 rcOld.left = ptOrigin.x + rcCol.left + dx;
4451 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4453 /* we can restore focus now */
4454 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4457 /***
4458 * DESCRIPTION:
4459 * Removes a column from the listview control.
4461 * PARAMETER(S):
4462 * [I] infoPtr : valid pointer to the listview structure
4463 * [I] nColumn : column index
4465 * RETURN:
4466 * SUCCESS : TRUE
4467 * FAILURE : FALSE
4469 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4471 RECT rcCol;
4473 TRACE("nColumn=%d\n", nColumn);
4475 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4476 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4478 /* While the MSDN specifically says that column zero should not be deleted,
4479 what actually happens is that the column itself is deleted but no items or subitems
4480 are removed.
4483 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4485 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4486 return FALSE;
4488 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4489 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4491 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4493 SUBITEM_INFO *lpSubItem, *lpDelItem;
4494 HDPA hdpaSubItems;
4495 INT nItem, nSubItem, i;
4497 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4499 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4500 nSubItem = 0;
4501 lpDelItem = 0;
4502 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4504 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4505 if (lpSubItem->iSubItem == nColumn)
4507 nSubItem = i;
4508 lpDelItem = lpSubItem;
4510 else if (lpSubItem->iSubItem > nColumn)
4512 lpSubItem->iSubItem--;
4516 /* if we found our subitem, zapp it */
4517 if (nSubItem > 0)
4519 /* free string */
4520 if (is_textW(lpDelItem->hdr.pszText))
4521 Free(lpDelItem->hdr.pszText);
4523 /* free item */
4524 Free(lpDelItem);
4526 /* free dpa memory */
4527 DPA_DeletePtr(hdpaSubItems, nSubItem);
4532 /* update the other column info */
4533 LISTVIEW_UpdateItemSize(infoPtr);
4534 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4535 LISTVIEW_InvalidateList(infoPtr);
4536 else
4537 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4539 return TRUE;
4542 /***
4543 * DESCRIPTION:
4544 * Invalidates the listview after an item's insertion or deletion.
4546 * PARAMETER(S):
4547 * [I] infoPtr : valid pointer to the listview structure
4548 * [I] nItem : item index
4549 * [I] dir : -1 if deleting, 1 if inserting
4551 * RETURN:
4552 * None
4554 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4556 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4557 INT nPerCol, nItemCol, nItemRow;
4558 RECT rcScroll;
4559 POINT Origin;
4561 /* if we don't refresh, what's the point of scrolling? */
4562 if (!is_redrawing(infoPtr)) return;
4564 assert (abs(dir) == 1);
4566 /* arrange icons if autoarrange is on */
4567 if (is_autoarrange(infoPtr))
4569 BOOL arrange = TRUE;
4570 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4571 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4572 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4575 /* scrollbars need updating */
4576 LISTVIEW_UpdateScroll(infoPtr);
4578 /* figure out the item's position */
4579 if (uView == LVS_REPORT)
4580 nPerCol = infoPtr->nItemCount + 1;
4581 else if (uView == LVS_LIST)
4582 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4583 else /* LVS_ICON, or LVS_SMALLICON */
4584 return;
4586 nItemCol = nItem / nPerCol;
4587 nItemRow = nItem % nPerCol;
4588 LISTVIEW_GetOrigin(infoPtr, &Origin);
4590 /* move the items below up a slot */
4591 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4592 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4593 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4594 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4595 OffsetRect(&rcScroll, Origin.x, Origin.y);
4596 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4597 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4599 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4600 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4601 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4604 /* report has only that column, so we're done */
4605 if (uView == LVS_REPORT) return;
4607 /* now for LISTs, we have to deal with the columns to the right */
4608 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4609 rcScroll.top = 0;
4610 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4611 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4612 OffsetRect(&rcScroll, Origin.x, Origin.y);
4613 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4614 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4615 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4618 /***
4619 * DESCRIPTION:
4620 * Removes an item from the listview control.
4622 * PARAMETER(S):
4623 * [I] infoPtr : valid pointer to the listview structure
4624 * [I] nItem : item index
4626 * RETURN:
4627 * SUCCESS : TRUE
4628 * FAILURE : FALSE
4630 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4632 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4633 LVITEMW item;
4635 TRACE("(nItem=%d)\n", nItem);
4637 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4639 /* remove selection, and focus */
4640 item.state = 0;
4641 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4642 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4644 /* send LVN_DELETEITEM notification. */
4645 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4647 /* we need to do this here, because we'll be deleting stuff */
4648 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4649 LISTVIEW_InvalidateItem(infoPtr, nItem);
4651 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4653 HDPA hdpaSubItems;
4654 ITEMHDR *hdrItem;
4655 INT i;
4657 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4658 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4660 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4661 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4662 Free(hdrItem);
4664 DPA_Destroy(hdpaSubItems);
4667 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4669 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4670 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4673 infoPtr->nItemCount--;
4674 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4676 /* now is the invalidation fun */
4677 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4678 return TRUE;
4682 /***
4683 * DESCRIPTION:
4684 * Callback implementation for editlabel control
4686 * PARAMETER(S):
4687 * [I] infoPtr : valid pointer to the listview structure
4688 * [I] pszText : modified text
4689 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4691 * RETURN:
4692 * SUCCESS : TRUE
4693 * FAILURE : FALSE
4695 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4697 HWND hwndSelf = infoPtr->hwndSelf;
4698 NMLVDISPINFOW dispInfo;
4700 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4702 ZeroMemory(&dispInfo, sizeof(dispInfo));
4703 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4704 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4705 dispInfo.item.iSubItem = 0;
4706 dispInfo.item.stateMask = ~0;
4707 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4708 /* add the text from the edit in */
4709 dispInfo.item.mask |= LVIF_TEXT;
4710 dispInfo.item.pszText = pszText;
4711 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4713 /* Do we need to update the Item Text */
4714 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4715 if (!IsWindow(hwndSelf))
4716 return FALSE;
4717 if (!pszText) return TRUE;
4719 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4721 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nEditLabelItem);
4722 ITEM_INFO* lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
4723 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4725 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nEditLabelItem);
4726 return TRUE;
4730 ZeroMemory(&dispInfo, sizeof(dispInfo));
4731 dispInfo.item.mask = LVIF_TEXT;
4732 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4733 dispInfo.item.iSubItem = 0;
4734 dispInfo.item.pszText = pszText;
4735 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4736 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4739 /***
4740 * DESCRIPTION:
4741 * Begin in place editing of specified list view item
4743 * PARAMETER(S):
4744 * [I] infoPtr : valid pointer to the listview structure
4745 * [I] nItem : item index
4746 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4748 * RETURN:
4749 * SUCCESS : TRUE
4750 * FAILURE : FALSE
4752 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4754 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4755 NMLVDISPINFOW dispInfo;
4756 RECT rect;
4757 HWND hwndSelf = infoPtr->hwndSelf;
4759 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4761 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4762 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4764 infoPtr->nEditLabelItem = nItem;
4766 /* Is the EditBox still there, if so remove it */
4767 if(infoPtr->hwndEdit != 0)
4769 SetFocus(infoPtr->hwndSelf);
4770 infoPtr->hwndEdit = 0;
4773 LISTVIEW_SetSelection(infoPtr, nItem);
4774 LISTVIEW_SetItemFocus(infoPtr, nItem);
4775 LISTVIEW_InvalidateItem(infoPtr, nItem);
4777 rect.left = LVIR_LABEL;
4778 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4780 ZeroMemory(&dispInfo, sizeof(dispInfo));
4781 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4782 dispInfo.item.iItem = nItem;
4783 dispInfo.item.iSubItem = 0;
4784 dispInfo.item.stateMask = ~0;
4785 dispInfo.item.pszText = szDispText;
4786 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4787 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4789 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4790 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4791 if (!infoPtr->hwndEdit) return 0;
4793 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4795 if (!IsWindow(hwndSelf))
4796 return 0;
4797 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4798 infoPtr->hwndEdit = 0;
4799 return 0;
4802 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4803 SetFocus(infoPtr->hwndEdit);
4804 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4805 return infoPtr->hwndEdit;
4809 /***
4810 * DESCRIPTION:
4811 * Ensures the specified item is visible, scrolling into view if necessary.
4813 * PARAMETER(S):
4814 * [I] infoPtr : valid pointer to the listview structure
4815 * [I] nItem : item index
4816 * [I] bPartial : partially or entirely visible
4818 * RETURN:
4819 * SUCCESS : TRUE
4820 * FAILURE : FALSE
4822 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4824 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4825 INT nScrollPosHeight = 0;
4826 INT nScrollPosWidth = 0;
4827 INT nHorzAdjust = 0;
4828 INT nVertAdjust = 0;
4829 INT nHorzDiff = 0;
4830 INT nVertDiff = 0;
4831 RECT rcItem, rcTemp;
4833 rcItem.left = LVIR_BOUNDS;
4834 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4836 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4838 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4840 /* scroll left/right, but in LVS_REPORT mode */
4841 if (uView == LVS_LIST)
4842 nScrollPosWidth = infoPtr->nItemWidth;
4843 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4844 nScrollPosWidth = 1;
4846 if (rcItem.left < infoPtr->rcList.left)
4848 nHorzAdjust = -1;
4849 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4851 else
4853 nHorzAdjust = 1;
4854 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4858 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4860 /* scroll up/down, but not in LVS_LIST mode */
4861 if (uView == LVS_REPORT)
4862 nScrollPosHeight = infoPtr->nItemHeight;
4863 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4864 nScrollPosHeight = 1;
4866 if (rcItem.top < infoPtr->rcList.top)
4868 nVertAdjust = -1;
4869 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4871 else
4873 nVertAdjust = 1;
4874 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4878 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4880 if (nScrollPosWidth)
4882 INT diff = nHorzDiff / nScrollPosWidth;
4883 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4884 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4887 if (nScrollPosHeight)
4889 INT diff = nVertDiff / nScrollPosHeight;
4890 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4891 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4894 return TRUE;
4897 /***
4898 * DESCRIPTION:
4899 * Searches for an item with specific characteristics.
4901 * PARAMETER(S):
4902 * [I] hwnd : window handle
4903 * [I] nStart : base item index
4904 * [I] lpFindInfo : item information to look for
4906 * RETURN:
4907 * SUCCESS : index of item
4908 * FAILURE : -1
4910 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
4911 const LVFINDINFOW *lpFindInfo)
4913 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4914 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4915 BOOL bWrap = FALSE, bNearest = FALSE;
4916 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4917 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4918 POINT Position, Destination;
4919 LVITEMW lvItem;
4921 if (!lpFindInfo || nItem < 0) return -1;
4923 lvItem.mask = 0;
4924 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4926 lvItem.mask |= LVIF_TEXT;
4927 lvItem.pszText = szDispText;
4928 lvItem.cchTextMax = DISP_TEXT_SIZE;
4931 if (lpFindInfo->flags & LVFI_WRAP)
4932 bWrap = TRUE;
4934 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4935 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4937 POINT Origin;
4938 RECT rcArea;
4940 LISTVIEW_GetOrigin(infoPtr, &Origin);
4941 Destination.x = lpFindInfo->pt.x - Origin.x;
4942 Destination.y = lpFindInfo->pt.y - Origin.y;
4943 switch(lpFindInfo->vkDirection)
4945 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4946 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4947 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4948 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4949 case VK_HOME: Destination.x = Destination.y = 0; break;
4950 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4951 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4952 case VK_END:
4953 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4954 Destination.x = rcArea.right;
4955 Destination.y = rcArea.bottom;
4956 break;
4957 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4959 bNearest = TRUE;
4961 else Destination.x = Destination.y = 0;
4963 /* if LVFI_PARAM is specified, all other flags are ignored */
4964 if (lpFindInfo->flags & LVFI_PARAM)
4966 lvItem.mask |= LVIF_PARAM;
4967 bNearest = FALSE;
4968 lvItem.mask &= ~LVIF_TEXT;
4971 again:
4972 for (; nItem < nLast; nItem++)
4974 lvItem.iItem = nItem;
4975 lvItem.iSubItem = 0;
4976 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4978 if (lvItem.mask & LVIF_PARAM)
4980 if (lpFindInfo->lParam == lvItem.lParam)
4981 return nItem;
4982 else
4983 continue;
4986 if (lvItem.mask & LVIF_TEXT)
4988 if (lpFindInfo->flags & LVFI_PARTIAL)
4990 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4992 else
4994 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4998 if (!bNearest) return nItem;
5000 /* This is very inefficient. To do a good job here,
5001 * we need a sorted array of (x,y) item positions */
5002 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5004 /* compute the distance^2 to the destination */
5005 xdist = Destination.x - Position.x;
5006 ydist = Destination.y - Position.y;
5007 dist = xdist * xdist + ydist * ydist;
5009 /* remember the distance, and item if it's closer */
5010 if (dist < mindist)
5012 mindist = dist;
5013 nNearestItem = nItem;
5017 if (bWrap)
5019 nItem = 0;
5020 nLast = min(nStart + 1, infoPtr->nItemCount);
5021 bWrap = FALSE;
5022 goto again;
5025 return nNearestItem;
5028 /***
5029 * DESCRIPTION:
5030 * Searches for an item with specific characteristics.
5032 * PARAMETER(S):
5033 * [I] hwnd : window handle
5034 * [I] nStart : base item index
5035 * [I] lpFindInfo : item information to look for
5037 * RETURN:
5038 * SUCCESS : index of item
5039 * FAILURE : -1
5041 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5042 const LVFINDINFOA *lpFindInfo)
5044 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5045 LVFINDINFOW fiw;
5046 INT res;
5047 LPWSTR strW = NULL;
5049 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5050 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5051 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5052 textfreeT(strW, FALSE);
5053 return res;
5056 /***
5057 * DESCRIPTION:
5058 * Retrieves the background image of the listview control.
5060 * PARAMETER(S):
5061 * [I] infoPtr : valid pointer to the listview structure
5062 * [O] lpBkImage : background image attributes
5064 * RETURN:
5065 * SUCCESS : TRUE
5066 * FAILURE : FALSE
5068 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5069 /* { */
5070 /* FIXME (listview, "empty stub!\n"); */
5071 /* return FALSE; */
5072 /* } */
5074 /***
5075 * DESCRIPTION:
5076 * Retrieves column attributes.
5078 * PARAMETER(S):
5079 * [I] infoPtr : valid pointer to the listview structure
5080 * [I] nColumn : column index
5081 * [IO] lpColumn : column information
5082 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5083 * otherwise it is in fact a LPLVCOLUMNA
5085 * RETURN:
5086 * SUCCESS : TRUE
5087 * FAILURE : FALSE
5089 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5091 COLUMN_INFO *lpColumnInfo;
5092 HDITEMW hdi;
5094 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5095 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5097 /* initialize memory */
5098 ZeroMemory(&hdi, sizeof(hdi));
5100 if (lpColumn->mask & LVCF_TEXT)
5102 hdi.mask |= HDI_TEXT;
5103 hdi.pszText = lpColumn->pszText;
5104 hdi.cchTextMax = lpColumn->cchTextMax;
5107 if (lpColumn->mask & LVCF_IMAGE)
5108 hdi.mask |= HDI_IMAGE;
5110 if (lpColumn->mask & LVCF_ORDER)
5111 hdi.mask |= HDI_ORDER;
5113 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5115 if (lpColumn->mask & LVCF_FMT)
5116 lpColumn->fmt = lpColumnInfo->fmt;
5118 if (lpColumn->mask & LVCF_WIDTH)
5119 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5121 if (lpColumn->mask & LVCF_IMAGE)
5122 lpColumn->iImage = hdi.iImage;
5124 if (lpColumn->mask & LVCF_ORDER)
5125 lpColumn->iOrder = hdi.iOrder;
5127 return TRUE;
5131 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5133 INT i;
5135 if (!lpiArray)
5136 return FALSE;
5138 /* FIXME: little hack */
5139 for (i = 0; i < iCount; i++)
5140 lpiArray[i] = i;
5142 return TRUE;
5145 /***
5146 * DESCRIPTION:
5147 * Retrieves the column width.
5149 * PARAMETER(S):
5150 * [I] infoPtr : valid pointer to the listview structure
5151 * [I] int : column index
5153 * RETURN:
5154 * SUCCESS : column width
5155 * FAILURE : zero
5157 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5159 INT nColumnWidth = 0;
5160 HDITEMW hdItem;
5162 TRACE("nColumn=%d\n", nColumn);
5164 /* we have a 'column' in LIST and REPORT mode only */
5165 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5167 case LVS_LIST:
5168 nColumnWidth = infoPtr->nItemWidth;
5169 break;
5170 case LVS_REPORT:
5171 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5172 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5173 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5175 * TODO: should we do the same in LVM_GETCOLUMN?
5177 hdItem.mask = HDI_WIDTH;
5178 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5180 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5181 return 0;
5183 nColumnWidth = hdItem.cxy;
5184 break;
5187 TRACE("nColumnWidth=%d\n", nColumnWidth);
5188 return nColumnWidth;
5191 /***
5192 * DESCRIPTION:
5193 * In list or report display mode, retrieves the number of items that can fit
5194 * vertically in the visible area. In icon or small icon display mode,
5195 * retrieves the total number of visible items.
5197 * PARAMETER(S):
5198 * [I] infoPtr : valid pointer to the listview structure
5200 * RETURN:
5201 * Number of fully visible items.
5203 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5205 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5207 case LVS_ICON:
5208 case LVS_SMALLICON:
5209 return infoPtr->nItemCount;
5210 case LVS_REPORT:
5211 return LISTVIEW_GetCountPerColumn(infoPtr);
5212 case LVS_LIST:
5213 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5215 assert(FALSE);
5216 return 0;
5219 /***
5220 * DESCRIPTION:
5221 * Retrieves an image list handle.
5223 * PARAMETER(S):
5224 * [I] infoPtr : valid pointer to the listview structure
5225 * [I] nImageList : image list identifier
5227 * RETURN:
5228 * SUCCESS : image list handle
5229 * FAILURE : NULL
5231 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5233 switch (nImageList)
5235 case LVSIL_NORMAL: return infoPtr->himlNormal;
5236 case LVSIL_SMALL: return infoPtr->himlSmall;
5237 case LVSIL_STATE: return infoPtr->himlState;
5239 return NULL;
5242 /* LISTVIEW_GetISearchString */
5244 /***
5245 * DESCRIPTION:
5246 * Retrieves item attributes.
5248 * PARAMETER(S):
5249 * [I] hwnd : window handle
5250 * [IO] lpLVItem : item info
5251 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5252 * if FALSE, then lpLVItem is a LPLVITEMA.
5254 * NOTE:
5255 * This is the internal 'GetItem' interface -- it tries to
5256 * be smart and avoid text copies, if possible, by modifying
5257 * lpLVItem->pszText to point to the text string. Please note
5258 * that this is not always possible (e.g. OWNERDATA), so on
5259 * entry you *must* supply valid values for pszText, and cchTextMax.
5260 * The only difference to the documented interface is that upon
5261 * return, you should use *only* the lpLVItem->pszText, rather than
5262 * the buffer pointer you provided on input. Most code already does
5263 * that, so it's not a problem.
5264 * For the two cases when the text must be copied (that is,
5265 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5267 * RETURN:
5268 * SUCCESS : TRUE
5269 * FAILURE : FALSE
5271 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5273 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5274 NMLVDISPINFOW dispInfo;
5275 ITEM_INFO *lpItem;
5276 ITEMHDR* pItemHdr;
5277 HDPA hdpaSubItems;
5278 INT isubitem;
5280 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5282 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5283 return FALSE;
5285 if (lpLVItem->mask == 0) return TRUE;
5287 /* make a local copy */
5288 isubitem = lpLVItem->iSubItem;
5290 /* a quick optimization if all we're asked is the focus state
5291 * these queries are worth optimising since they are common,
5292 * and can be answered in constant time, without the heavy accesses */
5293 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5294 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5296 lpLVItem->state = 0;
5297 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5298 lpLVItem->state |= LVIS_FOCUSED;
5299 return TRUE;
5302 ZeroMemory(&dispInfo, sizeof(dispInfo));
5304 /* if the app stores all the data, handle it separately */
5305 if (infoPtr->dwStyle & LVS_OWNERDATA)
5307 dispInfo.item.state = 0;
5309 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5310 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5312 /* NOTE: copy only fields which we _know_ are initialized, some apps
5313 * depend on the uninitialized fields being 0 */
5314 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5315 dispInfo.item.iItem = lpLVItem->iItem;
5316 dispInfo.item.iSubItem = isubitem;
5317 if (lpLVItem->mask & LVIF_TEXT)
5319 dispInfo.item.pszText = lpLVItem->pszText;
5320 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5322 if (lpLVItem->mask & LVIF_STATE)
5323 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5324 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5325 dispInfo.item.stateMask = lpLVItem->stateMask;
5326 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5328 /* full size structure expected - _WIN32IE >= 0x560 */
5329 *lpLVItem = dispInfo.item;
5331 else if (lpLVItem->mask & LVIF_INDENT)
5333 /* indent member expected - _WIN32IE >= 0x300 */
5334 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5336 else
5338 /* minimal structure expected */
5339 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5341 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5344 /* make sure lParam is zeroed out */
5345 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5347 /* we store only a little state, so if we're not asked, we're done */
5348 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5350 /* if focus is handled by us, report it */
5351 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5353 lpLVItem->state &= ~LVIS_FOCUSED;
5354 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5355 lpLVItem->state |= LVIS_FOCUSED;
5358 /* and do the same for selection, if we handle it */
5359 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5361 lpLVItem->state &= ~LVIS_SELECTED;
5362 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5363 lpLVItem->state |= LVIS_SELECTED;
5366 return TRUE;
5369 /* find the item and subitem structures before we proceed */
5370 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5371 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5372 assert (lpItem);
5374 if (isubitem)
5376 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5377 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5378 if (!lpSubItem)
5380 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5381 isubitem = 0;
5384 else
5385 pItemHdr = &lpItem->hdr;
5387 /* Do we need to query the state from the app? */
5388 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5390 dispInfo.item.mask |= LVIF_STATE;
5391 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5394 /* Do we need to enquire about the image? */
5395 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5396 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5398 dispInfo.item.mask |= LVIF_IMAGE;
5399 dispInfo.item.iImage = I_IMAGECALLBACK;
5402 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5403 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5405 dispInfo.item.mask |= LVIF_TEXT;
5406 dispInfo.item.pszText = lpLVItem->pszText;
5407 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5408 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5409 *dispInfo.item.pszText = '\0';
5412 /* If we don't have all the requested info, query the application */
5413 if (dispInfo.item.mask != 0)
5415 dispInfo.item.iItem = lpLVItem->iItem;
5416 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5417 dispInfo.item.lParam = lpItem->lParam;
5418 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5419 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5422 /* we should not store values for subitems */
5423 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5425 /* Now, handle the iImage field */
5426 if (dispInfo.item.mask & LVIF_IMAGE)
5428 lpLVItem->iImage = dispInfo.item.iImage;
5429 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5430 pItemHdr->iImage = dispInfo.item.iImage;
5432 else if (lpLVItem->mask & LVIF_IMAGE)
5434 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5435 lpLVItem->iImage = pItemHdr->iImage;
5436 else
5437 lpLVItem->iImage = 0;
5440 /* The pszText field */
5441 if (dispInfo.item.mask & LVIF_TEXT)
5443 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5444 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5446 lpLVItem->pszText = dispInfo.item.pszText;
5448 else if (lpLVItem->mask & LVIF_TEXT)
5450 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5451 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5454 /* Next is the lParam field */
5455 if (dispInfo.item.mask & LVIF_PARAM)
5457 lpLVItem->lParam = dispInfo.item.lParam;
5458 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5459 lpItem->lParam = dispInfo.item.lParam;
5461 else if (lpLVItem->mask & LVIF_PARAM)
5462 lpLVItem->lParam = lpItem->lParam;
5464 /* if this is a subitem, we're done */
5465 if (isubitem) return TRUE;
5467 /* ... the state field (this one is different due to uCallbackmask) */
5468 if (lpLVItem->mask & LVIF_STATE)
5470 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5471 if (dispInfo.item.mask & LVIF_STATE)
5473 lpLVItem->state &= ~dispInfo.item.stateMask;
5474 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5476 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5478 lpLVItem->state &= ~LVIS_FOCUSED;
5479 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5480 lpLVItem->state |= LVIS_FOCUSED;
5482 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5484 lpLVItem->state &= ~LVIS_SELECTED;
5485 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5486 lpLVItem->state |= LVIS_SELECTED;
5490 /* and last, but not least, the indent field */
5491 if (lpLVItem->mask & LVIF_INDENT)
5492 lpLVItem->iIndent = lpItem->iIndent;
5494 return TRUE;
5497 /***
5498 * DESCRIPTION:
5499 * Retrieves item attributes.
5501 * PARAMETER(S):
5502 * [I] hwnd : window handle
5503 * [IO] lpLVItem : item info
5504 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5505 * if FALSE, then lpLVItem is a LPLVITEMA.
5507 * NOTE:
5508 * This is the external 'GetItem' interface -- it properly copies
5509 * the text in the provided buffer.
5511 * RETURN:
5512 * SUCCESS : TRUE
5513 * FAILURE : FALSE
5515 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5517 LPWSTR pszText;
5518 BOOL bResult;
5520 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5521 return FALSE;
5523 pszText = lpLVItem->pszText;
5524 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5525 if (bResult && lpLVItem->pszText != pszText)
5526 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5527 lpLVItem->pszText = pszText;
5529 return bResult;
5533 /***
5534 * DESCRIPTION:
5535 * Retrieves the position (upper-left) of the listview control item.
5536 * Note that for LVS_ICON style, the upper-left is that of the icon
5537 * and not the bounding box.
5539 * PARAMETER(S):
5540 * [I] infoPtr : valid pointer to the listview structure
5541 * [I] nItem : item index
5542 * [O] lpptPosition : coordinate information
5544 * RETURN:
5545 * SUCCESS : TRUE
5546 * FAILURE : FALSE
5548 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5550 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5551 POINT Origin;
5553 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5555 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5557 LISTVIEW_GetOrigin(infoPtr, &Origin);
5558 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5560 if (uView == LVS_ICON)
5562 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5563 lpptPosition->y += ICON_TOP_PADDING;
5565 lpptPosition->x += Origin.x;
5566 lpptPosition->y += Origin.y;
5568 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5569 return TRUE;
5573 /***
5574 * DESCRIPTION:
5575 * Retrieves the bounding rectangle for a listview control item.
5577 * PARAMETER(S):
5578 * [I] infoPtr : valid pointer to the listview structure
5579 * [I] nItem : item index
5580 * [IO] lprc : bounding rectangle coordinates
5581 * lprc->left specifies the portion of the item for which the bounding
5582 * rectangle will be retrieved.
5584 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5585 * including the icon and label.
5587 * * For LVS_ICON
5588 * * Experiment shows that native control returns:
5589 * * width = min (48, length of text line)
5590 * * .left = position.x - (width - iconsize.cx)/2
5591 * * .right = .left + width
5592 * * height = #lines of text * ntmHeight + icon height + 8
5593 * * .top = position.y - 2
5594 * * .bottom = .top + height
5595 * * separation between items .y = itemSpacing.cy - height
5596 * * .x = itemSpacing.cx - width
5597 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5599 * * For LVS_ICON
5600 * * Experiment shows that native control returns:
5601 * * width = iconSize.cx + 16
5602 * * .left = position.x - (width - iconsize.cx)/2
5603 * * .right = .left + width
5604 * * height = iconSize.cy + 4
5605 * * .top = position.y - 2
5606 * * .bottom = .top + height
5607 * * separation between items .y = itemSpacing.cy - height
5608 * * .x = itemSpacing.cx - width
5609 * LVIR_LABEL Returns the bounding rectangle of the item text.
5611 * * For LVS_ICON
5612 * * Experiment shows that native control returns:
5613 * * width = text length
5614 * * .left = position.x - width/2
5615 * * .right = .left + width
5616 * * height = ntmH * linecount + 2
5617 * * .top = position.y + iconSize.cy + 6
5618 * * .bottom = .top + height
5619 * * separation between items .y = itemSpacing.cy - height
5620 * * .x = itemSpacing.cx - width
5621 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5622 * rectangles, but excludes columns in report view.
5624 * RETURN:
5625 * SUCCESS : TRUE
5626 * FAILURE : FALSE
5628 * NOTES
5629 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5630 * upon whether the window has the focus currently and on whether the item
5631 * is the one with the focus. Ensure that the control's record of which
5632 * item has the focus agrees with the items' records.
5634 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5636 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5637 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5638 BOOL doLabel = TRUE, oversizedBox = FALSE;
5639 POINT Position, Origin;
5640 LVITEMW lvItem;
5642 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5644 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5646 LISTVIEW_GetOrigin(infoPtr, &Origin);
5647 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5649 /* Be smart and try to figure out the minimum we have to do */
5650 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5651 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5652 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5653 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5654 oversizedBox = TRUE;
5656 /* get what we need from the item before hand, so we make
5657 * only one request. This can speed up things, if data
5658 * is stored on the app side */
5659 lvItem.mask = 0;
5660 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5661 if (doLabel) lvItem.mask |= LVIF_TEXT;
5662 lvItem.iItem = nItem;
5663 lvItem.iSubItem = 0;
5664 lvItem.pszText = szDispText;
5665 lvItem.cchTextMax = DISP_TEXT_SIZE;
5666 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5667 /* we got the state already up, simulate it here, to avoid a reget */
5668 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5670 lvItem.mask |= LVIF_STATE;
5671 lvItem.stateMask = LVIS_FOCUSED;
5672 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5675 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5676 lprc->left = LVIR_BOUNDS;
5677 switch(lprc->left)
5679 case LVIR_ICON:
5680 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5681 break;
5683 case LVIR_LABEL:
5684 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5685 break;
5687 case LVIR_BOUNDS:
5688 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5689 break;
5691 case LVIR_SELECTBOUNDS:
5692 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5693 break;
5695 default:
5696 WARN("Unknown value: %d\n", lprc->left);
5697 return FALSE;
5700 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5702 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5704 return TRUE;
5707 /***
5708 * DESCRIPTION:
5709 * Retrieves the spacing between listview control items.
5711 * PARAMETER(S):
5712 * [I] infoPtr : valid pointer to the listview structure
5713 * [IO] lprc : rectangle to receive the output
5714 * on input, lprc->top = nSubItem
5715 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5717 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5718 * not only those of the first column.
5719 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5721 * RETURN:
5722 * TRUE: success
5723 * FALSE: failure
5725 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5727 POINT Position;
5728 LVITEMW lvItem;
5729 INT nColumn;
5731 if (!lprc) return FALSE;
5733 nColumn = lprc->top;
5735 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5736 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5737 if (lprc->top == 0)
5738 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5740 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5742 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5744 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5746 lvItem.mask = 0;
5747 lvItem.iItem = nItem;
5748 lvItem.iSubItem = nColumn;
5750 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5751 switch(lprc->left)
5753 case LVIR_ICON:
5754 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5755 break;
5757 case LVIR_LABEL:
5758 case LVIR_BOUNDS:
5759 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5760 break;
5762 default:
5763 ERR("Unknown bounds=%d\n", lprc->left);
5764 return FALSE;
5767 OffsetRect(lprc, Position.x, Position.y);
5768 return TRUE;
5772 /***
5773 * DESCRIPTION:
5774 * Retrieves the width of a label.
5776 * PARAMETER(S):
5777 * [I] infoPtr : valid pointer to the listview structure
5779 * RETURN:
5780 * SUCCESS : string width (in pixels)
5781 * FAILURE : zero
5783 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5785 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5786 LVITEMW lvItem;
5788 TRACE("(nItem=%d)\n", nItem);
5790 lvItem.mask = LVIF_TEXT;
5791 lvItem.iItem = nItem;
5792 lvItem.iSubItem = 0;
5793 lvItem.pszText = szDispText;
5794 lvItem.cchTextMax = DISP_TEXT_SIZE;
5795 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5797 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5800 /***
5801 * DESCRIPTION:
5802 * Retrieves the spacing between listview control items.
5804 * PARAMETER(S):
5805 * [I] infoPtr : valid pointer to the listview structure
5806 * [I] bSmall : flag for small or large icon
5808 * RETURN:
5809 * Horizontal + vertical spacing
5811 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5813 LONG lResult;
5815 if (!bSmall)
5817 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5819 else
5821 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5822 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5823 else
5824 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5826 return lResult;
5829 /***
5830 * DESCRIPTION:
5831 * Retrieves the state of a listview control item.
5833 * PARAMETER(S):
5834 * [I] infoPtr : valid pointer to the listview structure
5835 * [I] nItem : item index
5836 * [I] uMask : state mask
5838 * RETURN:
5839 * State specified by the mask.
5841 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5843 LVITEMW lvItem;
5845 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5847 lvItem.iItem = nItem;
5848 lvItem.iSubItem = 0;
5849 lvItem.mask = LVIF_STATE;
5850 lvItem.stateMask = uMask;
5851 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5853 return lvItem.state & uMask;
5856 /***
5857 * DESCRIPTION:
5858 * Retrieves the text of a listview control item or subitem.
5860 * PARAMETER(S):
5861 * [I] hwnd : window handle
5862 * [I] nItem : item index
5863 * [IO] lpLVItem : item information
5864 * [I] isW : TRUE if lpLVItem is Unicode
5866 * RETURN:
5867 * SUCCESS : string length
5868 * FAILURE : 0
5870 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5872 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5874 lpLVItem->mask = LVIF_TEXT;
5875 lpLVItem->iItem = nItem;
5876 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5878 return textlenT(lpLVItem->pszText, isW);
5881 /***
5882 * DESCRIPTION:
5883 * Searches for an item based on properties + relationships.
5885 * PARAMETER(S):
5886 * [I] infoPtr : valid pointer to the listview structure
5887 * [I] nItem : item index
5888 * [I] uFlags : relationship flag
5890 * RETURN:
5891 * SUCCESS : item index
5892 * FAILURE : -1
5894 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5896 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5897 UINT uMask = 0;
5898 LVFINDINFOW lvFindInfo;
5899 INT nCountPerColumn;
5900 INT nCountPerRow;
5901 INT i;
5903 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5904 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5906 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5908 if (uFlags & LVNI_CUT)
5909 uMask |= LVIS_CUT;
5911 if (uFlags & LVNI_DROPHILITED)
5912 uMask |= LVIS_DROPHILITED;
5914 if (uFlags & LVNI_FOCUSED)
5915 uMask |= LVIS_FOCUSED;
5917 if (uFlags & LVNI_SELECTED)
5918 uMask |= LVIS_SELECTED;
5920 /* if we're asked for the focused item, that's only one,
5921 * so it's worth optimizing */
5922 if (uFlags & LVNI_FOCUSED)
5924 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
5925 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5928 if (uFlags & LVNI_ABOVE)
5930 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5932 while (nItem >= 0)
5934 nItem--;
5935 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5936 return nItem;
5939 else
5941 /* Special case for autoarrange - move 'til the top of a list */
5942 if (is_autoarrange(infoPtr))
5944 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5945 while (nItem - nCountPerRow >= 0)
5947 nItem -= nCountPerRow;
5948 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5949 return nItem;
5951 return -1;
5953 lvFindInfo.flags = LVFI_NEARESTXY;
5954 lvFindInfo.vkDirection = VK_UP;
5955 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5956 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5958 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5959 return nItem;
5963 else if (uFlags & LVNI_BELOW)
5965 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5967 while (nItem < infoPtr->nItemCount)
5969 nItem++;
5970 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5971 return nItem;
5974 else
5976 /* Special case for autoarrange - move 'til the bottom of a list */
5977 if (is_autoarrange(infoPtr))
5979 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5980 while (nItem + nCountPerRow < infoPtr->nItemCount )
5982 nItem += nCountPerRow;
5983 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5984 return nItem;
5986 return -1;
5988 lvFindInfo.flags = LVFI_NEARESTXY;
5989 lvFindInfo.vkDirection = VK_DOWN;
5990 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
5991 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5993 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5994 return nItem;
5998 else if (uFlags & LVNI_TOLEFT)
6000 if (uView == LVS_LIST)
6002 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6003 while (nItem - nCountPerColumn >= 0)
6005 nItem -= nCountPerColumn;
6006 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6007 return nItem;
6010 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6012 /* Special case for autoarrange - move 'ti the beginning of a row */
6013 if (is_autoarrange(infoPtr))
6015 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6016 while (nItem % nCountPerRow > 0)
6018 nItem --;
6019 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6020 return nItem;
6022 return -1;
6024 lvFindInfo.flags = LVFI_NEARESTXY;
6025 lvFindInfo.vkDirection = VK_LEFT;
6026 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6027 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6029 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6030 return nItem;
6034 else if (uFlags & LVNI_TORIGHT)
6036 if (uView == LVS_LIST)
6038 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6039 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6041 nItem += nCountPerColumn;
6042 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6043 return nItem;
6046 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6048 /* Special case for autoarrange - move 'til the end of a row */
6049 if (is_autoarrange(infoPtr))
6051 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6052 while (nItem % nCountPerRow < nCountPerRow - 1 )
6054 nItem ++;
6055 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6056 return nItem;
6058 return -1;
6060 lvFindInfo.flags = LVFI_NEARESTXY;
6061 lvFindInfo.vkDirection = VK_RIGHT;
6062 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6063 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6065 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6066 return nItem;
6070 else
6072 nItem++;
6074 /* search by index */
6075 for (i = nItem; i < infoPtr->nItemCount; i++)
6077 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6078 return i;
6082 return -1;
6085 /* LISTVIEW_GetNumberOfWorkAreas */
6087 /***
6088 * DESCRIPTION:
6089 * Retrieves the origin coordinates when in icon or small icon display mode.
6091 * PARAMETER(S):
6092 * [I] infoPtr : valid pointer to the listview structure
6093 * [O] lpptOrigin : coordinate information
6095 * RETURN:
6096 * None.
6098 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6100 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6101 INT nHorzPos = 0, nVertPos = 0;
6102 SCROLLINFO scrollInfo;
6104 scrollInfo.cbSize = sizeof(SCROLLINFO);
6105 scrollInfo.fMask = SIF_POS;
6107 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6108 nHorzPos = scrollInfo.nPos;
6109 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6110 nVertPos = scrollInfo.nPos;
6112 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6114 lpptOrigin->x = infoPtr->rcList.left;
6115 lpptOrigin->y = infoPtr->rcList.top;
6116 if (uView == LVS_LIST)
6117 nHorzPos *= infoPtr->nItemWidth;
6118 else if (uView == LVS_REPORT)
6119 nVertPos *= infoPtr->nItemHeight;
6121 lpptOrigin->x -= nHorzPos;
6122 lpptOrigin->y -= nVertPos;
6124 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6127 /***
6128 * DESCRIPTION:
6129 * Retrieves the width of a string.
6131 * PARAMETER(S):
6132 * [I] hwnd : window handle
6133 * [I] lpszText : text string to process
6134 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6136 * RETURN:
6137 * SUCCESS : string width (in pixels)
6138 * FAILURE : zero
6140 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6142 SIZE stringSize;
6144 stringSize.cx = 0;
6145 if (is_textT(lpszText, isW))
6147 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6148 HDC hdc = GetDC(infoPtr->hwndSelf);
6149 HFONT hOldFont = SelectObject(hdc, hFont);
6151 if (isW)
6152 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6153 else
6154 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6155 SelectObject(hdc, hOldFont);
6156 ReleaseDC(infoPtr->hwndSelf, hdc);
6158 return stringSize.cx;
6161 /***
6162 * DESCRIPTION:
6163 * Determines which listview item is located at the specified position.
6165 * PARAMETER(S):
6166 * [I] infoPtr : valid pointer to the listview structure
6167 * [IO] lpht : hit test information
6168 * [I] subitem : fill out iSubItem.
6169 * [I] select : return the index only if the hit selects the item
6171 * NOTE:
6172 * (mm 20001022): We must not allow iSubItem to be touched, for
6173 * an app might pass only a structure with space up to iItem!
6174 * (MS Office 97 does that for instance in the file open dialog)
6176 * RETURN:
6177 * SUCCESS : item index
6178 * FAILURE : -1
6180 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6182 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6183 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6184 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6185 POINT Origin, Position, opt;
6186 LVITEMW lvItem;
6187 ITERATOR i;
6188 INT iItem;
6190 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6192 lpht->flags = 0;
6193 lpht->iItem = -1;
6194 if (subitem) lpht->iSubItem = 0;
6196 if (infoPtr->rcList.left > lpht->pt.x)
6197 lpht->flags |= LVHT_TOLEFT;
6198 else if (infoPtr->rcList.right < lpht->pt.x)
6199 lpht->flags |= LVHT_TORIGHT;
6201 if (infoPtr->rcList.top > lpht->pt.y)
6202 lpht->flags |= LVHT_ABOVE;
6203 else if (infoPtr->rcList.bottom < lpht->pt.y)
6204 lpht->flags |= LVHT_BELOW;
6206 TRACE("lpht->flags=0x%x\n", lpht->flags);
6207 if (lpht->flags) return -1;
6209 lpht->flags |= LVHT_NOWHERE;
6211 LISTVIEW_GetOrigin(infoPtr, &Origin);
6213 /* first deal with the large items */
6214 rcSearch.left = lpht->pt.x;
6215 rcSearch.top = lpht->pt.y;
6216 rcSearch.right = rcSearch.left + 1;
6217 rcSearch.bottom = rcSearch.top + 1;
6219 iterator_frameditems(&i, infoPtr, &rcSearch);
6220 iterator_next(&i); /* go to first item in the sequence */
6221 iItem = i.nItem;
6222 iterator_destroy(&i);
6224 TRACE("lpht->iItem=%d\n", iItem);
6225 if (iItem == -1) return -1;
6227 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6228 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6229 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6230 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6231 lvItem.iItem = iItem;
6232 lvItem.iSubItem = 0;
6233 lvItem.pszText = szDispText;
6234 lvItem.cchTextMax = DISP_TEXT_SIZE;
6235 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6236 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6238 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6239 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6240 opt.x = lpht->pt.x - Position.x - Origin.x;
6241 opt.y = lpht->pt.y - Position.y - Origin.y;
6243 if (uView == LVS_REPORT)
6244 rcBounds = rcBox;
6245 else
6246 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6247 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6248 if (!PtInRect(&rcBounds, opt)) return -1;
6250 if (PtInRect(&rcIcon, opt))
6251 lpht->flags |= LVHT_ONITEMICON;
6252 else if (PtInRect(&rcLabel, opt))
6253 lpht->flags |= LVHT_ONITEMLABEL;
6254 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6255 lpht->flags |= LVHT_ONITEMSTATEICON;
6256 if (lpht->flags & LVHT_ONITEM)
6257 lpht->flags &= ~LVHT_NOWHERE;
6259 TRACE("lpht->flags=0x%x\n", lpht->flags);
6260 if (uView == LVS_REPORT && subitem)
6262 INT j;
6264 rcBounds.right = rcBounds.left;
6265 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6267 rcBounds.left = rcBounds.right;
6268 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6269 if (PtInRect(&rcBounds, opt))
6271 lpht->iSubItem = j;
6272 break;
6277 if (select && !(uView == LVS_REPORT &&
6278 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6279 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6281 if (uView == LVS_REPORT)
6283 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6284 UnionRect(&rcBounds, &rcBounds, &rcState);
6286 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6288 return lpht->iItem = iItem;
6292 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6293 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6294 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6295 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6296 their own sort proc. when sending LVM_SORTITEMS.
6298 /* Platform SDK:
6299 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6301 LVS_SORTXXX must be specified,
6302 LVS_OWNERDRAW is not set,
6303 <item>.pszText is not LPSTR_TEXTCALLBACK.
6305 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6306 are sorted based on item text..."
6308 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6310 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6311 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6312 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6314 /* if we're sorting descending, negate the return value */
6315 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6318 /***
6319 * DESCRIPTION:
6320 * Inserts a new item in the listview control.
6322 * PARAMETER(S):
6323 * [I] infoPtr : valid pointer to the listview structure
6324 * [I] lpLVItem : item information
6325 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6327 * RETURN:
6328 * SUCCESS : new item index
6329 * FAILURE : -1
6331 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6333 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6334 INT nItem;
6335 HDPA hdpaSubItems;
6336 NMLISTVIEW nmlv;
6337 ITEM_INFO *lpItem;
6338 BOOL is_sorted, has_changed;
6339 LVITEMW item;
6340 HWND hwndSelf = infoPtr->hwndSelf;
6342 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6344 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6346 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6347 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6349 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6351 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6353 /* insert item in listview control data structure */
6354 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6355 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6357 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6358 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6360 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6362 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6363 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6364 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6365 if (nItem == -1) goto fail;
6366 infoPtr->nItemCount++;
6368 /* shift indices first so they don't get tangled */
6369 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6371 /* set the item attributes */
6372 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6374 /* full size structure expected - _WIN32IE >= 0x560 */
6375 item = *lpLVItem;
6377 else if (lpLVItem->mask & LVIF_INDENT)
6379 /* indent member expected - _WIN32IE >= 0x300 */
6380 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6382 else
6384 /* minimal structure expected */
6385 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6387 item.iItem = nItem;
6388 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6390 item.mask |= LVIF_STATE;
6391 item.stateMask |= LVIS_STATEIMAGEMASK;
6392 item.state &= ~LVIS_STATEIMAGEMASK;
6393 item.state |= INDEXTOSTATEIMAGEMASK(1);
6395 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6397 /* if we're sorted, sort the list, and update the index */
6398 if (is_sorted)
6400 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6401 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6402 assert(nItem != -1);
6405 /* make room for the position, if we are in the right mode */
6406 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6408 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6409 goto undo;
6410 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6412 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6413 goto undo;
6417 /* send LVN_INSERTITEM notification */
6418 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6419 nmlv.iItem = nItem;
6420 nmlv.lParam = lpItem->lParam;
6421 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6422 if (!IsWindow(hwndSelf))
6423 return -1;
6425 /* align items (set position of each item) */
6426 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6428 POINT pt;
6430 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6431 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6432 else
6433 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6435 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6438 /* now is the invalidation fun */
6439 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6440 return nItem;
6442 undo:
6443 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6444 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6445 infoPtr->nItemCount--;
6446 fail:
6447 DPA_DeletePtr(hdpaSubItems, 0);
6448 DPA_Destroy (hdpaSubItems);
6449 Free (lpItem);
6450 return -1;
6453 /***
6454 * DESCRIPTION:
6455 * Redraws a range of items.
6457 * PARAMETER(S):
6458 * [I] infoPtr : valid pointer to the listview structure
6459 * [I] nFirst : first item
6460 * [I] nLast : last item
6462 * RETURN:
6463 * SUCCESS : TRUE
6464 * FAILURE : FALSE
6466 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6468 INT i;
6470 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6471 max(nFirst, nLast) >= infoPtr->nItemCount)
6472 return FALSE;
6474 for (i = nFirst; i <= nLast; i++)
6475 LISTVIEW_InvalidateItem(infoPtr, i);
6477 return TRUE;
6480 /***
6481 * DESCRIPTION:
6482 * Scroll the content of a listview.
6484 * PARAMETER(S):
6485 * [I] infoPtr : valid pointer to the listview structure
6486 * [I] dx : horizontal scroll amount in pixels
6487 * [I] dy : vertical scroll amount in pixels
6489 * RETURN:
6490 * SUCCESS : TRUE
6491 * FAILURE : FALSE
6493 * COMMENTS:
6494 * If the control is in report mode (LVS_REPORT) the control can
6495 * be scrolled only in line increments. "dy" will be rounded to the
6496 * nearest number of pixels that are a whole line. Ex: if line height
6497 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6498 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6500 * For: (per experimentaion with native control and CSpy ListView)
6501 * LVS_ICON dy=1 = 1 pixel (vertical only)
6502 * dx ignored
6503 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6504 * dx ignored
6505 * LVS_LIST dx=1 = 1 column (horizontal only)
6506 * but will only scroll 1 column per message
6507 * no matter what the value.
6508 * dy must be 0 or FALSE returned.
6509 * LVS_REPORT dx=1 = 1 pixel
6510 * dy= see above
6513 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6515 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6516 case LVS_REPORT:
6517 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6518 dy /= infoPtr->nItemHeight;
6519 break;
6520 case LVS_LIST:
6521 if (dy != 0) return FALSE;
6522 break;
6523 default: /* icon */
6524 dx = 0;
6525 break;
6528 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6529 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6531 return TRUE;
6534 /***
6535 * DESCRIPTION:
6536 * Sets the background color.
6538 * PARAMETER(S):
6539 * [I] infoPtr : valid pointer to the listview structure
6540 * [I] clrBk : background color
6542 * RETURN:
6543 * SUCCESS : TRUE
6544 * FAILURE : FALSE
6546 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6548 TRACE("(clrBk=%x)\n", clrBk);
6550 if(infoPtr->clrBk != clrBk) {
6551 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6552 infoPtr->clrBk = clrBk;
6553 if (clrBk == CLR_NONE)
6554 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6555 else
6556 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6557 LISTVIEW_InvalidateList(infoPtr);
6560 return TRUE;
6563 /* LISTVIEW_SetBkImage */
6565 /*** Helper for {Insert,Set}ColumnT *only* */
6566 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6567 const LVCOLUMNW *lpColumn, BOOL isW)
6569 if (lpColumn->mask & LVCF_FMT)
6571 /* format member is valid */
6572 lphdi->mask |= HDI_FORMAT;
6574 /* set text alignment (leftmost column must be left-aligned) */
6575 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6576 lphdi->fmt |= HDF_LEFT;
6577 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6578 lphdi->fmt |= HDF_RIGHT;
6579 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6580 lphdi->fmt |= HDF_CENTER;
6582 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6583 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6585 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6587 lphdi->fmt |= HDF_IMAGE;
6588 lphdi->iImage = I_IMAGECALLBACK;
6592 if (lpColumn->mask & LVCF_WIDTH)
6594 lphdi->mask |= HDI_WIDTH;
6595 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6597 /* make it fill the remainder of the controls width */
6598 RECT rcHeader;
6599 INT item_index;
6601 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6603 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6604 lphdi->cxy += rcHeader.right - rcHeader.left;
6607 /* retrieve the layout of the header */
6608 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6609 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6611 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6613 else
6614 lphdi->cxy = lpColumn->cx;
6617 if (lpColumn->mask & LVCF_TEXT)
6619 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6620 lphdi->fmt |= HDF_STRING;
6621 lphdi->pszText = lpColumn->pszText;
6622 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6625 if (lpColumn->mask & LVCF_IMAGE)
6627 lphdi->mask |= HDI_IMAGE;
6628 lphdi->iImage = lpColumn->iImage;
6631 if (lpColumn->mask & LVCF_ORDER)
6633 lphdi->mask |= HDI_ORDER;
6634 lphdi->iOrder = lpColumn->iOrder;
6639 /***
6640 * DESCRIPTION:
6641 * Inserts a new column.
6643 * PARAMETER(S):
6644 * [I] infoPtr : valid pointer to the listview structure
6645 * [I] nColumn : column index
6646 * [I] lpColumn : column information
6647 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6649 * RETURN:
6650 * SUCCESS : new column index
6651 * FAILURE : -1
6653 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6654 const LVCOLUMNW *lpColumn, BOOL isW)
6656 COLUMN_INFO *lpColumnInfo;
6657 INT nNewColumn;
6658 HDITEMW hdi;
6660 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6662 if (!lpColumn || nColumn < 0) return -1;
6663 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6665 ZeroMemory(&hdi, sizeof(HDITEMW));
6666 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6669 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6670 * (can be seen in SPY) otherwise column never gets added.
6672 if (!(lpColumn->mask & LVCF_WIDTH)) {
6673 hdi.mask |= HDI_WIDTH;
6674 hdi.cxy = 10;
6678 * when the iSubItem is available Windows copies it to the header lParam. It seems
6679 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6681 if (lpColumn->mask & LVCF_SUBITEM)
6683 hdi.mask |= HDI_LPARAM;
6684 hdi.lParam = lpColumn->iSubItem;
6687 /* insert item in header control */
6688 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6689 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6690 (WPARAM)nColumn, (LPARAM)&hdi);
6691 if (nNewColumn == -1) return -1;
6692 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6694 /* create our own column info */
6695 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6696 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6698 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6699 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6701 /* now we have to actually adjust the data */
6702 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6704 SUBITEM_INFO *lpSubItem;
6705 HDPA hdpaSubItems;
6706 INT nItem, i;
6708 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6710 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6711 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6713 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6714 if (lpSubItem->iSubItem >= nNewColumn)
6715 lpSubItem->iSubItem++;
6720 /* make space for the new column */
6721 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6722 LISTVIEW_UpdateItemSize(infoPtr);
6724 return nNewColumn;
6726 fail:
6727 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6728 if (lpColumnInfo)
6730 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6731 Free(lpColumnInfo);
6733 return -1;
6736 /***
6737 * DESCRIPTION:
6738 * Sets the attributes of a header item.
6740 * PARAMETER(S):
6741 * [I] infoPtr : valid pointer to the listview structure
6742 * [I] nColumn : column index
6743 * [I] lpColumn : column attributes
6744 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6746 * RETURN:
6747 * SUCCESS : TRUE
6748 * FAILURE : FALSE
6750 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6751 const LVCOLUMNW *lpColumn, BOOL isW)
6753 HDITEMW hdi, hdiget;
6754 BOOL bResult;
6756 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6758 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6760 ZeroMemory(&hdi, sizeof(HDITEMW));
6761 if (lpColumn->mask & LVCF_FMT)
6763 hdi.mask |= HDI_FORMAT;
6764 hdiget.mask = HDI_FORMAT;
6765 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6766 hdi.fmt = hdiget.fmt & HDF_STRING;
6768 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6770 /* set header item attributes */
6771 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6772 if (!bResult) return FALSE;
6774 if (lpColumn->mask & LVCF_FMT)
6776 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6777 int oldFmt = lpColumnInfo->fmt;
6779 lpColumnInfo->fmt = lpColumn->fmt;
6780 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6782 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6783 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6787 return TRUE;
6790 /***
6791 * DESCRIPTION:
6792 * Sets the column order array
6794 * PARAMETERS:
6795 * [I] infoPtr : valid pointer to the listview structure
6796 * [I] iCount : number of elements in column order array
6797 * [I] lpiArray : pointer to column order array
6799 * RETURN:
6800 * SUCCESS : TRUE
6801 * FAILURE : FALSE
6803 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6805 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6807 if (!lpiArray)
6808 return FALSE;
6810 return TRUE;
6814 /***
6815 * DESCRIPTION:
6816 * Sets the width of a column
6818 * PARAMETERS:
6819 * [I] infoPtr : valid pointer to the listview structure
6820 * [I] nColumn : column index
6821 * [I] cx : column width
6823 * RETURN:
6824 * SUCCESS : TRUE
6825 * FAILURE : FALSE
6827 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6829 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6830 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6831 INT max_cx = 0;
6832 HDITEMW hdi;
6834 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6836 /* set column width only if in report or list mode */
6837 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6839 /* take care of invalid cx values */
6840 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6841 else if (uView == LVS_LIST && cx < 1) return FALSE;
6843 /* resize all columns if in LVS_LIST mode */
6844 if(uView == LVS_LIST)
6846 infoPtr->nItemWidth = cx;
6847 LISTVIEW_InvalidateList(infoPtr);
6848 return TRUE;
6851 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6853 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6855 INT nLabelWidth;
6856 LVITEMW lvItem;
6858 lvItem.mask = LVIF_TEXT;
6859 lvItem.iItem = 0;
6860 lvItem.iSubItem = nColumn;
6861 lvItem.pszText = szDispText;
6862 lvItem.cchTextMax = DISP_TEXT_SIZE;
6863 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6865 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6866 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6867 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6869 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6870 max_cx += infoPtr->iconSize.cx;
6871 max_cx += TRAILING_LABEL_PADDING;
6874 /* autosize based on listview items width */
6875 if(cx == LVSCW_AUTOSIZE)
6876 cx = max_cx;
6877 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6879 /* if iCol is the last column make it fill the remainder of the controls width */
6880 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6882 RECT rcHeader;
6883 POINT Origin;
6885 LISTVIEW_GetOrigin(infoPtr, &Origin);
6886 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6888 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6890 else
6892 /* Despite what the MS docs say, if this is not the last
6893 column, then MS resizes the column to the width of the
6894 largest text string in the column, including headers
6895 and items. This is different from LVSCW_AUTOSIZE in that
6896 LVSCW_AUTOSIZE ignores the header string length. */
6897 cx = 0;
6899 /* retrieve header text */
6900 hdi.mask = HDI_TEXT;
6901 hdi.cchTextMax = DISP_TEXT_SIZE;
6902 hdi.pszText = szDispText;
6903 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
6905 HDC hdc = GetDC(infoPtr->hwndSelf);
6906 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6907 SIZE size;
6909 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6910 cx = size.cx + TRAILING_HEADER_PADDING;
6911 /* FIXME: Take into account the header image, if one is present */
6912 SelectObject(hdc, old_font);
6913 ReleaseDC(infoPtr->hwndSelf, hdc);
6915 cx = max (cx, max_cx);
6919 if (cx < 0) return FALSE;
6921 /* call header to update the column change */
6922 hdi.mask = HDI_WIDTH;
6923 hdi.cxy = cx;
6924 TRACE("hdi.cxy=%d\n", hdi.cxy);
6925 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
6928 /***
6929 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6932 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
6934 HDC hdc_wnd, hdc;
6935 HBITMAP hbm_im, hbm_mask, hbm_orig;
6936 RECT rc;
6937 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6938 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6939 HIMAGELIST himl;
6941 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6942 ILC_COLOR | ILC_MASK, 2, 2);
6943 hdc_wnd = GetDC(infoPtr->hwndSelf);
6944 hdc = CreateCompatibleDC(hdc_wnd);
6945 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6946 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6947 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6949 rc.left = rc.top = 0;
6950 rc.right = GetSystemMetrics(SM_CXSMICON);
6951 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6953 hbm_orig = SelectObject(hdc, hbm_mask);
6954 FillRect(hdc, &rc, hbr_white);
6955 InflateRect(&rc, -3, -3);
6956 FillRect(hdc, &rc, hbr_black);
6958 SelectObject(hdc, hbm_im);
6959 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6960 SelectObject(hdc, hbm_orig);
6961 ImageList_Add(himl, hbm_im, hbm_mask);
6963 SelectObject(hdc, hbm_im);
6964 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6965 SelectObject(hdc, hbm_orig);
6966 ImageList_Add(himl, hbm_im, hbm_mask);
6968 DeleteObject(hbm_mask);
6969 DeleteObject(hbm_im);
6970 DeleteDC(hdc);
6972 return himl;
6975 /***
6976 * DESCRIPTION:
6977 * Sets the extended listview style.
6979 * PARAMETERS:
6980 * [I] infoPtr : valid pointer to the listview structure
6981 * [I] dwMask : mask
6982 * [I] dwStyle : style
6984 * RETURN:
6985 * SUCCESS : previous style
6986 * FAILURE : 0
6988 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6990 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6992 /* set new style */
6993 if (dwMask)
6994 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6995 else
6996 infoPtr->dwLvExStyle = dwStyle;
6998 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
7000 HIMAGELIST himl = 0;
7001 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7003 LVITEMW item;
7004 item.mask = LVIF_STATE;
7005 item.stateMask = LVIS_STATEIMAGEMASK;
7006 item.state = INDEXTOSTATEIMAGEMASK(1);
7007 LISTVIEW_SetItemState(infoPtr, -1, &item);
7009 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7011 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7014 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_HEADERDRAGDROP)
7016 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7017 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7018 dwStyle |= HDS_DRAGDROP;
7019 else
7020 dwStyle &= ~HDS_DRAGDROP;
7021 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7024 return dwOldStyle;
7027 /***
7028 * DESCRIPTION:
7029 * Sets the new hot cursor used during hot tracking and hover selection.
7031 * PARAMETER(S):
7032 * [I] infoPtr : valid pointer to the listview structure
7033 * [I] hCursor : the new hot cursor handle
7035 * RETURN:
7036 * Returns the previous hot cursor
7038 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7040 HCURSOR oldCursor = infoPtr->hHotCursor;
7042 infoPtr->hHotCursor = hCursor;
7044 return oldCursor;
7048 /***
7049 * DESCRIPTION:
7050 * Sets the hot item index.
7052 * PARAMETERS:
7053 * [I] infoPtr : valid pointer to the listview structure
7054 * [I] iIndex : index
7056 * RETURN:
7057 * SUCCESS : previous hot item index
7058 * FAILURE : -1 (no hot item)
7060 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7062 INT iOldIndex = infoPtr->nHotItem;
7064 infoPtr->nHotItem = iIndex;
7066 return iOldIndex;
7070 /***
7071 * DESCRIPTION:
7072 * Sets the amount of time the cursor must hover over an item before it is selected.
7074 * PARAMETER(S):
7075 * [I] infoPtr : valid pointer to the listview structure
7076 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7078 * RETURN:
7079 * Returns the previous hover time
7081 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7083 DWORD oldHoverTime = infoPtr->dwHoverTime;
7085 infoPtr->dwHoverTime = dwHoverTime;
7087 return oldHoverTime;
7090 /***
7091 * DESCRIPTION:
7092 * Sets spacing for icons of LVS_ICON style.
7094 * PARAMETER(S):
7095 * [I] infoPtr : valid pointer to the listview structure
7096 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7097 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7099 * RETURN:
7100 * MAKELONG(oldcx, oldcy)
7102 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7104 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7105 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7107 TRACE("requested=(%d,%d)\n", cx, cy);
7109 /* this is supported only for LVS_ICON style */
7110 if (uView != LVS_ICON) return oldspacing;
7112 /* set to defaults, if instructed to */
7113 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7114 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7116 /* if 0 then compute width
7117 * FIXME: Should scan each item and determine max width of
7118 * icon or label, then make that the width */
7119 if (cx == 0)
7120 cx = infoPtr->iconSpacing.cx;
7122 /* if 0 then compute height */
7123 if (cy == 0)
7124 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7125 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7128 infoPtr->iconSpacing.cx = cx;
7129 infoPtr->iconSpacing.cy = cy;
7131 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7132 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7133 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7134 infoPtr->ntmHeight);
7136 /* these depend on the iconSpacing */
7137 LISTVIEW_UpdateItemSize(infoPtr);
7139 return oldspacing;
7142 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7144 INT cx, cy;
7146 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7148 size->cx = cx;
7149 size->cy = cy;
7151 else
7153 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7154 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7158 /***
7159 * DESCRIPTION:
7160 * Sets image lists.
7162 * PARAMETER(S):
7163 * [I] infoPtr : valid pointer to the listview structure
7164 * [I] nType : image list type
7165 * [I] himl : image list handle
7167 * RETURN:
7168 * SUCCESS : old image list
7169 * FAILURE : NULL
7171 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7173 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7174 INT oldHeight = infoPtr->nItemHeight;
7175 HIMAGELIST himlOld = 0;
7177 TRACE("(nType=%d, himl=%p\n", nType, himl);
7179 switch (nType)
7181 case LVSIL_NORMAL:
7182 himlOld = infoPtr->himlNormal;
7183 infoPtr->himlNormal = himl;
7184 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7185 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7186 break;
7188 case LVSIL_SMALL:
7189 himlOld = infoPtr->himlSmall;
7190 infoPtr->himlSmall = himl;
7191 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7192 break;
7194 case LVSIL_STATE:
7195 himlOld = infoPtr->himlState;
7196 infoPtr->himlState = himl;
7197 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7198 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7199 break;
7201 default:
7202 ERR("Unknown icon type=%d\n", nType);
7203 return NULL;
7206 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7207 if (infoPtr->nItemHeight != oldHeight)
7208 LISTVIEW_UpdateScroll(infoPtr);
7210 return himlOld;
7213 /***
7214 * DESCRIPTION:
7215 * Preallocates memory (does *not* set the actual count of items !)
7217 * PARAMETER(S):
7218 * [I] infoPtr : valid pointer to the listview structure
7219 * [I] nItems : item count (projected number of items to allocate)
7220 * [I] dwFlags : update flags
7222 * RETURN:
7223 * SUCCESS : TRUE
7224 * FAILURE : FALSE
7226 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7228 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7230 if (infoPtr->dwStyle & LVS_OWNERDATA)
7232 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7233 INT nOldCount = infoPtr->nItemCount;
7235 if (nItems < nOldCount)
7237 RANGE range = { nItems, nOldCount };
7238 ranges_del(infoPtr->selectionRanges, range);
7239 if (infoPtr->nFocusedItem >= nItems)
7241 infoPtr->nFocusedItem = -1;
7242 SetRectEmpty(&infoPtr->rcFocus);
7246 infoPtr->nItemCount = nItems;
7247 LISTVIEW_UpdateScroll(infoPtr);
7249 /* the flags are valid only in ownerdata report and list modes */
7250 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7252 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7253 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7255 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7256 LISTVIEW_InvalidateList(infoPtr);
7257 else
7259 INT nFrom, nTo;
7260 POINT Origin;
7261 RECT rcErase;
7263 LISTVIEW_GetOrigin(infoPtr, &Origin);
7264 nFrom = min(nOldCount, nItems);
7265 nTo = max(nOldCount, nItems);
7267 if (uView == LVS_REPORT)
7269 rcErase.left = 0;
7270 rcErase.top = nFrom * infoPtr->nItemHeight;
7271 rcErase.right = infoPtr->nItemWidth;
7272 rcErase.bottom = nTo * infoPtr->nItemHeight;
7273 OffsetRect(&rcErase, Origin.x, Origin.y);
7274 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7275 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7277 else /* LVS_LIST */
7279 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7281 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7282 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7283 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7284 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7285 OffsetRect(&rcErase, Origin.x, Origin.y);
7286 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7287 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7289 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7290 rcErase.top = 0;
7291 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7292 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7293 OffsetRect(&rcErase, Origin.x, Origin.y);
7294 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7295 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7299 else
7301 /* According to MSDN for non-LVS_OWNERDATA this is just
7302 * a performance issue. The control allocates its internal
7303 * data structures for the number of items specified. It
7304 * cuts down on the number of memory allocations. Therefore
7305 * we will just issue a WARN here
7307 WARN("for non-ownerdata performance option not implemented.\n");
7310 return TRUE;
7313 /***
7314 * DESCRIPTION:
7315 * Sets the position of an item.
7317 * PARAMETER(S):
7318 * [I] infoPtr : valid pointer to the listview structure
7319 * [I] nItem : item index
7320 * [I] pt : coordinate
7322 * RETURN:
7323 * SUCCESS : TRUE
7324 * FAILURE : FALSE
7326 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7328 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7329 POINT Origin;
7331 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7333 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7334 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7336 LISTVIEW_GetOrigin(infoPtr, &Origin);
7338 /* This point value seems to be an undocumented feature.
7339 * The best guess is that it means either at the origin,
7340 * or at true beginning of the list. I will assume the origin. */
7341 if ((pt.x == -1) && (pt.y == -1))
7342 pt = Origin;
7344 if (uView == LVS_ICON)
7346 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7347 pt.y -= ICON_TOP_PADDING;
7349 pt.x -= Origin.x;
7350 pt.y -= Origin.y;
7352 infoPtr->bAutoarrange = FALSE;
7354 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7357 /***
7358 * DESCRIPTION:
7359 * Sets the state of one or many items.
7361 * PARAMETER(S):
7362 * [I] infoPtr : valid pointer to the listview structure
7363 * [I] nItem : item index
7364 * [I] lpLVItem : item or subitem info
7366 * RETURN:
7367 * SUCCESS : TRUE
7368 * FAILURE : FALSE
7370 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7372 BOOL bResult = TRUE;
7373 LVITEMW lvItem;
7375 lvItem.iItem = nItem;
7376 lvItem.iSubItem = 0;
7377 lvItem.mask = LVIF_STATE;
7378 lvItem.state = lpLVItem->state;
7379 lvItem.stateMask = lpLVItem->stateMask;
7380 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7382 if (nItem == -1)
7384 /* apply to all items */
7385 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7386 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7388 else
7389 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7392 * Update selection mark
7394 * Investigation on windows 2k showed that selection mark was updated
7395 * whenever a new selection was made, but if the selected item was
7396 * unselected it was not updated.
7398 * we are probably still not 100% accurate, but this at least sets the
7399 * proper selection mark when it is needed
7402 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7403 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7405 int i;
7406 infoPtr->nSelectionMark = -1;
7407 for (i = 0; i < infoPtr->nItemCount; i++)
7409 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7411 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7413 infoPtr->nSelectionMark = i;
7414 break;
7417 else if (ranges_contain(infoPtr->selectionRanges, i))
7419 infoPtr->nSelectionMark = i;
7420 break;
7425 return bResult;
7428 /***
7429 * DESCRIPTION:
7430 * Sets the text of an item or subitem.
7432 * PARAMETER(S):
7433 * [I] hwnd : window handle
7434 * [I] nItem : item index
7435 * [I] lpLVItem : item or subitem info
7436 * [I] isW : TRUE if input is Unicode
7438 * RETURN:
7439 * SUCCESS : TRUE
7440 * FAILURE : FALSE
7442 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7444 LVITEMW lvItem;
7446 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7448 lvItem.iItem = nItem;
7449 lvItem.iSubItem = lpLVItem->iSubItem;
7450 lvItem.mask = LVIF_TEXT;
7451 lvItem.pszText = lpLVItem->pszText;
7452 lvItem.cchTextMax = lpLVItem->cchTextMax;
7454 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7456 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7459 /***
7460 * DESCRIPTION:
7461 * Set item index that marks the start of a multiple selection.
7463 * PARAMETER(S):
7464 * [I] infoPtr : valid pointer to the listview structure
7465 * [I] nIndex : index
7467 * RETURN:
7468 * Index number or -1 if there is no selection mark.
7470 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7472 INT nOldIndex = infoPtr->nSelectionMark;
7474 TRACE("(nIndex=%d)\n", nIndex);
7476 infoPtr->nSelectionMark = nIndex;
7478 return nOldIndex;
7481 /***
7482 * DESCRIPTION:
7483 * Sets the text background color.
7485 * PARAMETER(S):
7486 * [I] infoPtr : valid pointer to the listview structure
7487 * [I] clrTextBk : text background color
7489 * RETURN:
7490 * SUCCESS : TRUE
7491 * FAILURE : FALSE
7493 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7495 TRACE("(clrTextBk=%x)\n", clrTextBk);
7497 if (infoPtr->clrTextBk != clrTextBk)
7499 infoPtr->clrTextBk = clrTextBk;
7500 LISTVIEW_InvalidateList(infoPtr);
7503 return TRUE;
7506 /***
7507 * DESCRIPTION:
7508 * Sets the text foreground color.
7510 * PARAMETER(S):
7511 * [I] infoPtr : valid pointer to the listview structure
7512 * [I] clrText : text color
7514 * RETURN:
7515 * SUCCESS : TRUE
7516 * FAILURE : FALSE
7518 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7520 TRACE("(clrText=%x)\n", clrText);
7522 if (infoPtr->clrText != clrText)
7524 infoPtr->clrText = clrText;
7525 LISTVIEW_InvalidateList(infoPtr);
7528 return TRUE;
7531 /***
7532 * DESCRIPTION:
7533 * Determines which listview item is located at the specified position.
7535 * PARAMETER(S):
7536 * [I] infoPtr : valid pointer to the listview structure
7537 * [I] hwndNewToolTip : handle to new ToolTip
7539 * RETURN:
7540 * old tool tip
7542 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7544 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7545 infoPtr->hwndToolTip = hwndNewToolTip;
7546 return hwndOldToolTip;
7550 * DESCRIPTION:
7551 * sets the Unicode character format flag for the control
7552 * PARAMETER(S):
7553 * [I] infoPtr :valid pointer to the listview structure
7554 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7556 * RETURN:
7557 * Old Unicode Format
7559 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7561 BOOL rc = infoPtr->notifyFormat;
7562 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7563 return rc;
7566 /* LISTVIEW_SetWorkAreas */
7568 /***
7569 * DESCRIPTION:
7570 * Callback internally used by LISTVIEW_SortItems()
7572 * PARAMETER(S):
7573 * [I] first : pointer to first ITEM_INFO to compare
7574 * [I] second : pointer to second ITEM_INFO to compare
7575 * [I] lParam : HWND of control
7577 * RETURN:
7578 * if first comes before second : negative
7579 * if first comes after second : positive
7580 * if first and second are equivalent : zero
7582 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7584 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7585 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7586 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7588 /* Forward the call to the client defined callback */
7589 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7592 /***
7593 * DESCRIPTION:
7594 * Sorts the listview items.
7596 * PARAMETER(S):
7597 * [I] infoPtr : valid pointer to the listview structure
7598 * [I] pfnCompare : application-defined value
7599 * [I] lParamSort : pointer to comparision callback
7601 * RETURN:
7602 * SUCCESS : TRUE
7603 * FAILURE : FALSE
7605 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7607 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7608 HDPA hdpaSubItems;
7609 ITEM_INFO *lpItem;
7610 LPVOID selectionMarkItem;
7611 LVITEMW item;
7612 int i;
7614 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7616 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7618 if (!pfnCompare) return FALSE;
7619 if (!infoPtr->hdpaItems) return FALSE;
7621 /* if there are 0 or 1 items, there is no need to sort */
7622 if (infoPtr->nItemCount < 2) return TRUE;
7624 if (infoPtr->nFocusedItem >= 0)
7626 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7627 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7628 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7630 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7631 /* clear the lpItem->state for non-selected ones */
7632 /* remove the selection ranges */
7634 infoPtr->pfnCompare = pfnCompare;
7635 infoPtr->lParamSort = lParamSort;
7636 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7638 /* Adjust selections and indices so that they are the way they should
7639 * be after the sort (otherwise, the list items move around, but
7640 * whatever is at the item's previous original position will be
7641 * selected instead)
7643 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7644 for (i=0; i < infoPtr->nItemCount; i++)
7646 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7647 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7649 if (lpItem->state & LVIS_SELECTED)
7651 item.state = LVIS_SELECTED;
7652 item.stateMask = LVIS_SELECTED;
7653 LISTVIEW_SetItemState(infoPtr, i, &item);
7655 if (lpItem->state & LVIS_FOCUSED)
7657 infoPtr->nFocusedItem = i;
7658 lpItem->state &= ~LVIS_FOCUSED;
7661 if (selectionMarkItem != NULL)
7662 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7663 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7665 /* refresh the display */
7666 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7667 LISTVIEW_InvalidateList(infoPtr);
7669 return TRUE;
7672 /***
7673 * DESCRIPTION:
7674 * Update theme handle after a theme change.
7676 * PARAMETER(S):
7677 * [I] infoPtr : valid pointer to the listview structure
7679 * RETURN:
7680 * SUCCESS : 0
7681 * FAILURE : something else
7683 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7685 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7686 CloseThemeData(theme);
7687 OpenThemeData(infoPtr->hwndSelf, themeClass);
7688 return 0;
7691 /***
7692 * DESCRIPTION:
7693 * Updates an items or rearranges the listview control.
7695 * PARAMETER(S):
7696 * [I] infoPtr : valid pointer to the listview structure
7697 * [I] nItem : item index
7699 * RETURN:
7700 * SUCCESS : TRUE
7701 * FAILURE : FALSE
7703 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7705 TRACE("(nItem=%d)\n", nItem);
7707 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7709 /* rearrange with default alignment style */
7710 if (is_autoarrange(infoPtr))
7711 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7712 else
7713 LISTVIEW_InvalidateItem(infoPtr, nItem);
7715 return TRUE;
7718 /***
7719 * DESCRIPTION:
7720 * Draw the track line at the place defined in the infoPtr structure.
7721 * The line is drawn with a XOR pen so drawing the line for the second time
7722 * in the same place erases the line.
7724 * PARAMETER(S):
7725 * [I] infoPtr : valid pointer to the listview structure
7727 * RETURN:
7728 * SUCCESS : TRUE
7729 * FAILURE : FALSE
7731 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7733 HPEN hOldPen;
7734 HDC hdc;
7735 INT oldROP;
7737 if (infoPtr->xTrackLine == -1)
7738 return FALSE;
7740 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7741 return FALSE;
7742 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7743 oldROP = SetROP2(hdc, R2_XORPEN);
7744 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7745 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7746 SetROP2(hdc, oldROP);
7747 SelectObject(hdc, hOldPen);
7748 ReleaseDC(infoPtr->hwndSelf, hdc);
7749 return TRUE;
7752 /***
7753 * DESCRIPTION:
7754 * Called when an edit control should be displayed. This function is called after
7755 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7757 * PARAMETER(S):
7758 * [I] hwnd : Handle to the listview
7759 * [I] uMsg : WM_TIMER (ignored)
7760 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7761 * [I] dwTimer : The elapsed time (ignored)
7763 * RETURN:
7764 * None.
7766 static CALLBACK VOID LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7768 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7769 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7771 KillTimer(hwnd, idEvent);
7772 editItem->fEnabled = FALSE;
7773 /* check if the item is still selected */
7774 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7775 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7778 /***
7779 * DESCRIPTION:
7780 * Creates the listview control - the WM_NCCREATE phase.
7782 * PARAMETER(S):
7783 * [I] hwnd : window handle
7784 * [I] lpcs : the create parameters
7786 * RETURN:
7787 * Success: TRUE
7788 * Failure: FALSE
7790 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7792 LISTVIEW_INFO *infoPtr;
7793 LOGFONTW logFont;
7795 TRACE("(lpcs=%p)\n", lpcs);
7797 /* initialize info pointer */
7798 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7799 if (!infoPtr) return FALSE;
7801 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7803 infoPtr->hwndSelf = hwnd;
7804 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7805 /* determine the type of structures to use */
7806 infoPtr->hwndNotify = lpcs->hwndParent;
7807 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7809 /* initialize color information */
7810 infoPtr->clrBk = CLR_NONE;
7811 infoPtr->clrText = CLR_DEFAULT;
7812 infoPtr->clrTextBk = CLR_DEFAULT;
7813 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7815 /* set default values */
7816 infoPtr->nFocusedItem = -1;
7817 infoPtr->nSelectionMark = -1;
7818 infoPtr->nHotItem = -1;
7819 infoPtr->bRedraw = TRUE;
7820 infoPtr->bNoItemMetrics = TRUE;
7821 infoPtr->bDoChangeNotify = TRUE;
7822 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7823 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7824 infoPtr->nEditLabelItem = -1;
7825 infoPtr->dwHoverTime = -1; /* default system hover time */
7826 infoPtr->nMeasureItemHeight = 0;
7827 infoPtr->xTrackLine = -1; /* no track line */
7828 infoPtr->itemEdit.fEnabled = FALSE;
7830 /* get default font (icon title) */
7831 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7832 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7833 infoPtr->hFont = infoPtr->hDefaultFont;
7834 LISTVIEW_SaveTextMetrics(infoPtr);
7836 /* allocate memory for the data structure */
7837 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7838 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7839 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7840 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7841 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7842 return TRUE;
7844 fail:
7845 DestroyWindow(infoPtr->hwndHeader);
7846 ranges_destroy(infoPtr->selectionRanges);
7847 DPA_Destroy(infoPtr->hdpaItems);
7848 DPA_Destroy(infoPtr->hdpaPosX);
7849 DPA_Destroy(infoPtr->hdpaPosY);
7850 DPA_Destroy(infoPtr->hdpaColumns);
7851 Free(infoPtr);
7852 return FALSE;
7855 /***
7856 * DESCRIPTION:
7857 * Creates the listview control - the WM_CREATE phase. Most of the data is
7858 * already set up in LISTVIEW_NCCreate
7860 * PARAMETER(S):
7861 * [I] hwnd : window handle
7862 * [I] lpcs : the create parameters
7864 * RETURN:
7865 * Success: 0
7866 * Failure: -1
7868 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7870 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7871 UINT uView = lpcs->style & LVS_TYPEMASK;
7873 TRACE("(lpcs=%p)\n", lpcs);
7875 infoPtr->dwStyle = lpcs->style;
7876 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7877 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7879 /* create header */
7880 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7881 WS_CHILD | HDS_HORZ | HDS_FULLDRAG | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7882 0, 0, 0, 0, hwnd, NULL,
7883 lpcs->hInstance, NULL);
7884 if (!infoPtr->hwndHeader) return -1;
7886 /* set header unicode format */
7887 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7889 /* set header font */
7890 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7892 /* init item size to avoid division by 0 */
7893 LISTVIEW_UpdateItemSize (infoPtr);
7895 if (uView == LVS_REPORT)
7897 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7899 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7901 else
7903 /* set HDS_HIDDEN flag to hide the header bar */
7904 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7905 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7909 OpenThemeData(hwnd, themeClass);
7911 /* initialize the icon sizes */
7912 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7913 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7914 return 0;
7917 /***
7918 * DESCRIPTION:
7919 * Destroys the listview control.
7921 * PARAMETER(S):
7922 * [I] infoPtr : valid pointer to the listview structure
7924 * RETURN:
7925 * Success: 0
7926 * Failure: -1
7928 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
7930 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7931 CloseThemeData(theme);
7932 return 0;
7935 /***
7936 * DESCRIPTION:
7937 * Enables the listview control.
7939 * PARAMETER(S):
7940 * [I] infoPtr : valid pointer to the listview structure
7941 * [I] bEnable : specifies whether to enable or disable the window
7943 * RETURN:
7944 * SUCCESS : TRUE
7945 * FAILURE : FALSE
7947 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
7949 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
7950 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
7951 return TRUE;
7954 /***
7955 * DESCRIPTION:
7956 * Erases the background of the listview control.
7958 * PARAMETER(S):
7959 * [I] infoPtr : valid pointer to the listview structure
7960 * [I] hdc : device context handle
7962 * RETURN:
7963 * SUCCESS : TRUE
7964 * FAILURE : FALSE
7966 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
7968 RECT rc;
7970 TRACE("(hdc=%p)\n", hdc);
7972 if (!GetClipBox(hdc, &rc)) return FALSE;
7974 /* for double buffered controls we need to do this during refresh */
7975 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
7977 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7981 /***
7982 * DESCRIPTION:
7983 * Helper function for LISTVIEW_[HV]Scroll *only*.
7984 * Performs vertical/horizontal scrolling by a give amount.
7986 * PARAMETER(S):
7987 * [I] infoPtr : valid pointer to the listview structure
7988 * [I] dx : amount of horizontal scroll
7989 * [I] dy : amount of vertical scroll
7991 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7993 /* now we can scroll the list */
7994 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7995 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7996 /* if we have focus, adjust rect */
7997 OffsetRect(&infoPtr->rcFocus, dx, dy);
7998 UpdateWindow(infoPtr->hwndSelf);
8001 /***
8002 * DESCRIPTION:
8003 * Performs vertical scrolling.
8005 * PARAMETER(S):
8006 * [I] infoPtr : valid pointer to the listview structure
8007 * [I] nScrollCode : scroll code
8008 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8009 * [I] hScrollWnd : scrollbar control window handle
8011 * RETURN:
8012 * Zero
8014 * NOTES:
8015 * SB_LINEUP/SB_LINEDOWN:
8016 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8017 * for LVS_REPORT is 1 line
8018 * for LVS_LIST cannot occur
8021 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8022 INT nScrollDiff, HWND hScrollWnd)
8024 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8025 INT nOldScrollPos, nNewScrollPos;
8026 SCROLLINFO scrollInfo;
8027 BOOL is_an_icon;
8029 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8030 debugscrollcode(nScrollCode), nScrollDiff);
8032 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8034 scrollInfo.cbSize = sizeof(SCROLLINFO);
8035 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8037 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8039 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8041 nOldScrollPos = scrollInfo.nPos;
8042 switch (nScrollCode)
8044 case SB_INTERNAL:
8045 break;
8047 case SB_LINEUP:
8048 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8049 break;
8051 case SB_LINEDOWN:
8052 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8053 break;
8055 case SB_PAGEUP:
8056 nScrollDiff = -scrollInfo.nPage;
8057 break;
8059 case SB_PAGEDOWN:
8060 nScrollDiff = scrollInfo.nPage;
8061 break;
8063 case SB_THUMBPOSITION:
8064 case SB_THUMBTRACK:
8065 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8066 break;
8068 default:
8069 nScrollDiff = 0;
8072 /* quit right away if pos isn't changing */
8073 if (nScrollDiff == 0) return 0;
8075 /* calculate new position, and handle overflows */
8076 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8077 if (nScrollDiff > 0) {
8078 if (nNewScrollPos < nOldScrollPos ||
8079 nNewScrollPos > scrollInfo.nMax)
8080 nNewScrollPos = scrollInfo.nMax;
8081 } else {
8082 if (nNewScrollPos > nOldScrollPos ||
8083 nNewScrollPos < scrollInfo.nMin)
8084 nNewScrollPos = scrollInfo.nMin;
8087 /* set the new position, and reread in case it changed */
8088 scrollInfo.fMask = SIF_POS;
8089 scrollInfo.nPos = nNewScrollPos;
8090 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8092 /* carry on only if it really changed */
8093 if (nNewScrollPos == nOldScrollPos) return 0;
8095 /* now adjust to client coordinates */
8096 nScrollDiff = nOldScrollPos - nNewScrollPos;
8097 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8099 /* and scroll the window */
8100 scroll_list(infoPtr, 0, nScrollDiff);
8102 return 0;
8105 /***
8106 * DESCRIPTION:
8107 * Performs horizontal scrolling.
8109 * PARAMETER(S):
8110 * [I] infoPtr : valid pointer to the listview structure
8111 * [I] nScrollCode : scroll code
8112 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8113 * [I] hScrollWnd : scrollbar control window handle
8115 * RETURN:
8116 * Zero
8118 * NOTES:
8119 * SB_LINELEFT/SB_LINERIGHT:
8120 * for LVS_ICON, LVS_SMALLICON 1 pixel
8121 * for LVS_REPORT is 1 pixel
8122 * for LVS_LIST is 1 column --> which is a 1 because the
8123 * scroll is based on columns not pixels
8126 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8127 INT nScrollDiff, HWND hScrollWnd)
8129 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8130 INT nOldScrollPos, nNewScrollPos;
8131 SCROLLINFO scrollInfo;
8133 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8134 debugscrollcode(nScrollCode), nScrollDiff);
8136 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8138 scrollInfo.cbSize = sizeof(SCROLLINFO);
8139 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8141 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8143 nOldScrollPos = scrollInfo.nPos;
8145 switch (nScrollCode)
8147 case SB_INTERNAL:
8148 break;
8150 case SB_LINELEFT:
8151 nScrollDiff = -1;
8152 break;
8154 case SB_LINERIGHT:
8155 nScrollDiff = 1;
8156 break;
8158 case SB_PAGELEFT:
8159 nScrollDiff = -scrollInfo.nPage;
8160 break;
8162 case SB_PAGERIGHT:
8163 nScrollDiff = scrollInfo.nPage;
8164 break;
8166 case SB_THUMBPOSITION:
8167 case SB_THUMBTRACK:
8168 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8169 break;
8171 default:
8172 nScrollDiff = 0;
8175 /* quit right away if pos isn't changing */
8176 if (nScrollDiff == 0) return 0;
8178 /* calculate new position, and handle overflows */
8179 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8180 if (nScrollDiff > 0) {
8181 if (nNewScrollPos < nOldScrollPos ||
8182 nNewScrollPos > scrollInfo.nMax)
8183 nNewScrollPos = scrollInfo.nMax;
8184 } else {
8185 if (nNewScrollPos > nOldScrollPos ||
8186 nNewScrollPos < scrollInfo.nMin)
8187 nNewScrollPos = scrollInfo.nMin;
8190 /* set the new position, and reread in case it changed */
8191 scrollInfo.fMask = SIF_POS;
8192 scrollInfo.nPos = nNewScrollPos;
8193 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8195 /* carry on only if it really changed */
8196 if (nNewScrollPos == nOldScrollPos) return 0;
8198 if(uView == LVS_REPORT)
8199 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8201 /* now adjust to client coordinates */
8202 nScrollDiff = nOldScrollPos - nNewScrollPos;
8203 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8205 /* and scroll the window */
8206 scroll_list(infoPtr, nScrollDiff, 0);
8208 return 0;
8211 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8213 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8214 INT gcWheelDelta = 0;
8215 INT pulScrollLines = 3;
8216 SCROLLINFO scrollInfo;
8218 TRACE("(wheelDelta=%d)\n", wheelDelta);
8220 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8221 gcWheelDelta -= wheelDelta;
8223 scrollInfo.cbSize = sizeof(SCROLLINFO);
8224 scrollInfo.fMask = SIF_POS;
8226 switch(uView)
8228 case LVS_ICON:
8229 case LVS_SMALLICON:
8231 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8232 * should be fixed in the future.
8234 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8235 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8236 break;
8238 case LVS_REPORT:
8239 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8241 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8242 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8243 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8245 break;
8247 case LVS_LIST:
8248 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8249 break;
8251 return 0;
8254 /***
8255 * DESCRIPTION:
8256 * ???
8258 * PARAMETER(S):
8259 * [I] infoPtr : valid pointer to the listview structure
8260 * [I] nVirtualKey : virtual key
8261 * [I] lKeyData : key data
8263 * RETURN:
8264 * Zero
8266 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8268 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8269 HWND hwndSelf = infoPtr->hwndSelf;
8270 INT nItem = -1;
8271 NMLVKEYDOWN nmKeyDown;
8273 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8275 /* send LVN_KEYDOWN notification */
8276 nmKeyDown.wVKey = nVirtualKey;
8277 nmKeyDown.flags = 0;
8278 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8279 if (!IsWindow(hwndSelf))
8280 return 0;
8282 switch (nVirtualKey)
8284 case VK_SPACE:
8285 nItem = infoPtr->nFocusedItem;
8286 break;
8288 case VK_RETURN:
8289 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8291 if (!notify(infoPtr, NM_RETURN)) return 0;
8292 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8294 break;
8296 case VK_HOME:
8297 if (infoPtr->nItemCount > 0)
8298 nItem = 0;
8299 break;
8301 case VK_END:
8302 if (infoPtr->nItemCount > 0)
8303 nItem = infoPtr->nItemCount - 1;
8304 break;
8306 case VK_LEFT:
8307 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8308 break;
8310 case VK_UP:
8311 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8312 break;
8314 case VK_RIGHT:
8315 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8316 break;
8318 case VK_DOWN:
8319 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8320 break;
8322 case VK_PRIOR:
8323 if (uView == LVS_REPORT)
8325 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8326 if (infoPtr->nFocusedItem == topidx)
8327 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8328 else
8329 nItem = topidx;
8331 else
8332 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8333 * LISTVIEW_GetCountPerRow(infoPtr);
8334 if(nItem < 0) nItem = 0;
8335 break;
8337 case VK_NEXT:
8338 if (uView == LVS_REPORT)
8340 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8341 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8342 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8343 nItem = infoPtr->nFocusedItem + cnt - 1;
8344 else
8345 nItem = topidx + cnt - 1;
8347 else
8348 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8349 * LISTVIEW_GetCountPerRow(infoPtr);
8350 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8351 break;
8354 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8355 LISTVIEW_KeySelection(infoPtr, nItem);
8357 return 0;
8360 /***
8361 * DESCRIPTION:
8362 * Kills the focus.
8364 * PARAMETER(S):
8365 * [I] infoPtr : valid pointer to the listview structure
8367 * RETURN:
8368 * Zero
8370 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8372 TRACE("()\n");
8374 /* if we did not have the focus, there's nothing to do */
8375 if (!infoPtr->bFocus) return 0;
8377 /* send NM_KILLFOCUS notification */
8378 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8380 /* if we have a focus rectagle, get rid of it */
8381 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8383 /* set window focus flag */
8384 infoPtr->bFocus = FALSE;
8386 /* invalidate the selected items before reseting focus flag */
8387 LISTVIEW_InvalidateSelectedItems(infoPtr);
8389 return 0;
8392 /***
8393 * DESCRIPTION:
8394 * Processes double click messages (left mouse button).
8396 * PARAMETER(S):
8397 * [I] infoPtr : valid pointer to the listview structure
8398 * [I] wKey : key flag
8399 * [I] x,y : mouse coordinate
8401 * RETURN:
8402 * Zero
8404 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8406 LVHITTESTINFO htInfo;
8408 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8410 /* Cancel the item edition if any */
8411 if (infoPtr->itemEdit.fEnabled)
8413 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8414 infoPtr->itemEdit.fEnabled = FALSE;
8417 /* send NM_RELEASEDCAPTURE notification */
8418 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8420 htInfo.pt.x = x;
8421 htInfo.pt.y = y;
8423 /* send NM_DBLCLK notification */
8424 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8425 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8427 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8428 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8430 return 0;
8433 /***
8434 * DESCRIPTION:
8435 * Processes mouse down messages (left mouse button).
8437 * PARAMETERS:
8438 * infoPtr [I ] valid pointer to the listview structure
8439 * wKey [I ] key flag
8440 * x,y [I ] mouse coordinate
8442 * RETURN:
8443 * Zero
8445 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8447 LVHITTESTINFO lvHitTestInfo;
8448 static BOOL bGroupSelect = TRUE;
8449 BOOL bReceivedFocus = FALSE;
8450 POINT pt = { x, y };
8451 INT nItem;
8453 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8455 /* send NM_RELEASEDCAPTURE notification */
8456 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8458 if (!infoPtr->bFocus)
8460 bReceivedFocus = TRUE;
8461 SetFocus(infoPtr->hwndSelf);
8464 /* set left button down flag and record the click position */
8465 infoPtr->bLButtonDown = TRUE;
8466 infoPtr->ptClickPos = pt;
8468 lvHitTestInfo.pt.x = x;
8469 lvHitTestInfo.pt.y = y;
8471 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8472 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8473 infoPtr->nEditLabelItem = -1;
8474 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8476 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8478 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
8479 if(state == 1 || state == 2)
8481 LVITEMW lvitem;
8482 state ^= 3;
8483 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
8484 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8485 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8487 return 0;
8490 if (infoPtr->dwStyle & LVS_SINGLESEL)
8492 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8493 infoPtr->nEditLabelItem = nItem;
8494 else
8495 LISTVIEW_SetSelection(infoPtr, nItem);
8497 else
8499 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8501 if (bGroupSelect)
8503 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8504 LISTVIEW_SetItemFocus(infoPtr, nItem);
8505 infoPtr->nSelectionMark = nItem;
8507 else
8509 LVITEMW item;
8511 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8512 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8514 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8515 infoPtr->nSelectionMark = nItem;
8518 else if (wKey & MK_CONTROL)
8520 LVITEMW item;
8522 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8524 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8525 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8526 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8527 infoPtr->nSelectionMark = nItem;
8529 else if (wKey & MK_SHIFT)
8531 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8533 else
8535 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8536 infoPtr->nEditLabelItem = nItem;
8538 /* set selection (clears other pre-existing selections) */
8539 LISTVIEW_SetSelection(infoPtr, nItem);
8543 else
8545 /* remove all selections */
8546 LISTVIEW_DeselectAll(infoPtr);
8547 ReleaseCapture();
8550 if (bReceivedFocus)
8551 infoPtr->nEditLabelItem = -1;
8553 return 0;
8556 /***
8557 * DESCRIPTION:
8558 * Processes mouse up messages (left mouse button).
8560 * PARAMETERS:
8561 * infoPtr [I ] valid pointer to the listview structure
8562 * wKey [I ] key flag
8563 * x,y [I ] mouse coordinate
8565 * RETURN:
8566 * Zero
8568 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8570 LVHITTESTINFO lvHitTestInfo;
8572 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8574 if (!infoPtr->bLButtonDown) return 0;
8576 lvHitTestInfo.pt.x = x;
8577 lvHitTestInfo.pt.y = y;
8579 /* send NM_CLICK notification */
8580 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8581 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8583 /* set left button flag */
8584 infoPtr->bLButtonDown = FALSE;
8586 /* if we clicked on a selected item, edit the label */
8587 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8589 /* we want to make sure the user doesn't want to do a double click. So we will
8590 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8592 infoPtr->itemEdit.fEnabled = TRUE;
8593 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8594 SetTimer(infoPtr->hwndSelf,
8595 (UINT_PTR)&infoPtr->itemEdit,
8596 GetDoubleClickTime(),
8597 LISTVIEW_DelayedEditItem);
8600 return 0;
8603 /***
8604 * DESCRIPTION:
8605 * Destroys the listview control (called after WM_DESTROY).
8607 * PARAMETER(S):
8608 * [I] infoPtr : valid pointer to the listview structure
8610 * RETURN:
8611 * Zero
8613 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8615 TRACE("()\n");
8617 /* delete all items */
8618 LISTVIEW_DeleteAllItems(infoPtr);
8620 /* destroy data structure */
8621 DPA_Destroy(infoPtr->hdpaItems);
8622 DPA_Destroy(infoPtr->hdpaPosX);
8623 DPA_Destroy(infoPtr->hdpaPosY);
8624 DPA_Destroy(infoPtr->hdpaColumns);
8625 ranges_destroy(infoPtr->selectionRanges);
8627 /* destroy image lists */
8628 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8630 if (infoPtr->himlNormal)
8631 ImageList_Destroy(infoPtr->himlNormal);
8632 if (infoPtr->himlSmall)
8633 ImageList_Destroy(infoPtr->himlSmall);
8634 if (infoPtr->himlState)
8635 ImageList_Destroy(infoPtr->himlState);
8638 /* destroy font, bkgnd brush */
8639 infoPtr->hFont = 0;
8640 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8641 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8643 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8645 /* free listview info pointer*/
8646 Free(infoPtr);
8648 return 0;
8651 /***
8652 * DESCRIPTION:
8653 * Handles notifications from header.
8655 * PARAMETER(S):
8656 * [I] infoPtr : valid pointer to the listview structure
8657 * [I] nCtrlId : control identifier
8658 * [I] lpnmh : notification information
8660 * RETURN:
8661 * Zero
8663 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8665 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8666 HWND hwndSelf = infoPtr->hwndSelf;
8668 TRACE("(lpnmh=%p)\n", lpnmh);
8670 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8672 switch (lpnmh->hdr.code)
8674 case HDN_TRACKW:
8675 case HDN_TRACKA:
8677 COLUMN_INFO *lpColumnInfo;
8678 POINT ptOrigin;
8679 INT x;
8681 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8682 break;
8684 /* remove the old line (if any) */
8685 LISTVIEW_DrawTrackLine(infoPtr);
8687 /* compute & draw the new line */
8688 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8689 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8690 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8691 infoPtr->xTrackLine = x + ptOrigin.x;
8692 LISTVIEW_DrawTrackLine(infoPtr);
8693 break;
8696 case HDN_ENDTRACKA:
8697 case HDN_ENDTRACKW:
8698 /* remove the track line (if any) */
8699 LISTVIEW_DrawTrackLine(infoPtr);
8700 infoPtr->xTrackLine = -1;
8701 break;
8703 case HDN_ENDDRAG:
8704 FIXME("Changing column order not implemented\n");
8705 return TRUE;
8707 case HDN_ITEMCHANGINGW:
8708 case HDN_ITEMCHANGINGA:
8709 return notify_forward_header(infoPtr, lpnmh);
8711 case HDN_ITEMCHANGEDW:
8712 case HDN_ITEMCHANGEDA:
8714 COLUMN_INFO *lpColumnInfo;
8715 INT dx, cxy;
8717 notify_forward_header(infoPtr, lpnmh);
8718 if (!IsWindow(hwndSelf))
8719 break;
8721 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8723 HDITEMW hdi;
8725 hdi.mask = HDI_WIDTH;
8726 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8727 cxy = hdi.cxy;
8729 else
8730 cxy = lpnmh->pitem->cxy;
8732 /* determine how much we change since the last know position */
8733 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8734 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8735 if (dx != 0)
8737 lpColumnInfo->rcHeader.right += dx;
8738 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8739 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8740 else
8742 /* only needs to update the scrolls */
8743 infoPtr->nItemWidth += dx;
8744 LISTVIEW_UpdateScroll(infoPtr);
8746 LISTVIEW_UpdateItemSize(infoPtr);
8747 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8749 POINT ptOrigin;
8750 RECT rcCol = lpColumnInfo->rcHeader;
8752 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8753 OffsetRect(&rcCol, ptOrigin.x, 0);
8755 rcCol.top = infoPtr->rcList.top;
8756 rcCol.bottom = infoPtr->rcList.bottom;
8758 /* resizing left-aligned columns leaves most of the left side untouched */
8759 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8761 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8762 if (dx > 0)
8763 nMaxDirty += dx;
8764 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8767 /* when shrinking the last column clear the now unused field */
8768 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8769 rcCol.right -= dx;
8771 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8775 break;
8777 case HDN_ITEMCLICKW:
8778 case HDN_ITEMCLICKA:
8780 /* Handle sorting by Header Column */
8781 NMLISTVIEW nmlv;
8783 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8784 nmlv.iItem = -1;
8785 nmlv.iSubItem = lpnmh->iItem;
8786 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8788 break;
8790 case HDN_DIVIDERDBLCLICKW:
8791 case HDN_DIVIDERDBLCLICKA:
8792 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8793 break;
8796 return 0;
8799 /***
8800 * DESCRIPTION:
8801 * Paint non-client area of control.
8803 * PARAMETER(S):
8804 * [I] infoPtr : valid pointer to the listview structureof the sender
8805 * [I] region : update region
8807 * RETURN:
8808 * TRUE - frame was painted
8809 * FALSE - call default window proc
8811 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8813 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8814 HDC dc;
8815 RECT r;
8816 HRGN cliprgn;
8817 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8818 cyEdge = GetSystemMetrics (SM_CYEDGE);
8820 if (!theme) return FALSE;
8822 GetWindowRect(infoPtr->hwndSelf, &r);
8824 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8825 r.right - cxEdge, r.bottom - cyEdge);
8826 if (region != (HRGN)1)
8827 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8828 OffsetRect(&r, -r.left, -r.top);
8830 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
8831 OffsetRect(&r, -r.left, -r.top);
8833 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
8834 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
8835 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
8836 ReleaseDC(infoPtr->hwndSelf, dc);
8838 /* Call default proc to get the scrollbars etc. painted */
8839 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
8841 return TRUE;
8844 /***
8845 * DESCRIPTION:
8846 * Determines the type of structure to use.
8848 * PARAMETER(S):
8849 * [I] infoPtr : valid pointer to the listview structureof the sender
8850 * [I] hwndFrom : listview window handle
8851 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8853 * RETURN:
8854 * Zero
8856 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8858 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8860 if (nCommand != NF_REQUERY) return 0;
8862 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8864 return 0;
8867 /***
8868 * DESCRIPTION:
8869 * Paints/Repaints the listview control.
8871 * PARAMETER(S):
8872 * [I] infoPtr : valid pointer to the listview structure
8873 * [I] hdc : device context handle
8875 * RETURN:
8876 * Zero
8878 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8880 TRACE("(hdc=%p)\n", hdc);
8882 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8884 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8886 infoPtr->bNoItemMetrics = FALSE;
8887 LISTVIEW_UpdateItemSize(infoPtr);
8888 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8889 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8890 LISTVIEW_UpdateScroll(infoPtr);
8893 UpdateWindow(infoPtr->hwndHeader);
8895 if (hdc)
8896 LISTVIEW_Refresh(infoPtr, hdc, NULL);
8897 else
8899 PAINTSTRUCT ps;
8901 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8902 if (!hdc) return 1;
8903 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
8904 EndPaint(infoPtr->hwndSelf, &ps);
8907 return 0;
8911 /***
8912 * DESCRIPTION:
8913 * Paints/Repaints the listview control.
8915 * PARAMETER(S):
8916 * [I] infoPtr : valid pointer to the listview structure
8917 * [I] hdc : device context handle
8918 * [I] options : drawing options
8920 * RETURN:
8921 * Zero
8923 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
8925 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
8927 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
8928 return 0;
8930 if (options & PRF_ERASEBKGND)
8931 LISTVIEW_EraseBkgnd(infoPtr, hdc);
8933 if (options & PRF_CLIENT)
8934 LISTVIEW_Paint(infoPtr, hdc);
8936 return 0;
8940 /***
8941 * DESCRIPTION:
8942 * Processes double click messages (right mouse button).
8944 * PARAMETER(S):
8945 * [I] infoPtr : valid pointer to the listview structure
8946 * [I] wKey : key flag
8947 * [I] x,y : mouse coordinate
8949 * RETURN:
8950 * Zero
8952 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8954 LVHITTESTINFO lvHitTestInfo;
8956 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8958 /* send NM_RELEASEDCAPTURE notification */
8959 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8961 /* send NM_RDBLCLK notification */
8962 lvHitTestInfo.pt.x = x;
8963 lvHitTestInfo.pt.y = y;
8964 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8965 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8967 return 0;
8970 /***
8971 * DESCRIPTION:
8972 * Processes mouse down messages (right mouse button).
8974 * PARAMETER(S):
8975 * [I] infoPtr : valid pointer to the listview structure
8976 * [I] wKey : key flag
8977 * [I] x,y : mouse coordinate
8979 * RETURN:
8980 * Zero
8982 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8984 LVHITTESTINFO lvHitTestInfo;
8985 INT nItem;
8987 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
8989 /* send NM_RELEASEDCAPTURE notification */
8990 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8992 /* make sure the listview control window has the focus */
8993 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8995 /* set right button down flag */
8996 infoPtr->bRButtonDown = TRUE;
8998 /* determine the index of the selected item */
8999 lvHitTestInfo.pt.x = x;
9000 lvHitTestInfo.pt.y = y;
9001 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9003 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9005 LISTVIEW_SetItemFocus(infoPtr, nItem);
9006 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9007 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9008 LISTVIEW_SetSelection(infoPtr, nItem);
9010 else
9012 LISTVIEW_DeselectAll(infoPtr);
9015 return 0;
9018 /***
9019 * DESCRIPTION:
9020 * Processes mouse up messages (right mouse button).
9022 * PARAMETER(S):
9023 * [I] infoPtr : valid pointer to the listview structure
9024 * [I] wKey : key flag
9025 * [I] x,y : mouse coordinate
9027 * RETURN:
9028 * Zero
9030 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9032 LVHITTESTINFO lvHitTestInfo;
9033 POINT pt;
9035 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9037 if (!infoPtr->bRButtonDown) return 0;
9039 /* set button flag */
9040 infoPtr->bRButtonDown = FALSE;
9042 /* Send NM_RClICK notification */
9043 lvHitTestInfo.pt.x = x;
9044 lvHitTestInfo.pt.y = y;
9045 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9046 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9048 /* Change to screen coordinate for WM_CONTEXTMENU */
9049 pt = lvHitTestInfo.pt;
9050 ClientToScreen(infoPtr->hwndSelf, &pt);
9052 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9053 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9054 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9056 return 0;
9060 /***
9061 * DESCRIPTION:
9062 * Sets the cursor.
9064 * PARAMETER(S):
9065 * [I] infoPtr : valid pointer to the listview structure
9066 * [I] hwnd : window handle of window containing the cursor
9067 * [I] nHittest : hit-test code
9068 * [I] wMouseMsg : ideintifier of the mouse message
9070 * RETURN:
9071 * TRUE if cursor is set
9072 * FALSE otherwise
9074 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9076 LVHITTESTINFO lvHitTestInfo;
9078 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
9080 if(!infoPtr->hHotCursor) return FALSE;
9082 GetCursorPos(&lvHitTestInfo.pt);
9083 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9085 SetCursor(infoPtr->hHotCursor);
9087 return TRUE;
9090 /***
9091 * DESCRIPTION:
9092 * Sets the focus.
9094 * PARAMETER(S):
9095 * [I] infoPtr : valid pointer to the listview structure
9096 * [I] hwndLoseFocus : handle of previously focused window
9098 * RETURN:
9099 * Zero
9101 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9103 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9105 /* if we have the focus already, there's nothing to do */
9106 if (infoPtr->bFocus) return 0;
9108 /* send NM_SETFOCUS notification */
9109 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9111 /* set window focus flag */
9112 infoPtr->bFocus = TRUE;
9114 /* put the focus rect back on */
9115 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9117 /* redraw all visible selected items */
9118 LISTVIEW_InvalidateSelectedItems(infoPtr);
9120 return 0;
9123 /***
9124 * DESCRIPTION:
9125 * Sets the font.
9127 * PARAMETER(S):
9128 * [I] infoPtr : valid pointer to the listview structure
9129 * [I] fRedraw : font handle
9130 * [I] fRedraw : redraw flag
9132 * RETURN:
9133 * Zero
9135 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9137 HFONT oldFont = infoPtr->hFont;
9139 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9141 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9142 if (infoPtr->hFont == oldFont) return 0;
9144 LISTVIEW_SaveTextMetrics(infoPtr);
9146 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9147 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9149 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9151 return 0;
9154 /***
9155 * DESCRIPTION:
9156 * Message handling for WM_SETREDRAW.
9157 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9159 * PARAMETER(S):
9160 * [I] infoPtr : valid pointer to the listview structure
9161 * [I] bRedraw: state of redraw flag
9163 * RETURN:
9164 * DefWinProc return value
9166 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9168 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9170 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9171 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9173 infoPtr->bRedraw = bRedraw;
9175 if(!bRedraw) return 0;
9177 if (is_autoarrange(infoPtr))
9178 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9179 LISTVIEW_UpdateScroll(infoPtr);
9181 /* despite what the WM_SETREDRAW docs says, apps expect us
9182 * to invalidate the listview here... stupid! */
9183 LISTVIEW_InvalidateList(infoPtr);
9185 return 0;
9188 /***
9189 * DESCRIPTION:
9190 * Resizes the listview control. This function processes WM_SIZE
9191 * messages. At this time, the width and height are not used.
9193 * PARAMETER(S):
9194 * [I] infoPtr : valid pointer to the listview structure
9195 * [I] Width : new width
9196 * [I] Height : new height
9198 * RETURN:
9199 * Zero
9201 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9203 RECT rcOld = infoPtr->rcList;
9205 TRACE("(width=%d, height=%d)\n", Width, Height);
9207 LISTVIEW_UpdateSize(infoPtr);
9208 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9210 /* do not bother with display related stuff if we're not redrawing */
9211 if (!is_redrawing(infoPtr)) return 0;
9213 if (is_autoarrange(infoPtr))
9214 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9216 LISTVIEW_UpdateScroll(infoPtr);
9218 /* refresh all only for lists whose height changed significantly */
9219 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9220 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9221 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9222 LISTVIEW_InvalidateList(infoPtr);
9224 return 0;
9227 /***
9228 * DESCRIPTION:
9229 * Sets the size information.
9231 * PARAMETER(S):
9232 * [I] infoPtr : valid pointer to the listview structure
9234 * RETURN:
9235 * None
9237 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9239 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9241 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9243 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9245 if (uView == LVS_LIST)
9247 /* Apparently the "LIST" style is supposed to have the same
9248 * number of items in a column even if there is no scroll bar.
9249 * Since if a scroll bar already exists then the bottom is already
9250 * reduced, only reduce if the scroll bar does not currently exist.
9251 * The "2" is there to mimic the native control. I think it may be
9252 * related to either padding or edges. (GLA 7/2002)
9254 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9255 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9256 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9258 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
9260 HDLAYOUT hl;
9261 WINDOWPOS wp;
9263 hl.prc = &infoPtr->rcList;
9264 hl.pwpos = &wp;
9265 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9267 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9269 infoPtr->rcList.top = max(wp.cy, 0);
9272 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9275 /***
9276 * DESCRIPTION:
9277 * Processes WM_STYLECHANGED messages.
9279 * PARAMETER(S):
9280 * [I] infoPtr : valid pointer to the listview structure
9281 * [I] wStyleType : window style type (normal or extended)
9282 * [I] lpss : window style information
9284 * RETURN:
9285 * Zero
9287 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9288 const STYLESTRUCT *lpss)
9290 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9291 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9293 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9294 wStyleType, lpss->styleOld, lpss->styleNew);
9296 if (wStyleType != GWL_STYLE) return 0;
9298 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9299 /* what if LVS_OWNERDATA changed? */
9300 /* or LVS_SINGLESEL */
9301 /* or LVS_SORT{AS,DES}CENDING */
9303 infoPtr->dwStyle = lpss->styleNew;
9305 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9306 ((lpss->styleNew & WS_HSCROLL) == 0))
9307 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9309 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9310 ((lpss->styleNew & WS_VSCROLL) == 0))
9311 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9313 if (uNewView != uOldView)
9315 SIZE oldIconSize = infoPtr->iconSize;
9316 HIMAGELIST himl;
9318 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9319 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9321 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9322 SetRectEmpty(&infoPtr->rcFocus);
9324 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9325 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9327 if (uNewView == LVS_ICON)
9329 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9331 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9332 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9333 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9336 else if (uNewView == LVS_REPORT)
9338 HDLAYOUT hl;
9339 WINDOWPOS wp;
9341 hl.prc = &infoPtr->rcList;
9342 hl.pwpos = &wp;
9343 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9344 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
9347 LISTVIEW_UpdateItemSize(infoPtr);
9350 if (uNewView == LVS_REPORT)
9351 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
9353 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9354 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9355 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9357 /* update the size of the client area */
9358 LISTVIEW_UpdateSize(infoPtr);
9360 /* add scrollbars if needed */
9361 LISTVIEW_UpdateScroll(infoPtr);
9363 /* invalidate client area + erase background */
9364 LISTVIEW_InvalidateList(infoPtr);
9366 return 0;
9369 /***
9370 * DESCRIPTION:
9371 * Window procedure of the listview control.
9374 static LRESULT WINAPI
9375 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9377 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9379 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9381 if (!infoPtr && (uMsg != WM_NCCREATE))
9382 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9384 switch (uMsg)
9386 case LVM_APPROXIMATEVIEWRECT:
9387 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9388 LOWORD(lParam), HIWORD(lParam));
9389 case LVM_ARRANGE:
9390 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9392 /* case LVM_CANCELEDITLABEL: */
9394 case LVM_CREATEDRAGIMAGE:
9395 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9397 case LVM_DELETEALLITEMS:
9398 return LISTVIEW_DeleteAllItems(infoPtr);
9400 case LVM_DELETECOLUMN:
9401 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9403 case LVM_DELETEITEM:
9404 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9406 case LVM_EDITLABELW:
9407 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9409 case LVM_EDITLABELA:
9410 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9412 /* case LVM_ENABLEGROUPVIEW: */
9414 case LVM_ENSUREVISIBLE:
9415 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9417 case LVM_FINDITEMW:
9418 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9420 case LVM_FINDITEMA:
9421 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9423 case LVM_GETBKCOLOR:
9424 return infoPtr->clrBk;
9426 /* case LVM_GETBKIMAGE: */
9428 case LVM_GETCALLBACKMASK:
9429 return infoPtr->uCallbackMask;
9431 case LVM_GETCOLUMNA:
9432 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9434 case LVM_GETCOLUMNW:
9435 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9437 case LVM_GETCOLUMNORDERARRAY:
9438 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9440 case LVM_GETCOLUMNWIDTH:
9441 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9443 case LVM_GETCOUNTPERPAGE:
9444 return LISTVIEW_GetCountPerPage(infoPtr);
9446 case LVM_GETEDITCONTROL:
9447 return (LRESULT)infoPtr->hwndEdit;
9449 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9450 return infoPtr->dwLvExStyle;
9452 /* case LVM_GETGROUPINFO: */
9454 /* case LVM_GETGROUPMETRICS: */
9456 case LVM_GETHEADER:
9457 return (LRESULT)infoPtr->hwndHeader;
9459 case LVM_GETHOTCURSOR:
9460 return (LRESULT)infoPtr->hHotCursor;
9462 case LVM_GETHOTITEM:
9463 return infoPtr->nHotItem;
9465 case LVM_GETHOVERTIME:
9466 return infoPtr->dwHoverTime;
9468 case LVM_GETIMAGELIST:
9469 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9471 /* case LVM_GETINSERTMARK: */
9473 /* case LVM_GETINSERTMARKCOLOR: */
9475 /* case LVM_GETINSERTMARKRECT: */
9477 case LVM_GETISEARCHSTRINGA:
9478 case LVM_GETISEARCHSTRINGW:
9479 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9480 return FALSE;
9482 case LVM_GETITEMA:
9483 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9485 case LVM_GETITEMW:
9486 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9488 case LVM_GETITEMCOUNT:
9489 return infoPtr->nItemCount;
9491 case LVM_GETITEMPOSITION:
9492 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9494 case LVM_GETITEMRECT:
9495 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9497 case LVM_GETITEMSPACING:
9498 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9500 case LVM_GETITEMSTATE:
9501 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9503 case LVM_GETITEMTEXTA:
9504 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9506 case LVM_GETITEMTEXTW:
9507 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9509 case LVM_GETNEXTITEM:
9510 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9512 case LVM_GETNUMBEROFWORKAREAS:
9513 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9514 return 1;
9516 case LVM_GETORIGIN:
9517 if (!lParam) return FALSE;
9518 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9519 return TRUE;
9521 /* case LVM_GETOUTLINECOLOR: */
9523 /* case LVM_GETSELECTEDCOLUMN: */
9525 case LVM_GETSELECTEDCOUNT:
9526 return LISTVIEW_GetSelectedCount(infoPtr);
9528 case LVM_GETSELECTIONMARK:
9529 return infoPtr->nSelectionMark;
9531 case LVM_GETSTRINGWIDTHA:
9532 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9534 case LVM_GETSTRINGWIDTHW:
9535 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9537 case LVM_GETSUBITEMRECT:
9538 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9540 case LVM_GETTEXTBKCOLOR:
9541 return infoPtr->clrTextBk;
9543 case LVM_GETTEXTCOLOR:
9544 return infoPtr->clrText;
9546 /* case LVM_GETTILEINFO: */
9548 /* case LVM_GETTILEVIEWINFO: */
9550 case LVM_GETTOOLTIPS:
9551 if( !infoPtr->hwndToolTip )
9552 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9553 return (LRESULT)infoPtr->hwndToolTip;
9555 case LVM_GETTOPINDEX:
9556 return LISTVIEW_GetTopIndex(infoPtr);
9558 /*case LVM_GETUNICODEFORMAT:
9559 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
9560 return FALSE;*/
9562 /* case LVM_GETVIEW: */
9564 case LVM_GETVIEWRECT:
9565 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9567 case LVM_GETWORKAREAS:
9568 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9569 return FALSE;
9571 /* case LVM_HASGROUP: */
9573 case LVM_HITTEST:
9574 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9576 case LVM_INSERTCOLUMNA:
9577 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9579 case LVM_INSERTCOLUMNW:
9580 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9582 /* case LVM_INSERTGROUP: */
9584 /* case LVM_INSERTGROUPSORTED: */
9586 case LVM_INSERTITEMA:
9587 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9589 case LVM_INSERTITEMW:
9590 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9592 /* case LVM_INSERTMARKHITTEST: */
9594 /* case LVM_ISGROUPVIEWENABLED: */
9596 /* case LVM_MAPIDTOINDEX: */
9598 /* case LVM_MAPINDEXTOID: */
9600 /* case LVM_MOVEGROUP: */
9602 /* case LVM_MOVEITEMTOGROUP: */
9604 case LVM_REDRAWITEMS:
9605 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9607 /* case LVM_REMOVEALLGROUPS: */
9609 /* case LVM_REMOVEGROUP: */
9611 case LVM_SCROLL:
9612 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9614 case LVM_SETBKCOLOR:
9615 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9617 /* case LVM_SETBKIMAGE: */
9619 case LVM_SETCALLBACKMASK:
9620 infoPtr->uCallbackMask = (UINT)wParam;
9621 return TRUE;
9623 case LVM_SETCOLUMNA:
9624 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9626 case LVM_SETCOLUMNW:
9627 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9629 case LVM_SETCOLUMNORDERARRAY:
9630 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9632 case LVM_SETCOLUMNWIDTH:
9633 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9635 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9636 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9638 /* case LVM_SETGROUPINFO: */
9640 /* case LVM_SETGROUPMETRICS: */
9642 case LVM_SETHOTCURSOR:
9643 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9645 case LVM_SETHOTITEM:
9646 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9648 case LVM_SETHOVERTIME:
9649 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9651 case LVM_SETICONSPACING:
9652 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9654 case LVM_SETIMAGELIST:
9655 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9657 /* case LVM_SETINFOTIP: */
9659 /* case LVM_SETINSERTMARK: */
9661 /* case LVM_SETINSERTMARKCOLOR: */
9663 case LVM_SETITEMA:
9664 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9666 case LVM_SETITEMW:
9667 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9669 case LVM_SETITEMCOUNT:
9670 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9672 case LVM_SETITEMPOSITION:
9674 POINT pt;
9675 pt.x = (short)LOWORD(lParam);
9676 pt.y = (short)HIWORD(lParam);
9677 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9680 case LVM_SETITEMPOSITION32:
9681 if (lParam == 0) return FALSE;
9682 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9684 case LVM_SETITEMSTATE:
9685 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9687 case LVM_SETITEMTEXTA:
9688 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9690 case LVM_SETITEMTEXTW:
9691 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9693 /* case LVM_SETOUTLINECOLOR: */
9695 /* case LVM_SETSELECTEDCOLUMN: */
9697 case LVM_SETSELECTIONMARK:
9698 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9700 case LVM_SETTEXTBKCOLOR:
9701 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9703 case LVM_SETTEXTCOLOR:
9704 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9706 /* case LVM_SETTILEINFO: */
9708 /* case LVM_SETTILEVIEWINFO: */
9710 /* case LVM_SETTILEWIDTH: */
9712 case LVM_SETTOOLTIPS:
9713 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9715 case LVM_SETUNICODEFORMAT:
9716 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9718 /* case LVM_SETVIEW: */
9720 /* case LVM_SETWORKAREAS: */
9722 /* case LVM_SORTGROUPS: */
9724 case LVM_SORTITEMS:
9725 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9727 /* LVM_SORTITEMSEX: */
9729 case LVM_SUBITEMHITTEST:
9730 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9732 case LVM_UPDATE:
9733 return LISTVIEW_Update(infoPtr, (INT)wParam);
9735 case WM_CHAR:
9736 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9738 case WM_COMMAND:
9739 return LISTVIEW_Command(infoPtr, wParam, lParam);
9741 case WM_NCCREATE:
9742 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9744 case WM_CREATE:
9745 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9747 case WM_DESTROY:
9748 return LISTVIEW_Destroy(infoPtr);
9750 case WM_ENABLE:
9751 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9753 case WM_ERASEBKGND:
9754 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9756 case WM_GETDLGCODE:
9757 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9759 case WM_GETFONT:
9760 return (LRESULT)infoPtr->hFont;
9762 case WM_HSCROLL:
9763 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9765 case WM_KEYDOWN:
9766 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9768 case WM_KILLFOCUS:
9769 return LISTVIEW_KillFocus(infoPtr);
9771 case WM_LBUTTONDBLCLK:
9772 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9774 case WM_LBUTTONDOWN:
9775 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9777 case WM_LBUTTONUP:
9778 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9780 case WM_MOUSEMOVE:
9781 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9783 case WM_MOUSEHOVER:
9784 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9786 case WM_NCDESTROY:
9787 return LISTVIEW_NCDestroy(infoPtr);
9789 case WM_NCPAINT:
9790 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9791 return 0;
9792 goto fwd_msg;
9794 case WM_NOTIFY:
9795 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9796 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9797 else return 0;
9799 case WM_NOTIFYFORMAT:
9800 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9802 case WM_PRINTCLIENT:
9803 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
9805 case WM_PAINT:
9806 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9808 case WM_RBUTTONDBLCLK:
9809 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9811 case WM_RBUTTONDOWN:
9812 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9814 case WM_RBUTTONUP:
9815 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9817 case WM_SETCURSOR:
9818 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9819 return TRUE;
9820 goto fwd_msg;
9822 case WM_SETFOCUS:
9823 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9825 case WM_SETFONT:
9826 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9828 case WM_SETREDRAW:
9829 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9831 case WM_SIZE:
9832 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9834 case WM_STYLECHANGED:
9835 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9837 case WM_SYSCOLORCHANGE:
9838 COMCTL32_RefreshSysColors();
9839 return 0;
9841 /* case WM_TIMER: */
9842 case WM_THEMECHANGED:
9843 return LISTVIEW_ThemeChanged(infoPtr);
9845 case WM_VSCROLL:
9846 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9848 case WM_MOUSEWHEEL:
9849 if (wParam & (MK_SHIFT | MK_CONTROL))
9850 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9851 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9853 case WM_WINDOWPOSCHANGED:
9854 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9856 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9857 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9858 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9860 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
9862 MEASUREITEMSTRUCT mis;
9863 mis.CtlType = ODT_LISTVIEW;
9864 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
9865 mis.itemID = -1;
9866 mis.itemWidth = 0;
9867 mis.itemData = 0;
9868 mis.itemHeight= infoPtr->nItemHeight;
9869 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
9870 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
9871 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
9874 LISTVIEW_UpdateSize(infoPtr);
9875 LISTVIEW_UpdateScroll(infoPtr);
9877 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9879 /* case WM_WININICHANGE: */
9881 default:
9882 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9883 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
9885 fwd_msg:
9886 /* call default window procedure */
9887 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9892 /***
9893 * DESCRIPTION:
9894 * Registers the window class.
9896 * PARAMETER(S):
9897 * None
9899 * RETURN:
9900 * None
9902 void LISTVIEW_Register(void)
9904 WNDCLASSW wndClass;
9906 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9907 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9908 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
9909 wndClass.cbClsExtra = 0;
9910 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9911 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9912 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9913 wndClass.lpszClassName = WC_LISTVIEWW;
9914 RegisterClassW(&wndClass);
9917 /***
9918 * DESCRIPTION:
9919 * Unregisters the window class.
9921 * PARAMETER(S):
9922 * None
9924 * RETURN:
9925 * None
9927 void LISTVIEW_Unregister(void)
9929 UnregisterClassW(WC_LISTVIEWW, NULL);
9932 /***
9933 * DESCRIPTION:
9934 * Handle any WM_COMMAND messages
9936 * PARAMETER(S):
9937 * [I] infoPtr : valid pointer to the listview structure
9938 * [I] wParam : the first message parameter
9939 * [I] lParam : the second message parameter
9941 * RETURN:
9942 * Zero.
9944 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9946 switch (HIWORD(wParam))
9948 case EN_UPDATE:
9951 * Adjust the edit window size
9953 WCHAR buffer[1024];
9954 HDC hdc = GetDC(infoPtr->hwndEdit);
9955 HFONT hFont, hOldFont = 0;
9956 RECT rect;
9957 SIZE sz;
9958 int len;
9960 if (!infoPtr->hwndEdit || !hdc) return 0;
9961 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9962 GetWindowRect(infoPtr->hwndEdit, &rect);
9964 /* Select font to get the right dimension of the string */
9965 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9966 if(hFont != 0)
9968 hOldFont = SelectObject(hdc, hFont);
9971 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9973 TEXTMETRICW textMetric;
9975 /* Add Extra spacing for the next character */
9976 GetTextMetricsW(hdc, &textMetric);
9977 sz.cx += (textMetric.tmMaxCharWidth * 2);
9979 SetWindowPos (
9980 infoPtr->hwndEdit,
9981 HWND_TOP,
9984 sz.cx,
9985 rect.bottom - rect.top,
9986 SWP_DRAWFRAME|SWP_NOMOVE);
9988 if(hFont != 0)
9989 SelectObject(hdc, hOldFont);
9991 ReleaseDC(infoPtr->hwndEdit, hdc);
9993 break;
9996 default:
9997 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10000 return 0;
10004 /***
10005 * DESCRIPTION:
10006 * Subclassed edit control windproc function
10008 * PARAMETER(S):
10009 * [I] hwnd : the edit window handle
10010 * [I] uMsg : the message that is to be processed
10011 * [I] wParam : first message parameter
10012 * [I] lParam : second message parameter
10013 * [I] isW : TRUE if input is Unicode
10015 * RETURN:
10016 * Zero.
10018 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10020 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10021 BOOL cancel = FALSE;
10023 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10024 hwnd, uMsg, wParam, lParam, isW);
10026 switch (uMsg)
10028 case WM_GETDLGCODE:
10029 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10031 case WM_KILLFOCUS:
10032 break;
10034 case WM_DESTROY:
10036 WNDPROC editProc = infoPtr->EditWndProc;
10037 infoPtr->EditWndProc = 0;
10038 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10039 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10042 case WM_KEYDOWN:
10043 if (VK_ESCAPE == (INT)wParam)
10045 cancel = TRUE;
10046 break;
10048 else if (VK_RETURN == (INT)wParam)
10049 break;
10051 default:
10052 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10055 /* kill the edit */
10056 if (infoPtr->hwndEdit)
10058 LPWSTR buffer = NULL;
10060 infoPtr->hwndEdit = 0;
10061 if (!cancel)
10063 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10065 if (len)
10067 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10069 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10070 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10074 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10076 Free(buffer);
10079 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10080 return 0;
10083 /***
10084 * DESCRIPTION:
10085 * Subclassed edit control Unicode windproc function
10087 * PARAMETER(S):
10088 * [I] hwnd : the edit window handle
10089 * [I] uMsg : the message that is to be processed
10090 * [I] wParam : first message parameter
10091 * [I] lParam : second message parameter
10093 * RETURN:
10095 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10097 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10100 /***
10101 * DESCRIPTION:
10102 * Subclassed edit control ANSI windproc function
10104 * PARAMETER(S):
10105 * [I] hwnd : the edit window handle
10106 * [I] uMsg : the message that is to be processed
10107 * [I] wParam : first message parameter
10108 * [I] lParam : second message parameter
10110 * RETURN:
10112 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10114 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10117 /***
10118 * DESCRIPTION:
10119 * Creates a subclassed edit cotrol
10121 * PARAMETER(S):
10122 * [I] infoPtr : valid pointer to the listview structure
10123 * [I] text : initial text for the edit
10124 * [I] style : the window style
10125 * [I] isW : TRUE if input is Unicode
10127 * RETURN:
10129 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10130 INT x, INT y, INT width, INT height, BOOL isW)
10132 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10133 HWND hedit;
10134 SIZE sz;
10135 HDC hdc;
10136 HDC hOldFont=0;
10137 TEXTMETRICW textMetric;
10138 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10140 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10142 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10143 hdc = GetDC(infoPtr->hwndSelf);
10145 /* Select the font to get appropriate metric dimensions */
10146 if(infoPtr->hFont != 0)
10147 hOldFont = SelectObject(hdc, infoPtr->hFont);
10149 /*Get String Length in pixels */
10150 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10152 /*Add Extra spacing for the next character */
10153 GetTextMetricsW(hdc, &textMetric);
10154 sz.cx += (textMetric.tmMaxCharWidth * 2);
10156 if(infoPtr->hFont != 0)
10157 SelectObject(hdc, hOldFont);
10159 ReleaseDC(infoPtr->hwndSelf, hdc);
10160 if (isW)
10161 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10162 else
10163 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10165 if (!hedit) return 0;
10167 infoPtr->EditWndProc = (WNDPROC)
10168 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10169 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10171 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10173 return hedit;