push 65f191514f6da34cdfbbc73b8f5d66c351839600
[wine/hacks.git] / dlls / comctl32 / listview.c
blob1c84f0272dbdff39cfc2662fab102c0654204763
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
40 * or small icon and the LVS_AUTOARRANGE style is specified.
41 * -- WM_TIMER
42 * -- WM_WININICHANGE
44 * Features
45 * -- Hot item handling, mouse hovering
46 * -- Workareas support
47 * -- Tilemode support
48 * -- Groups support
50 * Bugs
51 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
52 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
53 * -- LVA_SNAPTOGRID not implemented
54 * -- LISTVIEW_ApproximateViewRect partially implemented
55 * -- LISTVIEW_[GS]etColumnOrderArray stubs
56 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
57 * -- LISTVIEW_SetIconSpacing is incomplete
58 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
60 * Speedups
61 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
62 * linear in the number of items in the list, and this is
63 * unacceptable for large lists.
64 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
65 * binary search to calculate item index (e.g. DPA_Search()).
66 * This requires sorted state to be reliably tracked in item modifiers.
67 * -- we should keep an ordered array of coordinates in iconic mode
68 * this would allow to frame items (iterator_frameditems),
69 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
71 * Flags
72 * -- LVIF_COLUMNS
73 * -- LVIF_GROUPID
75 * States
76 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
77 * -- LVIS_CUT
78 * -- LVIS_DROPHILITED
79 * -- LVIS_OVERLAYMASK
81 * Styles
82 * -- LVS_NOLABELWRAP
83 * -- LVS_NOSCROLL (see Q137520)
84 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
85 * -- LVS_ALIGNTOP
86 * -- LVS_TYPESTYLEMASK
88 * Extended Styles
89 * -- LVS_EX_BORDERSELECT
90 * -- LVS_EX_FLATSB
91 * -- LVS_EX_HEADERDRAGDROP
92 * -- LVS_EX_INFOTIP
93 * -- LVS_EX_LABELTIP
94 * -- LVS_EX_MULTIWORKAREAS
95 * -- LVS_EX_REGIONAL
96 * -- LVS_EX_SIMPLESELECT
97 * -- LVS_EX_TWOCLICKACTIVATE
98 * -- LVS_EX_UNDERLINECOLD
99 * -- LVS_EX_UNDERLINEHOT
101 * Notifications:
102 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
103 * -- LVN_GETINFOTIP
104 * -- LVN_HOTTRACK
105 * -- LVN_MARQUEEBEGIN
106 * -- LVN_SETDISPINFO
107 * -- NM_HOVER
108 * -- LVN_BEGINRDRAG
110 * Messages:
111 * -- LVM_CANCELEDITLABEL
112 * -- LVM_ENABLEGROUPVIEW
113 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
114 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
115 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
116 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
117 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
118 * -- LVM_GETINSERTMARKRECT
119 * -- LVM_GETNUMBEROFWORKAREAS
120 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
121 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
122 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
123 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
124 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
125 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
126 * -- LVM_GETVIEW, LVM_SETVIEW
127 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
128 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
129 * -- LVM_INSERTGROUPSORTED
130 * -- LVM_INSERTMARKHITTEST
131 * -- LVM_ISGROUPVIEWENABLED
132 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
133 * -- LVM_MOVEGROUP
134 * -- LVM_MOVEITEMTOGROUP
135 * -- LVM_SETINFOTIP
136 * -- LVM_SETTILEWIDTH
137 * -- LVM_SORTGROUPS
139 * Macros:
140 * -- ListView_GetHoverTime, ListView_SetHoverTime
141 * -- ListView_GetISearchString
142 * -- ListView_GetNumberOfWorkAreas
143 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
144 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
146 * Functions:
147 * -- LVGroupComparE
149 * Known differences in message stream from native control (not known if
150 * these differences cause problems):
151 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
152 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
153 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
154 * processing for "USEDOUBLECLICKTIME".
157 #include "config.h"
158 #include "wine/port.h"
160 #include <assert.h>
161 #include <ctype.h>
162 #include <string.h>
163 #include <stdlib.h>
164 #include <stdarg.h>
165 #include <stdio.h>
167 #include "windef.h"
168 #include "winbase.h"
169 #include "winnt.h"
170 #include "wingdi.h"
171 #include "winuser.h"
172 #include "winnls.h"
173 #include "commctrl.h"
174 #include "comctl32.h"
175 #include "uxtheme.h"
177 #include "wine/debug.h"
178 #include "wine/unicode.h"
180 WINE_DEFAULT_DEBUG_CHANNEL(listview);
182 /* make sure you set this to 0 for production use! */
183 #define DEBUG_RANGES 1
185 typedef struct tagCOLUMN_INFO
187 RECT rcHeader; /* tracks the header's rectangle */
188 int fmt; /* same as LVCOLUMN.fmt */
189 } COLUMN_INFO;
191 typedef struct tagITEMHDR
193 LPWSTR pszText;
194 INT iImage;
195 } ITEMHDR, *LPITEMHDR;
197 typedef struct tagSUBITEM_INFO
199 ITEMHDR hdr;
200 INT iSubItem;
201 } SUBITEM_INFO;
203 typedef struct tagITEM_INFO
205 ITEMHDR hdr;
206 UINT state;
207 LPARAM lParam;
208 INT iIndent;
209 } ITEM_INFO;
211 typedef struct tagRANGE
213 INT lower;
214 INT upper;
215 } RANGE;
217 typedef struct tagRANGES
219 HDPA hdpa;
220 } *RANGES;
222 typedef struct tagITERATOR
224 INT nItem;
225 INT nSpecial;
226 RANGE range;
227 RANGES ranges;
228 INT index;
229 } ITERATOR;
231 typedef struct tagDELAYED_ITEM_EDIT
233 BOOL fEnabled;
234 INT iItem;
235 } DELAYED_ITEM_EDIT;
237 typedef struct tagLISTVIEW_INFO
239 HWND hwndSelf;
240 HBRUSH hBkBrush;
241 COLORREF clrBk;
242 COLORREF clrText;
243 COLORREF clrTextBk;
244 HIMAGELIST himlNormal;
245 HIMAGELIST himlSmall;
246 HIMAGELIST himlState;
247 BOOL bLButtonDown;
248 BOOL bRButtonDown;
249 BOOL bDragging;
250 POINT ptClickPos; /* point where the user clicked */
251 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
252 INT nItemHeight;
253 INT nItemWidth;
254 RANGES selectionRanges;
255 INT nSelectionMark;
256 INT nHotItem;
257 SHORT notifyFormat;
258 HWND hwndNotify;
259 RECT rcList; /* This rectangle is really the window
260 * client rectangle possibly reduced by the
261 * horizontal scroll bar and/or header - see
262 * LISTVIEW_UpdateSize. This rectangle offset
263 * by the LISTVIEW_GetOrigin value is in
264 * client coordinates */
265 SIZE iconSize;
266 SIZE iconSpacing;
267 SIZE iconStateSize;
268 UINT uCallbackMask;
269 HWND hwndHeader;
270 HCURSOR hHotCursor;
271 HFONT hDefaultFont;
272 HFONT hFont;
273 INT ntmHeight; /* Some cached metrics of the font used */
274 INT ntmMaxCharWidth; /* by the listview to draw items */
275 INT nEllipsisWidth;
276 BOOL bRedraw; /* Turns on/off repaints & invalidations */
277 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
278 BOOL bFocus;
279 BOOL bDoChangeNotify; /* send change notification messages? */
280 INT nFocusedItem;
281 RECT rcFocus;
282 DWORD dwStyle; /* the cached window GWL_STYLE */
283 DWORD dwLvExStyle; /* extended listview style */
284 INT nItemCount; /* the number of items in the list */
285 HDPA hdpaItems; /* array ITEM_INFO pointers */
286 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
287 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
288 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
289 POINT currIconPos; /* this is the position next icon will be placed */
290 PFNLVCOMPARE pfnCompare;
291 LPARAM lParamSort;
292 HWND hwndEdit;
293 WNDPROC EditWndProc;
294 INT nEditLabelItem;
295 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
296 DWORD dwHoverTime;
297 HWND hwndToolTip;
299 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
301 DWORD lastKeyPressTimestamp;
302 WPARAM charCode;
303 INT nSearchParamLength;
304 WCHAR szSearchParam[ MAX_PATH ];
305 BOOL bIsDrawing;
306 INT nMeasureItemHeight;
307 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
308 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
310 DWORD iVersion; /* CCM_[G,S]ETVERSION */
311 } LISTVIEW_INFO;
314 * constants
316 /* How many we debug buffer to allocate */
317 #define DEBUG_BUFFERS 20
318 /* The size of a single debug bbuffer */
319 #define DEBUG_BUFFER_SIZE 256
321 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
322 #define SB_INTERNAL -1
324 /* maximum size of a label */
325 #define DISP_TEXT_SIZE 512
327 /* padding for items in list and small icon display modes */
328 #define WIDTH_PADDING 12
330 /* padding for items in list, report and small icon display modes */
331 #define HEIGHT_PADDING 1
333 /* offset of items in report display mode */
334 #define REPORT_MARGINX 2
336 /* padding for icon in large icon display mode
337 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
338 * that HITTEST will see.
339 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
340 * ICON_TOP_PADDING - sum of the two above.
341 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
342 * LABEL_HOR_PADDING - between text and sides of box
343 * LABEL_VERT_PADDING - between bottom of text and end of box
345 * ICON_LR_PADDING - additional width above icon size.
346 * ICON_LR_HALF - half of the above value
348 #define ICON_TOP_PADDING_NOTHITABLE 2
349 #define ICON_TOP_PADDING_HITABLE 2
350 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
351 #define ICON_BOTTOM_PADDING 4
352 #define LABEL_HOR_PADDING 5
353 #define LABEL_VERT_PADDING 7
354 #define ICON_LR_PADDING 16
355 #define ICON_LR_HALF (ICON_LR_PADDING/2)
357 /* default label width for items in list and small icon display modes */
358 #define DEFAULT_LABEL_WIDTH 40
360 /* default column width for items in list display mode */
361 #define DEFAULT_COLUMN_WIDTH 128
363 /* Size of "line" scroll for V & H scrolls */
364 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
366 /* Padding between image and label */
367 #define IMAGE_PADDING 2
369 /* Padding behind the label */
370 #define TRAILING_LABEL_PADDING 12
371 #define TRAILING_HEADER_PADDING 11
373 /* Border for the icon caption */
374 #define CAPTION_BORDER 2
376 /* Standard DrawText flags */
377 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
378 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
379 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
381 /* Image index from state */
382 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
384 /* The time in milliseconds to reset the search in the list */
385 #define KEY_DELAY 450
387 /* Dump the LISTVIEW_INFO structure to the debug channel */
388 #define LISTVIEW_DUMP(iP) do { \
389 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
390 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
391 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
392 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
393 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
394 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
395 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
396 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
397 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
398 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
399 } while(0)
401 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
404 * forward declarations
406 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
407 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
408 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
409 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
410 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
411 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
412 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
413 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
414 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
415 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
416 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
417 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
418 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
419 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
420 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
421 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
422 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
423 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
424 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
425 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
427 /******** Text handling functions *************************************/
429 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
430 * text string. The string may be ANSI or Unicode, in which case
431 * the boolean isW tells us the type of the string.
433 * The name of the function tell what type of strings it expects:
434 * W: Unicode, T: ANSI/Unicode - function of isW
437 static inline BOOL is_textW(LPCWSTR text)
439 return text != NULL && text != LPSTR_TEXTCALLBACKW;
442 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
444 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
445 return is_textW(text);
448 static inline int textlenT(LPCWSTR text, BOOL isW)
450 return !is_textT(text, isW) ? 0 :
451 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
454 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
456 if (isDestW)
457 if (isSrcW) lstrcpynW(dest, src, max);
458 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
459 else
460 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
461 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
464 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
466 LPWSTR wstr = (LPWSTR)text;
468 if (!isW && is_textT(text, isW))
470 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
471 wstr = Alloc(len * sizeof(WCHAR));
472 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
474 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
475 return wstr;
478 static inline void textfreeT(LPWSTR wstr, BOOL isW)
480 if (!isW && is_textT(wstr, isW)) Free (wstr);
484 * dest is a pointer to a Unicode string
485 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
487 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
489 BOOL bResult = TRUE;
491 if (src == LPSTR_TEXTCALLBACKW)
493 if (is_textW(*dest)) Free(*dest);
494 *dest = LPSTR_TEXTCALLBACKW;
496 else
498 LPWSTR pszText = textdupTtoW(src, isW);
499 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
500 bResult = Str_SetPtrW(dest, pszText);
501 textfreeT(pszText, isW);
503 return bResult;
507 * compares a Unicode to a Unicode/ANSI text string
509 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
511 if (!aw) return bt ? -1 : 0;
512 if (!bt) return aw ? 1 : 0;
513 if (aw == LPSTR_TEXTCALLBACKW)
514 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
515 if (bt != LPSTR_TEXTCALLBACKW)
517 LPWSTR bw = textdupTtoW(bt, isW);
518 int r = bw ? lstrcmpW(aw, bw) : 1;
519 textfreeT(bw, isW);
520 return r;
523 return 1;
526 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
528 int res;
530 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
531 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
532 return res ? res - sizeof(WCHAR) : res;
535 /******** Debugging functions *****************************************/
537 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
539 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
540 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
543 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
545 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
546 n = min(textlenT(text, isW), n);
547 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
550 static char* debug_getbuf(void)
552 static int index = 0;
553 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
554 return buffers[index++ % DEBUG_BUFFERS];
557 static inline const char* debugrange(const RANGE *lprng)
559 if (!lprng) return "(null)";
560 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
563 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
565 char* buf = debug_getbuf(), *text = buf;
566 int len, size = DEBUG_BUFFER_SIZE;
568 if (pScrollInfo == NULL) return "(null)";
569 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
570 if (len == -1) goto end; buf += len; size -= len;
571 if (pScrollInfo->fMask & SIF_RANGE)
572 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
573 else len = 0;
574 if (len == -1) goto end; buf += len; size -= len;
575 if (pScrollInfo->fMask & SIF_PAGE)
576 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
577 else len = 0;
578 if (len == -1) goto end; buf += len; size -= len;
579 if (pScrollInfo->fMask & SIF_POS)
580 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
581 else len = 0;
582 if (len == -1) goto end; buf += len; size -= len;
583 if (pScrollInfo->fMask & SIF_TRACKPOS)
584 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
585 else len = 0;
586 if (len == -1) goto end; buf += len; size -= len;
587 goto undo;
588 end:
589 buf = text + strlen(text);
590 undo:
591 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
592 return text;
595 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
597 if (!plvnm) return "(null)";
598 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
599 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
600 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
601 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
604 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
606 char* buf = debug_getbuf(), *text = buf;
607 int len, size = DEBUG_BUFFER_SIZE;
609 if (lpLVItem == NULL) return "(null)";
610 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
611 if (len == -1) goto end; buf += len; size -= len;
612 if (lpLVItem->mask & LVIF_STATE)
613 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
614 else len = 0;
615 if (len == -1) goto end; buf += len; size -= len;
616 if (lpLVItem->mask & LVIF_TEXT)
617 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
618 else len = 0;
619 if (len == -1) goto end; buf += len; size -= len;
620 if (lpLVItem->mask & LVIF_IMAGE)
621 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
622 else len = 0;
623 if (len == -1) goto end; buf += len; size -= len;
624 if (lpLVItem->mask & LVIF_PARAM)
625 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
626 else len = 0;
627 if (len == -1) goto end; buf += len; size -= len;
628 if (lpLVItem->mask & LVIF_INDENT)
629 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
630 else len = 0;
631 if (len == -1) goto end; buf += len; size -= len;
632 goto undo;
633 end:
634 buf = text + strlen(text);
635 undo:
636 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
637 return text;
640 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
642 char* buf = debug_getbuf(), *text = buf;
643 int len, size = DEBUG_BUFFER_SIZE;
645 if (lpColumn == NULL) return "(null)";
646 len = snprintf(buf, size, "{");
647 if (len == -1) goto end; buf += len; size -= len;
648 if (lpColumn->mask & LVCF_SUBITEM)
649 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
650 else len = 0;
651 if (len == -1) goto end; buf += len; size -= len;
652 if (lpColumn->mask & LVCF_FMT)
653 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
654 else len = 0;
655 if (len == -1) goto end; buf += len; size -= len;
656 if (lpColumn->mask & LVCF_WIDTH)
657 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
658 else len = 0;
659 if (len == -1) goto end; buf += len; size -= len;
660 if (lpColumn->mask & LVCF_TEXT)
661 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
662 else len = 0;
663 if (len == -1) goto end; buf += len; size -= len;
664 if (lpColumn->mask & LVCF_IMAGE)
665 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
666 else len = 0;
667 if (len == -1) goto end; buf += len; size -= len;
668 if (lpColumn->mask & LVCF_ORDER)
669 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
670 else len = 0;
671 if (len == -1) goto end; buf += len; size -= len;
672 goto undo;
673 end:
674 buf = text + strlen(text);
675 undo:
676 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
677 return text;
680 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
682 if (!lpht) return "(null)";
684 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
685 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
688 /* Return the corresponding text for a given scroll value */
689 static inline LPCSTR debugscrollcode(int nScrollCode)
691 switch(nScrollCode)
693 case SB_LINELEFT: return "SB_LINELEFT";
694 case SB_LINERIGHT: return "SB_LINERIGHT";
695 case SB_PAGELEFT: return "SB_PAGELEFT";
696 case SB_PAGERIGHT: return "SB_PAGERIGHT";
697 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
698 case SB_THUMBTRACK: return "SB_THUMBTRACK";
699 case SB_ENDSCROLL: return "SB_ENDSCROLL";
700 case SB_INTERNAL: return "SB_INTERNAL";
701 default: return "unknown";
706 /******** Notification functions ************************************/
708 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
710 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
711 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
714 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
716 LRESULT result;
718 TRACE("(code=%d)\n", code);
720 pnmh->hwndFrom = infoPtr->hwndSelf;
721 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
722 pnmh->code = code;
723 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
725 TRACE(" <= %ld\n", result);
727 return result;
730 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
732 NMHDR nmh;
733 HWND hwnd = infoPtr->hwndSelf;
734 notify_hdr(infoPtr, code, &nmh);
735 return IsWindow(hwnd);
738 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
740 NMITEMACTIVATE nmia;
741 LVITEMW item;
743 if (htInfo) {
744 nmia.uNewState = 0;
745 nmia.uOldState = 0;
746 nmia.uChanged = 0;
747 nmia.uKeyFlags = 0;
749 item.mask = LVIF_PARAM|LVIF_STATE;
750 item.iItem = htInfo->iItem;
751 item.iSubItem = 0;
752 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
753 nmia.lParam = item.lParam;
754 nmia.uOldState = item.state;
755 nmia.uNewState = item.state | LVIS_ACTIVATING;
756 nmia.uChanged = LVIF_STATE;
759 nmia.iItem = htInfo->iItem;
760 nmia.iSubItem = htInfo->iSubItem;
761 nmia.ptAction = htInfo->pt;
763 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
764 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
765 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
767 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
770 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
772 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
773 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
776 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
778 NMITEMACTIVATE nmia;
779 LVITEMW item;
780 HWND hwnd = infoPtr->hwndSelf;
782 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
783 ZeroMemory(&nmia, sizeof(nmia));
784 nmia.iItem = lvht->iItem;
785 nmia.iSubItem = lvht->iSubItem;
786 nmia.ptAction = lvht->pt;
787 item.mask = LVIF_PARAM;
788 item.iItem = lvht->iItem;
789 item.iSubItem = 0;
790 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
791 notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
792 return IsWindow(hwnd);
795 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
797 NMLISTVIEW nmlv;
798 LVITEMW item;
799 HWND hwnd = infoPtr->hwndSelf;
801 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
802 nmlv.iItem = nItem;
803 item.mask = LVIF_PARAM;
804 item.iItem = nItem;
805 item.iSubItem = 0;
806 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
807 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
808 return IsWindow(hwnd);
811 static int get_ansi_notification(UINT unicodeNotificationCode)
813 switch (unicodeNotificationCode)
815 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
816 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
817 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
818 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
819 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
820 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
822 ERR("unknown notification %x\n", unicodeNotificationCode);
823 assert(FALSE);
824 return 0;
828 Send notification. depends on dispinfoW having same
829 structure as dispinfoA.
830 infoPtr : listview struct
831 notificationCode : *Unicode* notification code
832 pdi : dispinfo structure (can be unicode or ansi)
833 isW : TRUE if dispinfo is Unicode
835 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
837 BOOL bResult = FALSE;
838 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
839 INT cchTempBufMax = 0, savCchTextMax = 0;
840 UINT realNotifCode;
841 LPWSTR pszTempBuf = NULL, savPszText = NULL;
843 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
845 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
846 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
849 if (convertToAnsi || convertToUnicode)
851 if (notificationCode != LVN_GETDISPINFOW)
853 cchTempBufMax = convertToUnicode ?
854 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
855 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
857 else
859 cchTempBufMax = pdi->item.cchTextMax;
860 *pdi->item.pszText = 0; /* make sure we don't process garbage */
863 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
864 if (!pszTempBuf) return FALSE;
866 if (convertToUnicode)
867 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
868 pszTempBuf, cchTempBufMax);
869 else
870 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
871 cchTempBufMax, NULL, NULL);
873 savCchTextMax = pdi->item.cchTextMax;
874 savPszText = pdi->item.pszText;
875 pdi->item.pszText = pszTempBuf;
876 pdi->item.cchTextMax = cchTempBufMax;
879 if (infoPtr->notifyFormat == NFR_ANSI)
880 realNotifCode = get_ansi_notification(notificationCode);
881 else
882 realNotifCode = notificationCode;
883 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
884 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
886 if (convertToUnicode || convertToAnsi)
888 if (convertToUnicode) /* note : pointer can be changed by app ! */
889 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
890 savCchTextMax, NULL, NULL);
891 else
892 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
893 savPszText, savCchTextMax);
894 pdi->item.pszText = savPszText; /* restores our buffer */
895 pdi->item.cchTextMax = savCchTextMax;
896 Free (pszTempBuf);
898 return bResult;
901 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
902 const RECT *rcBounds, const LVITEMW *lplvItem)
904 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
905 lpnmlvcd->nmcd.hdc = hdc;
906 lpnmlvcd->nmcd.rc = *rcBounds;
907 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
908 lpnmlvcd->clrText = infoPtr->clrText;
909 if (!lplvItem) return;
910 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
911 lpnmlvcd->iSubItem = lplvItem->iSubItem;
912 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
913 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
914 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
915 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
918 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
920 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
921 DWORD result;
923 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
924 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
925 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
926 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
927 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
928 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
929 return result;
932 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
934 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
935 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
936 if (lpnmlvcd->clrText == CLR_DEFAULT)
937 lpnmlvcd->clrText = comctl32_color.clrWindowText;
939 /* apparently, for selected items, we have to override the returned values */
940 if (!SubItem)
942 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
944 if (infoPtr->bFocus)
946 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
947 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
949 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
951 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
952 lpnmlvcd->clrText = comctl32_color.clrBtnText;
957 /* Set the text attributes */
958 if (lpnmlvcd->clrTextBk != CLR_NONE)
960 SetBkMode(hdc, OPAQUE);
961 SetBkColor(hdc,lpnmlvcd->clrTextBk);
963 else
964 SetBkMode(hdc, TRANSPARENT);
965 SetTextColor(hdc, lpnmlvcd->clrText);
968 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
970 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
973 /******** Item iterator functions **********************************/
975 static RANGES ranges_create(int count);
976 static void ranges_destroy(RANGES ranges);
977 static BOOL ranges_add(RANGES ranges, RANGE range);
978 static BOOL ranges_del(RANGES ranges, RANGE range);
979 static void ranges_dump(RANGES ranges);
981 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
983 RANGE range = { nItem, nItem + 1 };
985 return ranges_add(ranges, range);
988 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
990 RANGE range = { nItem, nItem + 1 };
992 return ranges_del(ranges, range);
995 /***
996 * ITERATOR DOCUMENTATION
998 * The iterator functions allow for easy, and convenient iteration
999 * over items of interest in the list. Typically, you create a
1000 * iterator, use it, and destroy it, as such:
1001 * ITERATOR i;
1003 * iterator_xxxitems(&i, ...);
1004 * while (iterator_{prev,next}(&i)
1006 * //code which uses i.nItem
1008 * iterator_destroy(&i);
1010 * where xxx is either: framed, or visible.
1011 * Note that it is important that the code destroys the iterator
1012 * after it's done with it, as the creation of the iterator may
1013 * allocate memory, which thus needs to be freed.
1015 * You can iterate both forwards, and backwards through the list,
1016 * by using iterator_next or iterator_prev respectively.
1018 * Lower numbered items are draw on top of higher number items in
1019 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1020 * items may overlap). So, to test items, you should use
1021 * iterator_next
1022 * which lists the items top to bottom (in Z-order).
1023 * For drawing items, you should use
1024 * iterator_prev
1025 * which lists the items bottom to top (in Z-order).
1026 * If you keep iterating over the items after the end-of-items
1027 * marker (-1) is returned, the iterator will start from the
1028 * beginning. Typically, you don't need to test for -1,
1029 * because iterator_{next,prev} will return TRUE if more items
1030 * are to be iterated over, or FALSE otherwise.
1032 * Note: the iterator is defined to be bidirectional. That is,
1033 * any number of prev followed by any number of next, or
1034 * five versa, should leave the iterator at the same item:
1035 * prev * n, next * n = next * n, prev * n
1037 * The iterator has a notion of an out-of-order, special item,
1038 * which sits at the start of the list. This is used in
1039 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1040 * which needs to be first, as it may overlap other items.
1042 * The code is a bit messy because we have:
1043 * - a special item to deal with
1044 * - simple range, or composite range
1045 * - empty range.
1046 * If you find bugs, or want to add features, please make sure you
1047 * always check/modify *both* iterator_prev, and iterator_next.
1050 /****
1051 * This function iterates through the items in increasing order,
1052 * but prefixed by the special item, then -1. That is:
1053 * special, 1, 2, 3, ..., n, -1.
1054 * Each item is listed only once.
1056 static inline BOOL iterator_next(ITERATOR* i)
1058 if (i->nItem == -1)
1060 i->nItem = i->nSpecial;
1061 if (i->nItem != -1) return TRUE;
1063 if (i->nItem == i->nSpecial)
1065 if (i->ranges) i->index = 0;
1066 goto pickarange;
1069 i->nItem++;
1070 testitem:
1071 if (i->nItem == i->nSpecial) i->nItem++;
1072 if (i->nItem < i->range.upper) return TRUE;
1074 pickarange:
1075 if (i->ranges)
1077 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1078 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1079 else goto end;
1081 else if (i->nItem >= i->range.upper) goto end;
1083 i->nItem = i->range.lower;
1084 if (i->nItem >= 0) goto testitem;
1085 end:
1086 i->nItem = -1;
1087 return FALSE;
1090 /****
1091 * This function iterates through the items in decreasing order,
1092 * followed by the special item, then -1. That is:
1093 * n, n-1, ..., 3, 2, 1, special, -1.
1094 * Each item is listed only once.
1096 static inline BOOL iterator_prev(ITERATOR* i)
1098 BOOL start = FALSE;
1100 if (i->nItem == -1)
1102 start = TRUE;
1103 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1104 goto pickarange;
1106 if (i->nItem == i->nSpecial)
1108 i->nItem = -1;
1109 return FALSE;
1112 testitem:
1113 i->nItem--;
1114 if (i->nItem == i->nSpecial) i->nItem--;
1115 if (i->nItem >= i->range.lower) return TRUE;
1117 pickarange:
1118 if (i->ranges)
1120 if (i->index > 0)
1121 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1122 else goto end;
1124 else if (!start && i->nItem < i->range.lower) goto end;
1126 i->nItem = i->range.upper;
1127 if (i->nItem > 0) goto testitem;
1128 end:
1129 return (i->nItem = i->nSpecial) != -1;
1132 static RANGE iterator_range(const ITERATOR *i)
1134 RANGE range;
1136 if (!i->ranges) return i->range;
1138 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1140 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1141 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1143 else range.lower = range.upper = 0;
1145 return range;
1148 /***
1149 * Releases resources associated with this ierator.
1151 static inline void iterator_destroy(const ITERATOR *i)
1153 ranges_destroy(i->ranges);
1156 /***
1157 * Create an empty iterator.
1159 static inline BOOL iterator_empty(ITERATOR* i)
1161 ZeroMemory(i, sizeof(*i));
1162 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1163 return TRUE;
1166 /***
1167 * Create an iterator over a range.
1169 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1171 iterator_empty(i);
1172 i->range = range;
1173 return TRUE;
1176 /***
1177 * Create an iterator over a bunch of ranges.
1178 * Please note that the iterator will take ownership of the ranges,
1179 * and will free them upon destruction.
1181 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1183 iterator_empty(i);
1184 i->ranges = ranges;
1185 return TRUE;
1188 /***
1189 * Creates an iterator over the items which intersect lprc.
1191 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1193 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1194 RECT frame = *lprc, rcItem, rcTemp;
1195 POINT Origin;
1197 /* in case we fail, we want to return an empty iterator */
1198 if (!iterator_empty(i)) return FALSE;
1200 LISTVIEW_GetOrigin(infoPtr, &Origin);
1202 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1203 OffsetRect(&frame, -Origin.x, -Origin.y);
1205 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1207 INT nItem;
1209 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1211 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1212 if (IntersectRect(&rcTemp, &rcItem, lprc))
1213 i->nSpecial = infoPtr->nFocusedItem;
1215 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1216 /* to do better here, we need to have PosX, and PosY sorted */
1217 TRACE("building icon ranges:\n");
1218 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1220 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1221 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1222 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1223 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1224 if (IntersectRect(&rcTemp, &rcItem, &frame))
1225 ranges_additem(i->ranges, nItem);
1227 return TRUE;
1229 else if (uView == LVS_REPORT)
1231 RANGE range;
1233 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1234 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1236 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1237 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1238 if (range.upper <= range.lower) return TRUE;
1239 if (!iterator_rangeitems(i, range)) return FALSE;
1240 TRACE(" report=%s\n", debugrange(&i->range));
1242 else
1244 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1245 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1246 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1247 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1248 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1249 INT lower = nFirstCol * nPerCol + nFirstRow;
1250 RANGE item_range;
1251 INT nCol;
1253 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1254 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1256 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1258 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1259 TRACE("building list ranges:\n");
1260 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1262 item_range.lower = nCol * nPerCol + nFirstRow;
1263 if(item_range.lower >= infoPtr->nItemCount) break;
1264 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1265 TRACE(" list=%s\n", debugrange(&item_range));
1266 ranges_add(i->ranges, item_range);
1270 return TRUE;
1273 /***
1274 * Creates an iterator over the items which intersect the visible region of hdc.
1276 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1278 POINT Origin, Position;
1279 RECT rcItem, rcClip;
1280 INT rgntype;
1282 rgntype = GetClipBox(hdc, &rcClip);
1283 if (rgntype == NULLREGION) return iterator_empty(i);
1284 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1285 if (rgntype == SIMPLEREGION) return TRUE;
1287 /* first deal with the special item */
1288 if (i->nSpecial != -1)
1290 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1291 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1294 /* if we can't deal with the region, we'll just go with the simple range */
1295 LISTVIEW_GetOrigin(infoPtr, &Origin);
1296 TRACE("building visible range:\n");
1297 if (!i->ranges && i->range.lower < i->range.upper)
1299 if (!(i->ranges = ranges_create(50))) return TRUE;
1300 if (!ranges_add(i->ranges, i->range))
1302 ranges_destroy(i->ranges);
1303 i->ranges = 0;
1304 return TRUE;
1308 /* now delete the invisible items from the list */
1309 while(iterator_next(i))
1311 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1312 rcItem.left = Position.x + Origin.x;
1313 rcItem.top = Position.y + Origin.y;
1314 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1315 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1316 if (!RectVisible(hdc, &rcItem))
1317 ranges_delitem(i->ranges, i->nItem);
1319 /* the iterator should restart on the next iterator_next */
1320 TRACE("done\n");
1322 return TRUE;
1325 /******** Misc helper functions ************************************/
1327 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1328 WPARAM wParam, LPARAM lParam, BOOL isW)
1330 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1331 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1334 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1336 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1338 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1339 (uView == LVS_ICON || uView == LVS_SMALLICON);
1342 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1344 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1345 if(state == 1 || state == 2)
1347 LVITEMW lvitem;
1348 state ^= 3;
1349 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1350 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1351 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1355 /******** Internal API functions ************************************/
1357 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1359 static COLUMN_INFO mainItem;
1361 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1362 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1363 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1366 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1368 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1369 HINSTANCE hInst;
1371 if (infoPtr->hwndHeader) return 0;
1373 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1375 /* setup creation flags */
1376 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1377 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1379 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1381 /* create header */
1382 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1383 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1384 if (!infoPtr->hwndHeader) return -1;
1386 /* set header unicode format */
1387 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1389 /* set header font */
1390 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1392 LISTVIEW_UpdateSize(infoPtr);
1394 return 0;
1397 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1399 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1402 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1404 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1407 /* used to handle collapse main item column case */
1408 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1410 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1411 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1414 /* Listview invalidation functions: use _only_ these functions to invalidate */
1416 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1418 return infoPtr->bRedraw;
1421 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1423 if(!is_redrawing(infoPtr)) return;
1424 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1425 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1428 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1430 RECT rcBox;
1432 if(!is_redrawing(infoPtr)) return;
1433 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1434 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1437 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1439 POINT Origin, Position;
1440 RECT rcBox;
1442 if(!is_redrawing(infoPtr)) return;
1443 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1444 LISTVIEW_GetOrigin(infoPtr, &Origin);
1445 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1446 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1447 rcBox.top = 0;
1448 rcBox.bottom = infoPtr->nItemHeight;
1449 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1450 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1453 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1455 LISTVIEW_InvalidateRect(infoPtr, NULL);
1458 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1460 RECT rcCol;
1462 if(!is_redrawing(infoPtr)) return;
1463 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1464 rcCol.top = infoPtr->rcList.top;
1465 rcCol.bottom = infoPtr->rcList.bottom;
1466 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1469 /***
1470 * DESCRIPTION:
1471 * Retrieves the number of items that can fit vertically in the client area.
1473 * PARAMETER(S):
1474 * [I] infoPtr : valid pointer to the listview structure
1476 * RETURN:
1477 * Number of items per row.
1479 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1481 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1483 return max(nListWidth/infoPtr->nItemWidth, 1);
1486 /***
1487 * DESCRIPTION:
1488 * Retrieves the number of items that can fit horizontally in the client
1489 * area.
1491 * PARAMETER(S):
1492 * [I] infoPtr : valid pointer to the listview structure
1494 * RETURN:
1495 * Number of items per column.
1497 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1499 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1501 return max(nListHeight / infoPtr->nItemHeight, 1);
1505 /*************************************************************************
1506 * LISTVIEW_ProcessLetterKeys
1508 * Processes keyboard messages generated by pressing the letter keys
1509 * on the keyboard.
1510 * What this does is perform a case insensitive search from the
1511 * current position with the following quirks:
1512 * - If two chars or more are pressed in quick succession we search
1513 * for the corresponding string (e.g. 'abc').
1514 * - If there is a delay we wipe away the current search string and
1515 * restart with just that char.
1516 * - If the user keeps pressing the same character, whether slowly or
1517 * fast, so that the search string is entirely composed of this
1518 * character ('aaaaa' for instance), then we search for first item
1519 * that starting with that character.
1520 * - If the user types the above character in quick succession, then
1521 * we must also search for the corresponding string ('aaaaa'), and
1522 * go to that string if there is a match.
1524 * PARAMETERS
1525 * [I] hwnd : handle to the window
1526 * [I] charCode : the character code, the actual character
1527 * [I] keyData : key data
1529 * RETURNS
1531 * Zero.
1533 * BUGS
1535 * - The current implementation has a list of characters it will
1536 * accept and it ignores everything else. In particular it will
1537 * ignore accentuated characters which seems to match what
1538 * Windows does. But I'm not sure it makes sense to follow
1539 * Windows there.
1540 * - We don't sound a beep when the search fails.
1542 * SEE ALSO
1544 * TREEVIEW_ProcessLetterKeys
1546 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1548 INT nItem;
1549 INT endidx,idx;
1550 LVITEMW item;
1551 WCHAR buffer[MAX_PATH];
1552 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1554 /* simple parameter checking */
1555 if (!charCode || !keyData) return 0;
1557 /* only allow the valid WM_CHARs through */
1558 if (!isalnumW(charCode) &&
1559 charCode != '.' && charCode != '`' && charCode != '!' &&
1560 charCode != '@' && charCode != '#' && charCode != '$' &&
1561 charCode != '%' && charCode != '^' && charCode != '&' &&
1562 charCode != '*' && charCode != '(' && charCode != ')' &&
1563 charCode != '-' && charCode != '_' && charCode != '+' &&
1564 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1565 charCode != '}' && charCode != '[' && charCode != '{' &&
1566 charCode != '/' && charCode != '?' && charCode != '>' &&
1567 charCode != '<' && charCode != ',' && charCode != '~')
1568 return 0;
1570 /* if there's one item or less, there is no where to go */
1571 if (infoPtr->nItemCount <= 1) return 0;
1573 /* update the search parameters */
1574 infoPtr->lastKeyPressTimestamp = GetTickCount();
1575 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1576 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1577 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1578 if (infoPtr->charCode != charCode)
1579 infoPtr->charCode = charCode = 0;
1580 } else {
1581 infoPtr->charCode=charCode;
1582 infoPtr->szSearchParam[0]=charCode;
1583 infoPtr->nSearchParamLength=1;
1584 /* Redundant with the 1 char string */
1585 charCode=0;
1588 /* and search from the current position */
1589 nItem=-1;
1590 if (infoPtr->nFocusedItem >= 0) {
1591 endidx=infoPtr->nFocusedItem;
1592 idx=endidx;
1593 /* if looking for single character match,
1594 * then we must always move forward
1596 if (infoPtr->nSearchParamLength == 1)
1597 idx++;
1598 } else {
1599 endidx=infoPtr->nItemCount;
1600 idx=0;
1603 /* Let application handle this for virtual listview */
1604 if (infoPtr->dwStyle & LVS_OWNERDATA)
1606 NMLVFINDITEMW nmlv;
1607 LVFINDINFOW lvfi;
1609 ZeroMemory(&lvfi, sizeof(lvfi));
1610 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1611 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1612 lvfi.psz = infoPtr->szSearchParam;
1613 nmlv.iStart = idx;
1614 nmlv.lvfi = lvfi;
1616 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1618 if (nItem != -1)
1619 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1621 return 0;
1624 do {
1625 if (idx == infoPtr->nItemCount) {
1626 if (endidx == infoPtr->nItemCount || endidx == 0)
1627 break;
1628 idx=0;
1631 /* get item */
1632 item.mask = LVIF_TEXT;
1633 item.iItem = idx;
1634 item.iSubItem = 0;
1635 item.pszText = buffer;
1636 item.cchTextMax = MAX_PATH;
1637 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1639 /* check for a match */
1640 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1641 nItem=idx;
1642 break;
1643 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1644 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1645 /* This would work but we must keep looking for a longer match */
1646 nItem=idx;
1648 idx++;
1649 } while (idx != endidx);
1651 if (nItem != -1)
1652 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1654 return 0;
1657 /*************************************************************************
1658 * LISTVIEW_UpdateHeaderSize [Internal]
1660 * Function to resize the header control
1662 * PARAMS
1663 * [I] hwnd : handle to a window
1664 * [I] nNewScrollPos : scroll pos to set
1666 * RETURNS
1667 * None.
1669 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1671 RECT winRect;
1672 POINT point[2];
1674 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1676 if (!infoPtr->hwndHeader) return;
1678 GetWindowRect(infoPtr->hwndHeader, &winRect);
1679 point[0].x = winRect.left;
1680 point[0].y = winRect.top;
1681 point[1].x = winRect.right;
1682 point[1].y = winRect.bottom;
1684 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1685 point[0].x = -nNewScrollPos;
1686 point[1].x += nNewScrollPos;
1688 SetWindowPos(infoPtr->hwndHeader,0,
1689 point[0].x,point[0].y,point[1].x,point[1].y,
1690 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1691 SWP_NOZORDER | SWP_NOACTIVATE);
1694 /***
1695 * DESCRIPTION:
1696 * Update the scrollbars. This functions should be called whenever
1697 * the content, size or view changes.
1699 * PARAMETER(S):
1700 * [I] infoPtr : valid pointer to the listview structure
1702 * RETURN:
1703 * None
1705 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1707 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1708 SCROLLINFO horzInfo, vertInfo;
1709 INT dx, dy;
1711 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1713 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1714 horzInfo.cbSize = sizeof(SCROLLINFO);
1715 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1717 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1718 if (uView == LVS_LIST)
1720 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1721 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1723 /* scroll by at least one column per page */
1724 if(horzInfo.nPage < infoPtr->nItemWidth)
1725 horzInfo.nPage = infoPtr->nItemWidth;
1727 horzInfo.nPage /= infoPtr->nItemWidth;
1729 else if (uView == LVS_REPORT)
1731 horzInfo.nMax = infoPtr->nItemWidth;
1733 else /* LVS_ICON, or LVS_SMALLICON */
1735 RECT rcView;
1737 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1740 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1741 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1742 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1743 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1744 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1746 /* Setting the horizontal scroll can change the listview size
1747 * (and potentially everything else) so we need to recompute
1748 * everything again for the vertical scroll
1751 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1752 vertInfo.cbSize = sizeof(SCROLLINFO);
1753 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1755 if (uView == LVS_REPORT)
1757 vertInfo.nMax = infoPtr->nItemCount;
1759 /* scroll by at least one page */
1760 if(vertInfo.nPage < infoPtr->nItemHeight)
1761 vertInfo.nPage = infoPtr->nItemHeight;
1763 if (infoPtr->nItemHeight > 0)
1764 vertInfo.nPage /= infoPtr->nItemHeight;
1766 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1768 RECT rcView;
1770 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1773 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1774 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1775 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1776 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1777 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1779 /* Change of the range may have changed the scroll pos. If so move the content */
1780 if (dx != 0 || dy != 0)
1782 RECT listRect;
1783 listRect = infoPtr->rcList;
1784 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1785 SW_ERASE | SW_INVALIDATE);
1788 /* Update the Header Control */
1789 if (uView == LVS_REPORT)
1791 horzInfo.fMask = SIF_POS;
1792 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1793 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1798 /***
1799 * DESCRIPTION:
1800 * Shows/hides the focus rectangle.
1802 * PARAMETER(S):
1803 * [I] infoPtr : valid pointer to the listview structure
1804 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1806 * RETURN:
1807 * None
1809 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1811 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1812 HDC hdc;
1814 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1816 if (infoPtr->nFocusedItem < 0) return;
1818 /* we need some gymnastics in ICON mode to handle large items */
1819 if (uView == LVS_ICON)
1821 RECT rcBox;
1823 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1824 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1826 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1827 return;
1831 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1833 /* for some reason, owner draw should work only in report mode */
1834 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1836 DRAWITEMSTRUCT dis;
1837 LVITEMW item;
1839 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1840 HFONT hOldFont = SelectObject(hdc, hFont);
1842 item.iItem = infoPtr->nFocusedItem;
1843 item.iSubItem = 0;
1844 item.mask = LVIF_PARAM;
1845 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1847 ZeroMemory(&dis, sizeof(dis));
1848 dis.CtlType = ODT_LISTVIEW;
1849 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1850 dis.itemID = item.iItem;
1851 dis.itemAction = ODA_FOCUS;
1852 if (fShow) dis.itemState |= ODS_FOCUS;
1853 dis.hwndItem = infoPtr->hwndSelf;
1854 dis.hDC = hdc;
1855 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1856 dis.itemData = item.lParam;
1858 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1860 SelectObject(hdc, hOldFont);
1862 else
1864 LISTVIEW_DrawFocusRect(infoPtr, hdc);
1866 done:
1867 ReleaseDC(infoPtr->hwndSelf, hdc);
1870 /***
1871 * Invalidates all visible selected items.
1873 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1875 ITERATOR i;
1877 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1878 while(iterator_next(&i))
1880 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1881 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1883 iterator_destroy(&i);
1887 /***
1888 * DESCRIPTION: [INTERNAL]
1889 * Computes an item's (left,top) corner, relative to rcView.
1890 * That is, the position has NOT been made relative to the Origin.
1891 * This is deliberate, to avoid computing the Origin over, and
1892 * over again, when this function is called in a loop. Instead,
1893 * one can factor the computation of the Origin before the loop,
1894 * and offset the value returned by this function, on every iteration.
1896 * PARAMETER(S):
1897 * [I] infoPtr : valid pointer to the listview structure
1898 * [I] nItem : item number
1899 * [O] lpptOrig : item top, left corner
1901 * RETURN:
1902 * None.
1904 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1906 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1908 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1910 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1912 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1913 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1915 else if (uView == LVS_LIST)
1917 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1918 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1919 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1921 else /* LVS_REPORT */
1923 lpptPosition->x = REPORT_MARGINX;
1924 /* item is always at zero indexed column */
1925 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
1926 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
1927 lpptPosition->y = nItem * infoPtr->nItemHeight;
1931 /***
1932 * DESCRIPTION: [INTERNAL]
1933 * Compute the rectangles of an item. This is to localize all
1934 * the computations in one place. If you are not interested in some
1935 * of these values, simply pass in a NULL -- the function is smart
1936 * enough to compute only what's necessary. The function computes
1937 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1938 * one, the BOX rectangle. This rectangle is very cheap to compute,
1939 * and is guaranteed to contain all the other rectangles. Computing
1940 * the ICON rect is also cheap, but all the others are potentially
1941 * expensive. This gives an easy and effective optimization when
1942 * searching (like point inclusion, or rectangle intersection):
1943 * first test against the BOX, and if TRUE, test against the desired
1944 * rectangle.
1945 * If the function does not have all the necessary information
1946 * to computed the requested rectangles, will crash with a
1947 * failed assertion. This is done so we catch all programming
1948 * errors, given that the function is called only from our code.
1950 * We have the following 'special' meanings for a few fields:
1951 * * If LVIS_FOCUSED is set, we assume the item has the focus
1952 * This is important in ICON mode, where it might get a larger
1953 * then usual rectangle
1955 * Please note that subitem support works only in REPORT mode.
1957 * PARAMETER(S):
1958 * [I] infoPtr : valid pointer to the listview structure
1959 * [I] lpLVItem : item to compute the measures for
1960 * [O] lprcBox : ptr to Box rectangle
1961 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1962 * [0] lprcSelectBox : ptr to select box rectangle
1963 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1964 * [O] lprcIcon : ptr to Icon rectangle
1965 * Same as LVM_GETITEMRECT with LVIR_ICON
1966 * [O] lprcStateIcon: ptr to State Icon rectangle
1967 * [O] lprcLabel : ptr to Label rectangle
1968 * Same as LVM_GETITEMRECT with LVIR_LABEL
1970 * RETURN:
1971 * None.
1973 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1974 LPRECT lprcBox, LPRECT lprcSelectBox,
1975 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1977 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1978 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1979 RECT Box, SelectBox, Icon, Label;
1980 COLUMN_INFO *lpColumnInfo = NULL;
1981 SIZE labelSize = { 0, 0 };
1983 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1985 /* Be smart and try to figure out the minimum we have to do */
1986 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1987 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1989 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1990 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1992 if (lprcSelectBox) doSelectBox = TRUE;
1993 if (lprcLabel) doLabel = TRUE;
1994 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1995 if (doSelectBox)
1997 doIcon = TRUE;
1998 doLabel = TRUE;
2001 /************************************************************/
2002 /* compute the box rectangle (it should be cheap to do) */
2003 /************************************************************/
2004 if (lpLVItem->iSubItem || uView == LVS_REPORT)
2005 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2007 if (lpLVItem->iSubItem)
2009 Box = lpColumnInfo->rcHeader;
2011 else
2013 Box.left = 0;
2014 Box.right = infoPtr->nItemWidth;
2016 Box.top = 0;
2017 Box.bottom = infoPtr->nItemHeight;
2019 /******************************************************************/
2020 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2021 /******************************************************************/
2022 if (doIcon)
2024 LONG state_width = 0;
2026 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2027 state_width = infoPtr->iconStateSize.cx;
2029 if (uView == LVS_ICON)
2031 Icon.left = Box.left + state_width;
2032 if (infoPtr->himlNormal)
2033 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2034 Icon.top = Box.top + ICON_TOP_PADDING;
2035 Icon.right = Icon.left;
2036 Icon.bottom = Icon.top;
2037 if (infoPtr->himlNormal)
2039 Icon.right += infoPtr->iconSize.cx;
2040 Icon.bottom += infoPtr->iconSize.cy;
2043 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2045 Icon.left = Box.left + state_width;
2047 if (uView == LVS_REPORT && lpLVItem->iSubItem == 0)
2048 Icon.left += REPORT_MARGINX;
2050 Icon.top = Box.top;
2051 Icon.right = Icon.left;
2052 if (infoPtr->himlSmall &&
2053 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2054 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2055 Icon.right += infoPtr->iconSize.cx;
2056 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2058 if(lprcIcon) *lprcIcon = Icon;
2059 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2061 /* TODO: is this correct? */
2062 if (lprcStateIcon)
2064 lprcStateIcon->left = Icon.left - state_width;
2065 lprcStateIcon->right = Icon.left;
2066 lprcStateIcon->top = Icon.top;
2067 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2068 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2071 else Icon.right = 0;
2073 /************************************************************/
2074 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2075 /************************************************************/
2076 if (doLabel)
2078 /* calculate how far to the right can the label stretch */
2079 Label.right = Box.right;
2080 if (uView == LVS_REPORT)
2082 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2085 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2087 labelSize.cx = infoPtr->nItemWidth;
2088 labelSize.cy = infoPtr->nItemHeight;
2089 goto calc_label;
2092 /* we need the text in non owner draw mode */
2093 assert(lpLVItem->mask & LVIF_TEXT);
2094 if (is_textT(lpLVItem->pszText, TRUE))
2096 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2097 HDC hdc = GetDC(infoPtr->hwndSelf);
2098 HFONT hOldFont = SelectObject(hdc, hFont);
2099 UINT uFormat;
2100 RECT rcText;
2102 /* compute rough rectangle where the label will go */
2103 SetRectEmpty(&rcText);
2104 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2105 rcText.bottom = infoPtr->nItemHeight;
2106 if (uView == LVS_ICON)
2107 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2109 /* now figure out the flags */
2110 if (uView == LVS_ICON)
2111 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2112 else
2113 uFormat = LV_SL_DT_FLAGS;
2115 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2117 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2118 labelSize.cy = rcText.bottom - rcText.top;
2120 SelectObject(hdc, hOldFont);
2121 ReleaseDC(infoPtr->hwndSelf, hdc);
2124 calc_label:
2125 if (uView == LVS_ICON)
2127 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2128 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2129 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2130 Label.right = Label.left + labelSize.cx;
2131 Label.bottom = Label.top + infoPtr->nItemHeight;
2132 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2134 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2135 labelSize.cy /= infoPtr->ntmHeight;
2136 labelSize.cy = max(labelSize.cy, 1);
2137 labelSize.cy *= infoPtr->ntmHeight;
2139 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2141 else if (uView == LVS_REPORT)
2143 Label.left = Icon.right;
2144 Label.top = Box.top;
2145 Label.right = lpColumnInfo->rcHeader.right;
2146 Label.bottom = Label.top + infoPtr->nItemHeight;
2148 else /* LVS_SMALLICON or LVS_LIST */
2150 Label.left = Icon.right;
2151 Label.top = Box.top;
2152 Label.right = min(Label.left + labelSize.cx, Label.right);
2153 Label.bottom = Label.top + infoPtr->nItemHeight;
2156 if (lprcLabel) *lprcLabel = Label;
2157 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2160 /************************************************************/
2161 /* compute STATEICON bounding box */
2162 /************************************************************/
2163 if (doSelectBox)
2165 if (uView == LVS_REPORT)
2167 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2168 SelectBox.top = Box.top;
2169 SelectBox.bottom = Box.bottom;
2170 if (lpLVItem->iSubItem == 0)
2172 /* we need the indent in report mode */
2173 assert(lpLVItem->mask & LVIF_INDENT);
2174 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2176 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2178 else
2180 UnionRect(&SelectBox, &Icon, &Label);
2182 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2183 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2186 /* Fix the Box if necessary */
2187 if (lprcBox)
2189 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2190 else *lprcBox = Box;
2192 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2195 /***
2196 * DESCRIPTION: [INTERNAL]
2198 * PARAMETER(S):
2199 * [I] infoPtr : valid pointer to the listview structure
2200 * [I] nItem : item number
2201 * [O] lprcBox : ptr to Box rectangle
2203 * RETURN:
2204 * None.
2206 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2208 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2209 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2210 POINT Position, Origin;
2211 LVITEMW lvItem;
2213 LISTVIEW_GetOrigin(infoPtr, &Origin);
2214 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2216 /* Be smart and try to figure out the minimum we have to do */
2217 lvItem.mask = 0;
2218 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2219 lvItem.mask |= LVIF_TEXT;
2220 lvItem.iItem = nItem;
2221 lvItem.iSubItem = 0;
2222 lvItem.pszText = szDispText;
2223 lvItem.cchTextMax = DISP_TEXT_SIZE;
2224 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2225 if (uView == LVS_ICON)
2227 lvItem.mask |= LVIF_STATE;
2228 lvItem.stateMask = LVIS_FOCUSED;
2229 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2231 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2233 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2237 /***
2238 * DESCRIPTION:
2239 * Returns the current icon position, and advances it along the top.
2240 * The returned position is not offset by Origin.
2242 * PARAMETER(S):
2243 * [I] infoPtr : valid pointer to the listview structure
2244 * [O] lpPos : will get the current icon position
2246 * RETURN:
2247 * None
2249 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2251 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2253 *lpPos = infoPtr->currIconPos;
2255 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2256 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2258 infoPtr->currIconPos.x = 0;
2259 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2263 /***
2264 * DESCRIPTION:
2265 * Returns the current icon position, and advances it down the left edge.
2266 * The returned position is not offset by Origin.
2268 * PARAMETER(S):
2269 * [I] infoPtr : valid pointer to the listview structure
2270 * [O] lpPos : will get the current icon position
2272 * RETURN:
2273 * None
2275 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2277 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2279 *lpPos = infoPtr->currIconPos;
2281 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2282 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2284 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2285 infoPtr->currIconPos.y = 0;
2289 /***
2290 * DESCRIPTION:
2291 * Moves an icon to the specified position.
2292 * It takes care of invalidating the item, etc.
2294 * PARAMETER(S):
2295 * [I] infoPtr : valid pointer to the listview structure
2296 * [I] nItem : the item to move
2297 * [I] lpPos : the new icon position
2298 * [I] isNew : flags the item as being new
2300 * RETURN:
2301 * Success: TRUE
2302 * Failure: FALSE
2304 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2306 POINT old;
2308 if (!isNew)
2310 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2311 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2313 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2314 LISTVIEW_InvalidateItem(infoPtr, nItem);
2317 /* Allocating a POINTER for every item is too resource intensive,
2318 * so we'll keep the (x,y) in different arrays */
2319 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2320 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2322 LISTVIEW_InvalidateItem(infoPtr, nItem);
2324 return TRUE;
2327 /***
2328 * DESCRIPTION:
2329 * Arranges listview items in icon display mode.
2331 * PARAMETER(S):
2332 * [I] infoPtr : valid pointer to the listview structure
2333 * [I] nAlignCode : alignment code
2335 * RETURN:
2336 * SUCCESS : TRUE
2337 * FAILURE : FALSE
2339 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2341 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2342 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2343 POINT pos;
2344 INT i;
2346 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2348 TRACE("nAlignCode=%d\n", nAlignCode);
2350 if (nAlignCode == LVA_DEFAULT)
2352 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2353 else nAlignCode = LVA_ALIGNTOP;
2356 switch (nAlignCode)
2358 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2359 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2360 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2361 default: return FALSE;
2364 infoPtr->bAutoarrange = TRUE;
2365 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2366 for (i = 0; i < infoPtr->nItemCount; i++)
2368 next_pos(infoPtr, &pos);
2369 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2372 return TRUE;
2375 /***
2376 * DESCRIPTION:
2377 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2378 * For LVS_REPORT always returns empty rectangle.
2380 * PARAMETER(S):
2381 * [I] infoPtr : valid pointer to the listview structure
2382 * [O] lprcView : bounding rectangle
2384 * RETURN:
2385 * SUCCESS : TRUE
2386 * FAILURE : FALSE
2388 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2390 INT i, x, y;
2392 SetRectEmpty(lprcView);
2394 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2396 case LVS_ICON:
2397 case LVS_SMALLICON:
2398 for (i = 0; i < infoPtr->nItemCount; i++)
2400 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2401 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2402 lprcView->right = max(lprcView->right, x);
2403 lprcView->bottom = max(lprcView->bottom, y);
2405 if (infoPtr->nItemCount > 0)
2407 lprcView->right += infoPtr->nItemWidth;
2408 lprcView->bottom += infoPtr->nItemHeight;
2410 break;
2412 case LVS_LIST:
2413 y = LISTVIEW_GetCountPerColumn(infoPtr);
2414 x = infoPtr->nItemCount / y;
2415 if (infoPtr->nItemCount % y) x++;
2416 lprcView->right = x * infoPtr->nItemWidth;
2417 lprcView->bottom = y * infoPtr->nItemHeight;
2418 break;
2422 /***
2423 * DESCRIPTION:
2424 * Retrieves the bounding rectangle of all the items.
2426 * PARAMETER(S):
2427 * [I] infoPtr : valid pointer to the listview structure
2428 * [O] lprcView : bounding rectangle
2430 * RETURN:
2431 * SUCCESS : TRUE
2432 * FAILURE : FALSE
2434 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2436 POINT ptOrigin;
2438 TRACE("(lprcView=%p)\n", lprcView);
2440 if (!lprcView) return FALSE;
2442 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2444 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT)
2446 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2447 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2450 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2452 return TRUE;
2455 /***
2456 * DESCRIPTION:
2457 * Retrieves the subitem pointer associated with the subitem index.
2459 * PARAMETER(S):
2460 * [I] hdpaSubItems : DPA handle for a specific item
2461 * [I] nSubItem : index of subitem
2463 * RETURN:
2464 * SUCCESS : subitem pointer
2465 * FAILURE : NULL
2467 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2469 SUBITEM_INFO *lpSubItem;
2470 INT i;
2472 /* we should binary search here if need be */
2473 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2475 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2476 if (lpSubItem->iSubItem == nSubItem)
2477 return lpSubItem;
2480 return NULL;
2484 /***
2485 * DESCRIPTION:
2486 * Calculates the desired item width.
2488 * PARAMETER(S):
2489 * [I] infoPtr : valid pointer to the listview structure
2491 * RETURN:
2492 * The desired item width.
2494 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2496 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2497 INT nItemWidth = 0;
2499 TRACE("uView=%d\n", uView);
2501 if (uView == LVS_ICON)
2502 nItemWidth = infoPtr->iconSpacing.cx;
2503 else if (uView == LVS_REPORT)
2505 RECT rcHeader;
2507 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2509 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2510 nItemWidth = rcHeader.right;
2513 else /* LVS_SMALLICON, or LVS_LIST */
2515 INT i;
2517 for (i = 0; i < infoPtr->nItemCount; i++)
2518 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2520 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2521 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2523 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2526 return max(nItemWidth, 1);
2529 /***
2530 * DESCRIPTION:
2531 * Calculates the desired item height.
2533 * PARAMETER(S):
2534 * [I] infoPtr : valid pointer to the listview structure
2536 * RETURN:
2537 * The desired item height.
2539 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2541 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2542 INT nItemHeight;
2544 TRACE("uView=%d\n", uView);
2546 if (uView == LVS_ICON)
2547 nItemHeight = infoPtr->iconSpacing.cy;
2548 else
2550 nItemHeight = infoPtr->ntmHeight;
2551 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2552 nItemHeight++;
2553 if (infoPtr->himlState)
2554 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2555 if (infoPtr->himlSmall)
2556 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2557 if (infoPtr->himlState || infoPtr->himlSmall)
2558 nItemHeight += HEIGHT_PADDING;
2559 if (infoPtr->nMeasureItemHeight > 0)
2560 nItemHeight = infoPtr->nMeasureItemHeight;
2563 return max(nItemHeight, 1);
2566 /***
2567 * DESCRIPTION:
2568 * Updates the width, and height of an item.
2570 * PARAMETER(S):
2571 * [I] infoPtr : valid pointer to the listview structure
2573 * RETURN:
2574 * None.
2576 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2578 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2579 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2583 /***
2584 * DESCRIPTION:
2585 * Retrieves and saves important text metrics info for the current
2586 * Listview font.
2588 * PARAMETER(S):
2589 * [I] infoPtr : valid pointer to the listview structure
2592 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2594 HDC hdc = GetDC(infoPtr->hwndSelf);
2595 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2596 HFONT hOldFont = SelectObject(hdc, hFont);
2597 TEXTMETRICW tm;
2598 SIZE sz;
2600 if (GetTextMetricsW(hdc, &tm))
2602 infoPtr->ntmHeight = tm.tmHeight;
2603 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2606 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2607 infoPtr->nEllipsisWidth = sz.cx;
2609 SelectObject(hdc, hOldFont);
2610 ReleaseDC(infoPtr->hwndSelf, hdc);
2612 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2615 /***
2616 * DESCRIPTION:
2617 * A compare function for ranges
2619 * PARAMETER(S)
2620 * [I] range1 : pointer to range 1;
2621 * [I] range2 : pointer to range 2;
2622 * [I] flags : flags
2624 * RETURNS:
2625 * > 0 : if range 1 > range 2
2626 * < 0 : if range 2 > range 1
2627 * = 0 : if range intersects range 2
2629 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2631 INT cmp;
2633 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2634 cmp = -1;
2635 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2636 cmp = 1;
2637 else
2638 cmp = 0;
2640 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2642 return cmp;
2645 #if DEBUG_RANGES
2646 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2647 #else
2648 #define ranges_check(ranges, desc) do { } while(0)
2649 #endif
2651 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2653 INT i;
2654 RANGE *prev, *curr;
2656 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2657 assert (ranges);
2658 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2659 ranges_dump(ranges);
2660 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2662 prev = DPA_GetPtr(ranges->hdpa, 0);
2663 assert (prev->lower >= 0 && prev->lower < prev->upper);
2664 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2666 curr = DPA_GetPtr(ranges->hdpa, i);
2667 assert (prev->upper <= curr->lower);
2668 assert (curr->lower < curr->upper);
2669 prev = curr;
2672 TRACE("--- Done checking---\n");
2675 static RANGES ranges_create(int count)
2677 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2678 if (!ranges) return NULL;
2679 ranges->hdpa = DPA_Create(count);
2680 if (ranges->hdpa) return ranges;
2681 Free(ranges);
2682 return NULL;
2685 static void ranges_clear(RANGES ranges)
2687 INT i;
2689 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2690 Free(DPA_GetPtr(ranges->hdpa, i));
2691 DPA_DeleteAllPtrs(ranges->hdpa);
2695 static void ranges_destroy(RANGES ranges)
2697 if (!ranges) return;
2698 ranges_clear(ranges);
2699 DPA_Destroy(ranges->hdpa);
2700 Free(ranges);
2703 static RANGES ranges_clone(RANGES ranges)
2705 RANGES clone;
2706 INT i;
2708 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2710 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2712 RANGE *newrng = Alloc(sizeof(RANGE));
2713 if (!newrng) goto fail;
2714 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2715 DPA_SetPtr(clone->hdpa, i, newrng);
2717 return clone;
2719 fail:
2720 TRACE ("clone failed\n");
2721 ranges_destroy(clone);
2722 return NULL;
2725 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2727 INT i;
2729 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2730 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2732 return ranges;
2735 static void ranges_dump(RANGES ranges)
2737 INT i;
2739 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2740 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2743 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2745 RANGE srchrng = { nItem, nItem + 1 };
2747 TRACE("(nItem=%d)\n", nItem);
2748 ranges_check(ranges, "before contain");
2749 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2752 static INT ranges_itemcount(RANGES ranges)
2754 INT i, count = 0;
2756 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2758 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2759 count += sel->upper - sel->lower;
2762 return count;
2765 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2767 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2768 INT index;
2770 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2771 if (index == -1) return TRUE;
2773 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2775 chkrng = DPA_GetPtr(ranges->hdpa, index);
2776 if (chkrng->lower >= nItem)
2777 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2778 if (chkrng->upper > nItem)
2779 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2781 return TRUE;
2784 static BOOL ranges_add(RANGES ranges, RANGE range)
2786 RANGE srchrgn;
2787 INT index;
2789 TRACE("(%s)\n", debugrange(&range));
2790 ranges_check(ranges, "before add");
2792 /* try find overlapping regions first */
2793 srchrgn.lower = range.lower - 1;
2794 srchrgn.upper = range.upper + 1;
2795 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2797 if (index == -1)
2799 RANGE *newrgn;
2801 TRACE("Adding new range\n");
2803 /* create the brand new range to insert */
2804 newrgn = Alloc(sizeof(RANGE));
2805 if(!newrgn) goto fail;
2806 *newrgn = range;
2808 /* figure out where to insert it */
2809 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2810 TRACE("index=%d\n", index);
2811 if (index == -1) index = 0;
2813 /* and get it over with */
2814 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2816 Free(newrgn);
2817 goto fail;
2820 else
2822 RANGE *chkrgn, *mrgrgn;
2823 INT fromindex, mergeindex;
2825 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2826 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2828 chkrgn->lower = min(range.lower, chkrgn->lower);
2829 chkrgn->upper = max(range.upper, chkrgn->upper);
2831 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2833 /* merge now common ranges */
2834 fromindex = 0;
2835 srchrgn.lower = chkrgn->lower - 1;
2836 srchrgn.upper = chkrgn->upper + 1;
2840 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2841 if (mergeindex == -1) break;
2842 if (mergeindex == index)
2844 fromindex = index + 1;
2845 continue;
2848 TRACE("Merge with index %i\n", mergeindex);
2850 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2851 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2852 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2853 Free(mrgrgn);
2854 DPA_DeletePtr(ranges->hdpa, mergeindex);
2855 if (mergeindex < index) index --;
2856 } while(1);
2859 ranges_check(ranges, "after add");
2860 return TRUE;
2862 fail:
2863 ranges_check(ranges, "failed add");
2864 return FALSE;
2867 static BOOL ranges_del(RANGES ranges, RANGE range)
2869 RANGE *chkrgn;
2870 INT index;
2872 TRACE("(%s)\n", debugrange(&range));
2873 ranges_check(ranges, "before del");
2875 /* we don't use DPAS_SORTED here, since we need *
2876 * to find the first overlapping range */
2877 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2878 while(index != -1)
2880 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2882 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2884 /* case 1: Same range */
2885 if ( (chkrgn->upper == range.upper) &&
2886 (chkrgn->lower == range.lower) )
2888 DPA_DeletePtr(ranges->hdpa, index);
2889 break;
2891 /* case 2: engulf */
2892 else if ( (chkrgn->upper <= range.upper) &&
2893 (chkrgn->lower >= range.lower) )
2895 DPA_DeletePtr(ranges->hdpa, index);
2897 /* case 3: overlap upper */
2898 else if ( (chkrgn->upper <= range.upper) &&
2899 (chkrgn->lower < range.lower) )
2901 chkrgn->upper = range.lower;
2903 /* case 4: overlap lower */
2904 else if ( (chkrgn->upper > range.upper) &&
2905 (chkrgn->lower >= range.lower) )
2907 chkrgn->lower = range.upper;
2908 break;
2910 /* case 5: fully internal */
2911 else
2913 RANGE tmprgn = *chkrgn, *newrgn;
2915 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2916 newrgn->lower = chkrgn->lower;
2917 newrgn->upper = range.lower;
2918 chkrgn->lower = range.upper;
2919 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2921 Free(newrgn);
2922 goto fail;
2924 chkrgn = &tmprgn;
2925 break;
2928 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2931 ranges_check(ranges, "after del");
2932 return TRUE;
2934 fail:
2935 ranges_check(ranges, "failed del");
2936 return FALSE;
2939 /***
2940 * DESCRIPTION:
2941 * Removes all selection ranges
2943 * Parameters(s):
2944 * [I] infoPtr : valid pointer to the listview structure
2945 * [I] toSkip : item range to skip removing the selection
2947 * RETURNS:
2948 * SUCCESS : TRUE
2949 * FAILURE : FALSE
2951 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2953 LVITEMW lvItem;
2954 ITERATOR i;
2955 RANGES clone;
2957 TRACE("()\n");
2959 lvItem.state = 0;
2960 lvItem.stateMask = LVIS_SELECTED;
2962 /* need to clone the DPA because callbacks can change it */
2963 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2964 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2965 while(iterator_next(&i))
2966 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2967 /* note that the iterator destructor will free the cloned range */
2968 iterator_destroy(&i);
2970 return TRUE;
2973 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2975 RANGES toSkip;
2977 if (!(toSkip = ranges_create(1))) return FALSE;
2978 if (nItem != -1) ranges_additem(toSkip, nItem);
2979 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2980 ranges_destroy(toSkip);
2981 return TRUE;
2984 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2986 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2989 /***
2990 * DESCRIPTION:
2991 * Retrieves the number of items that are marked as selected.
2993 * PARAMETER(S):
2994 * [I] infoPtr : valid pointer to the listview structure
2996 * RETURN:
2997 * Number of items selected.
2999 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3001 INT nSelectedCount = 0;
3003 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3005 INT i;
3006 for (i = 0; i < infoPtr->nItemCount; i++)
3008 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3009 nSelectedCount++;
3012 else
3013 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3015 TRACE("nSelectedCount=%d\n", nSelectedCount);
3016 return nSelectedCount;
3019 /***
3020 * DESCRIPTION:
3021 * Manages the item focus.
3023 * PARAMETER(S):
3024 * [I] infoPtr : valid pointer to the listview structure
3025 * [I] nItem : item index
3027 * RETURN:
3028 * TRUE : focused item changed
3029 * FALSE : focused item has NOT changed
3031 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3033 INT oldFocus = infoPtr->nFocusedItem;
3034 LVITEMW lvItem;
3036 if (nItem == infoPtr->nFocusedItem) return FALSE;
3038 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3039 lvItem.stateMask = LVIS_FOCUSED;
3040 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3042 return oldFocus != infoPtr->nFocusedItem;
3045 /* Helper function for LISTVIEW_ShiftIndices *only* */
3046 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3048 if (nShiftItem < nItem) return nShiftItem;
3050 if (nShiftItem > nItem) return nShiftItem + direction;
3052 if (direction > 0) return nShiftItem + direction;
3054 return min(nShiftItem, infoPtr->nItemCount - 1);
3058 * DESCRIPTION:
3059 * Updates the various indices after an item has been inserted or deleted.
3061 * PARAMETER(S):
3062 * [I] infoPtr : valid pointer to the listview structure
3063 * [I] nItem : item index
3064 * [I] direction : Direction of shift, +1 or -1.
3066 * RETURN:
3067 * None
3069 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3071 INT nNewFocus;
3072 BOOL bOldChange;
3074 /* temporarily disable change notification while shifting items */
3075 bOldChange = infoPtr->bDoChangeNotify;
3076 infoPtr->bDoChangeNotify = FALSE;
3078 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3080 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3082 assert(abs(direction) == 1);
3084 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3086 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3087 if (nNewFocus != infoPtr->nFocusedItem)
3088 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3090 /* But we are not supposed to modify nHotItem! */
3092 infoPtr->bDoChangeNotify = bOldChange;
3097 * DESCRIPTION:
3098 * Adds a block of selections.
3100 * PARAMETER(S):
3101 * [I] infoPtr : valid pointer to the listview structure
3102 * [I] nItem : item index
3104 * RETURN:
3105 * Whether the window is still valid.
3107 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3109 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3110 INT nLast = max(infoPtr->nSelectionMark, nItem);
3111 HWND hwndSelf = infoPtr->hwndSelf;
3112 NMLVODSTATECHANGE nmlv;
3113 LVITEMW item;
3114 BOOL bOldChange;
3115 INT i;
3117 /* Temporarily disable change notification
3118 * If the control is LVS_OWNERDATA, we need to send
3119 * only one LVN_ODSTATECHANGED notification.
3120 * See MSDN documentation for LVN_ITEMCHANGED.
3122 bOldChange = infoPtr->bDoChangeNotify;
3123 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3125 if (nFirst == -1) nFirst = nItem;
3127 item.state = LVIS_SELECTED;
3128 item.stateMask = LVIS_SELECTED;
3130 for (i = nFirst; i <= nLast; i++)
3131 LISTVIEW_SetItemState(infoPtr,i,&item);
3133 ZeroMemory(&nmlv, sizeof(nmlv));
3134 nmlv.iFrom = nFirst;
3135 nmlv.iTo = nLast;
3136 nmlv.uNewState = 0;
3137 nmlv.uOldState = item.state;
3139 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3140 if (!IsWindow(hwndSelf))
3141 return FALSE;
3142 infoPtr->bDoChangeNotify = bOldChange;
3143 return TRUE;
3147 /***
3148 * DESCRIPTION:
3149 * Sets a single group selection.
3151 * PARAMETER(S):
3152 * [I] infoPtr : valid pointer to the listview structure
3153 * [I] nItem : item index
3155 * RETURN:
3156 * None
3158 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3160 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3161 RANGES selection;
3162 LVITEMW item;
3163 ITERATOR i;
3164 BOOL bOldChange;
3166 if (!(selection = ranges_create(100))) return;
3168 item.state = LVIS_SELECTED;
3169 item.stateMask = LVIS_SELECTED;
3171 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3173 if (infoPtr->nSelectionMark == -1)
3175 infoPtr->nSelectionMark = nItem;
3176 ranges_additem(selection, nItem);
3178 else
3180 RANGE sel;
3182 sel.lower = min(infoPtr->nSelectionMark, nItem);
3183 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3184 ranges_add(selection, sel);
3187 else
3189 RECT rcItem, rcSel, rcSelMark;
3190 POINT ptItem;
3192 rcItem.left = LVIR_BOUNDS;
3193 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3194 rcSelMark.left = LVIR_BOUNDS;
3195 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3196 UnionRect(&rcSel, &rcItem, &rcSelMark);
3197 iterator_frameditems(&i, infoPtr, &rcSel);
3198 while(iterator_next(&i))
3200 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3201 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3203 iterator_destroy(&i);
3206 /* disable per item notifications on LVS_OWNERDATA style
3207 FIXME: single LVN_ODSTATECHANGED should be used */
3208 bOldChange = infoPtr->bDoChangeNotify;
3209 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3211 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3214 iterator_rangesitems(&i, selection);
3215 while(iterator_next(&i))
3216 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3217 /* this will also destroy the selection */
3218 iterator_destroy(&i);
3220 infoPtr->bDoChangeNotify = bOldChange;
3222 LISTVIEW_SetItemFocus(infoPtr, nItem);
3225 /***
3226 * DESCRIPTION:
3227 * Sets a single selection.
3229 * PARAMETER(S):
3230 * [I] infoPtr : valid pointer to the listview structure
3231 * [I] nItem : item index
3233 * RETURN:
3234 * None
3236 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3238 LVITEMW lvItem;
3240 TRACE("nItem=%d\n", nItem);
3242 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3244 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3245 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3246 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3248 infoPtr->nSelectionMark = nItem;
3251 /***
3252 * DESCRIPTION:
3253 * Set selection(s) with keyboard.
3255 * PARAMETER(S):
3256 * [I] infoPtr : valid pointer to the listview structure
3257 * [I] nItem : item index
3258 * [I] space : VK_SPACE code sent
3260 * RETURN:
3261 * SUCCESS : TRUE (needs to be repainted)
3262 * FAILURE : FALSE (nothing has changed)
3264 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3266 /* FIXME: pass in the state */
3267 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3268 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3269 BOOL bResult = FALSE;
3271 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3272 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3274 bResult = TRUE;
3276 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3277 LISTVIEW_SetSelection(infoPtr, nItem);
3278 else
3280 if (wShift)
3281 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3282 else if (wCtrl)
3284 LVITEMW lvItem;
3285 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3286 lvItem.stateMask = LVIS_SELECTED;
3287 if (space)
3289 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3290 if (lvItem.state & LVIS_SELECTED)
3291 infoPtr->nSelectionMark = nItem;
3293 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3296 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3299 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3300 return bResult;
3303 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3305 LVHITTESTINFO lvHitTestInfo;
3307 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3308 lvHitTestInfo.pt.x = pt.x;
3309 lvHitTestInfo.pt.y = pt.y;
3311 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3313 lpLVItem->mask = LVIF_PARAM;
3314 lpLVItem->iItem = lvHitTestInfo.iItem;
3315 lpLVItem->iSubItem = 0;
3317 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3320 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3322 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3323 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3324 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3327 /***
3328 * DESCRIPTION:
3329 * Called when the mouse is being actively tracked and has hovered for a specified
3330 * amount of time
3332 * PARAMETER(S):
3333 * [I] infoPtr : valid pointer to the listview structure
3334 * [I] fwKeys : key indicator
3335 * [I] x,y : mouse position
3337 * RETURN:
3338 * 0 if the message was processed, non-zero if there was an error
3340 * INFO:
3341 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3342 * over the item for a certain period of time.
3345 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3347 if (LISTVIEW_isHotTracking(infoPtr))
3349 LVITEMW item;
3350 POINT pt;
3352 pt.x = x;
3353 pt.y = y;
3355 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3356 LISTVIEW_SetSelection(infoPtr, item.iItem);
3359 return 0;
3362 /***
3363 * DESCRIPTION:
3364 * Called whenever WM_MOUSEMOVE is received.
3366 * PARAMETER(S):
3367 * [I] infoPtr : valid pointer to the listview structure
3368 * [I] fwKeys : key indicator
3369 * [I] x,y : mouse position
3371 * RETURN:
3372 * 0 if the message is processed, non-zero if there was an error
3374 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3376 TRACKMOUSEEVENT trackinfo;
3378 if (!(fwKeys & MK_LBUTTON))
3379 infoPtr->bLButtonDown = FALSE;
3381 if (infoPtr->bLButtonDown)
3383 POINT tmp;
3384 RECT rect;
3385 LVHITTESTINFO lvHitTestInfo;
3386 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3387 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3389 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3390 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3391 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3392 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3394 tmp.x = x;
3395 tmp.y = y;
3397 lvHitTestInfo.pt = tmp;
3398 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3400 /* reset item marker */
3401 if (infoPtr->nLButtonDownItem != lvHitTestInfo.iItem)
3402 infoPtr->nLButtonDownItem = -1;
3404 if (!PtInRect(&rect, tmp))
3406 /* this path covers the following:
3407 1. WM_LBUTTONDOWN over selected item (sets focus on it)
3408 2. change focus with keys
3409 3. move mouse over item from step 1 selects it and moves focus on it */
3410 if (infoPtr->nLButtonDownItem != -1 &&
3411 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
3413 LVITEMW lvItem;
3415 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3416 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3418 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
3419 infoPtr->nLButtonDownItem = -1;
3422 if (!infoPtr->bDragging)
3424 NMLISTVIEW nmlv;
3426 lvHitTestInfo.pt = infoPtr->ptClickPos;
3427 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3429 ZeroMemory(&nmlv, sizeof(nmlv));
3430 nmlv.iItem = lvHitTestInfo.iItem;
3431 nmlv.ptAction = infoPtr->ptClickPos;
3433 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3434 infoPtr->bDragging = TRUE;
3437 return 0;
3441 /* see if we are supposed to be tracking mouse hovering */
3442 if (LISTVIEW_isHotTracking(infoPtr)) {
3443 /* fill in the trackinfo struct */
3444 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3445 trackinfo.dwFlags = TME_QUERY;
3446 trackinfo.hwndTrack = infoPtr->hwndSelf;
3447 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3449 /* see if we are already tracking this hwnd */
3450 _TrackMouseEvent(&trackinfo);
3452 if(!(trackinfo.dwFlags & TME_HOVER)) {
3453 trackinfo.dwFlags = TME_HOVER;
3455 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3456 _TrackMouseEvent(&trackinfo);
3460 return 0;
3464 /***
3465 * Tests whether the item is assignable to a list with style lStyle
3467 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3469 if ( (lpLVItem->mask & LVIF_TEXT) &&
3470 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3471 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3473 return TRUE;
3477 /***
3478 * DESCRIPTION:
3479 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3481 * PARAMETER(S):
3482 * [I] infoPtr : valid pointer to the listview structure
3483 * [I] lpLVItem : valid pointer to new item attributes
3484 * [I] isNew : the item being set is being inserted
3485 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3486 * [O] bChanged : will be set to TRUE if the item really changed
3488 * RETURN:
3489 * SUCCESS : TRUE
3490 * FAILURE : FALSE
3492 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3494 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3495 ITEM_INFO *lpItem;
3496 NMLISTVIEW nmlv;
3497 UINT uChanged = 0;
3498 LVITEMW item;
3499 /* stateMask is ignored for LVM_INSERTITEM */
3500 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3502 TRACE("()\n");
3504 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3506 if (lpLVItem->mask == 0) return TRUE;
3508 if (infoPtr->dwStyle & LVS_OWNERDATA)
3510 /* a virtual listview only stores selection and focus */
3511 if (lpLVItem->mask & ~LVIF_STATE)
3512 return FALSE;
3513 lpItem = NULL;
3515 else
3517 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3518 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3519 assert (lpItem);
3522 /* we need to get the lParam and state of the item */
3523 item.iItem = lpLVItem->iItem;
3524 item.iSubItem = lpLVItem->iSubItem;
3525 item.mask = LVIF_STATE | LVIF_PARAM;
3526 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
3528 item.state = 0;
3529 item.lParam = 0;
3530 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3532 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3533 /* determine what fields will change */
3534 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3535 uChanged |= LVIF_STATE;
3537 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3538 uChanged |= LVIF_IMAGE;
3540 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3541 uChanged |= LVIF_PARAM;
3543 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3544 uChanged |= LVIF_INDENT;
3546 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3547 uChanged |= LVIF_TEXT;
3549 TRACE("uChanged=0x%x\n", uChanged);
3550 if (!uChanged) return TRUE;
3551 *bChanged = TRUE;
3553 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3554 nmlv.iItem = lpLVItem->iItem;
3555 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3556 nmlv.uOldState = item.state;
3557 nmlv.uChanged = uChanged;
3558 nmlv.lParam = item.lParam;
3560 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3561 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3562 /* are enabled */
3563 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3565 HWND hwndSelf = infoPtr->hwndSelf;
3567 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3568 return FALSE;
3569 if (!IsWindow(hwndSelf))
3570 return FALSE;
3573 /* copy information */
3574 if (lpLVItem->mask & LVIF_TEXT)
3575 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3577 if (lpLVItem->mask & LVIF_IMAGE)
3578 lpItem->hdr.iImage = lpLVItem->iImage;
3580 if (lpLVItem->mask & LVIF_PARAM)
3581 lpItem->lParam = lpLVItem->lParam;
3583 if (lpLVItem->mask & LVIF_INDENT)
3584 lpItem->iIndent = lpLVItem->iIndent;
3586 if (uChanged & LVIF_STATE)
3588 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3590 lpItem->state &= ~stateMask;
3591 lpItem->state |= (lpLVItem->state & stateMask);
3593 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3595 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3596 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3598 else if (stateMask & LVIS_SELECTED)
3600 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3602 /* if we are asked to change focus, and we manage it, do it */
3603 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3605 if (lpLVItem->state & LVIS_FOCUSED)
3607 if (infoPtr->nFocusedItem != -1)
3609 /* remove current focus */
3610 item.mask = LVIF_STATE;
3611 item.state = 0;
3612 item.stateMask = LVIS_FOCUSED;
3614 /* recurse with redrawing an item */
3615 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3618 infoPtr->nFocusedItem = lpLVItem->iItem;
3619 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3621 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3623 infoPtr->nFocusedItem = -1;
3628 /* if we're inserting the item, we're done */
3629 if (isNew) return TRUE;
3631 /* send LVN_ITEMCHANGED notification */
3632 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3633 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3635 return TRUE;
3638 /***
3639 * DESCRIPTION:
3640 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3642 * PARAMETER(S):
3643 * [I] infoPtr : valid pointer to the listview structure
3644 * [I] lpLVItem : valid pointer to new subitem attributes
3645 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3646 * [O] bChanged : will be set to TRUE if the item really changed
3648 * RETURN:
3649 * SUCCESS : TRUE
3650 * FAILURE : FALSE
3652 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3654 HDPA hdpaSubItems;
3655 SUBITEM_INFO *lpSubItem;
3657 /* we do not support subitems for virtual listviews */
3658 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3660 /* set subitem only if column is present */
3661 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3663 /* First do some sanity checks */
3664 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3665 particularly useful. We currently do not actually do anything with
3666 the flag on subitems.
3668 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3669 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3671 /* get the subitem structure, and create it if not there */
3672 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3673 assert (hdpaSubItems);
3675 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3676 if (!lpSubItem)
3678 SUBITEM_INFO *tmpSubItem;
3679 INT i;
3681 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3682 if (!lpSubItem) return FALSE;
3683 /* we could binary search here, if need be...*/
3684 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3686 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3687 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3689 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3691 Free(lpSubItem);
3692 return FALSE;
3694 lpSubItem->iSubItem = lpLVItem->iSubItem;
3695 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3696 *bChanged = TRUE;
3699 if (lpLVItem->mask & LVIF_IMAGE)
3700 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3702 lpSubItem->hdr.iImage = lpLVItem->iImage;
3703 *bChanged = TRUE;
3706 if (lpLVItem->mask & LVIF_TEXT)
3707 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3709 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3710 *bChanged = TRUE;
3713 return TRUE;
3716 /***
3717 * DESCRIPTION:
3718 * Sets item attributes.
3720 * PARAMETER(S):
3721 * [I] infoPtr : valid pointer to the listview structure
3722 * [I] lpLVItem : new item attributes
3723 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3725 * RETURN:
3726 * SUCCESS : TRUE
3727 * FAILURE : FALSE
3729 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3731 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3732 HWND hwndSelf = infoPtr->hwndSelf;
3733 LPWSTR pszText = NULL;
3734 BOOL bResult, bChanged = FALSE;
3736 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3738 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3739 return FALSE;
3741 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3742 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3744 pszText = lpLVItem->pszText;
3745 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3748 /* actually set the fields */
3749 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3751 if (lpLVItem->iSubItem)
3752 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3753 else
3754 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3755 if (!IsWindow(hwndSelf))
3756 return FALSE;
3758 /* redraw item, if necessary */
3759 if (bChanged && !infoPtr->bIsDrawing)
3761 /* this little optimization eliminates some nasty flicker */
3762 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3763 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3764 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3765 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3766 else
3767 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3769 /* restore text */
3770 if (pszText)
3772 textfreeT(lpLVItem->pszText, isW);
3773 lpLVItem->pszText = pszText;
3776 return bResult;
3779 /***
3780 * DESCRIPTION:
3781 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3783 * PARAMETER(S):
3784 * [I] infoPtr : valid pointer to the listview structure
3786 * RETURN:
3787 * item index
3789 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3791 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3792 INT nItem = 0;
3793 SCROLLINFO scrollInfo;
3795 scrollInfo.cbSize = sizeof(SCROLLINFO);
3796 scrollInfo.fMask = SIF_POS;
3798 if (uView == LVS_LIST)
3800 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3801 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3803 else if (uView == LVS_REPORT)
3805 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3806 nItem = scrollInfo.nPos;
3808 else
3810 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3811 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3814 TRACE("nItem=%d\n", nItem);
3816 return nItem;
3820 /***
3821 * DESCRIPTION:
3822 * Erases the background of the given rectangle
3824 * PARAMETER(S):
3825 * [I] infoPtr : valid pointer to the listview structure
3826 * [I] hdc : device context handle
3827 * [I] lprcBox : clipping rectangle
3829 * RETURN:
3830 * Success: TRUE
3831 * Failure: FALSE
3833 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3835 if (!infoPtr->hBkBrush) return FALSE;
3837 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3839 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3842 /***
3843 * DESCRIPTION:
3844 * Draws an item.
3846 * PARAMETER(S):
3847 * [I] infoPtr : valid pointer to the listview structure
3848 * [I] hdc : device context handle
3849 * [I] nItem : item index
3850 * [I] nSubItem : subitem index
3851 * [I] pos : item position in client coordinates
3852 * [I] cdmode : custom draw mode
3854 * RETURN:
3855 * Success: TRUE
3856 * Failure: FALSE
3858 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3860 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3861 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3862 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3863 DWORD cdsubitemmode = CDRF_DODEFAULT;
3864 LPRECT lprcFocus;
3865 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3866 NMLVCUSTOMDRAW nmlvcd;
3867 HIMAGELIST himl;
3868 LVITEMW lvItem;
3869 HFONT hOldFont;
3871 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3873 /* get information needed for drawing the item */
3874 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3875 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3876 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3877 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3878 lvItem.iItem = nItem;
3879 lvItem.iSubItem = nSubItem;
3880 lvItem.state = 0;
3881 lvItem.lParam = 0;
3882 lvItem.cchTextMax = DISP_TEXT_SIZE;
3883 lvItem.pszText = szDispText;
3884 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3885 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3886 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3887 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3888 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3890 /* now check if we need to update the focus rectangle */
3891 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3893 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3894 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3895 OffsetRect(&rcBox, pos.x, pos.y);
3896 OffsetRect(&rcSelect, pos.x, pos.y);
3897 OffsetRect(&rcIcon, pos.x, pos.y);
3898 OffsetRect(&rcStateIcon, pos.x, pos.y);
3899 OffsetRect(&rcLabel, pos.x, pos.y);
3900 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3901 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3902 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3904 /* fill in the custom draw structure */
3905 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3907 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3908 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3909 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3910 if (cdmode & CDRF_NOTIFYITEMDRAW)
3911 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3912 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3913 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3914 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3915 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3917 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3918 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3920 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3921 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3922 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3923 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3925 /* in full row select, subitems, will just use main item's colors */
3926 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3927 nmlvcd.clrTextBk = CLR_NONE;
3929 /* state icons */
3930 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3932 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3933 if (uStateImage)
3935 TRACE("uStateImage=%d\n", uStateImage);
3936 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3937 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3941 /* small icons */
3942 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3943 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3945 TRACE("iImage=%d\n", lvItem.iImage);
3946 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3947 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3948 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3951 /* Don't bother painting item being edited */
3952 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3954 /* FIXME: temporary hack */
3955 rcSelect.left = rcLabel.left;
3957 /* draw the selection background, if we're drawing the main item */
3958 if (nSubItem == 0)
3960 /* in icon mode, the label rect is really what we want to draw the
3961 * background for */
3962 if (uView == LVS_ICON)
3963 rcSelect = rcLabel;
3965 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3966 rcSelect.right = rcBox.right;
3968 if (nmlvcd.clrTextBk != CLR_NONE)
3969 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3970 /* store new focus rectangle */
3971 if (infoPtr->nFocusedItem == nItem) infoPtr->rcFocus = rcSelect;
3974 /* figure out the text drawing flags */
3975 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3976 if (uView == LVS_ICON)
3977 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3978 else if (nSubItem)
3980 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3982 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3983 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3984 default: uFormat |= DT_LEFT;
3987 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3989 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3990 else rcLabel.left += LABEL_HOR_PADDING;
3992 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3994 /* for GRIDLINES reduce the bottom so the text formats correctly */
3995 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3996 rcLabel.bottom--;
3998 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
4000 postpaint:
4001 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4002 notify_postpaint(infoPtr, &nmlvcd);
4003 if (cdsubitemmode & CDRF_NEWFONT)
4004 SelectObject(hdc, hOldFont);
4005 return TRUE;
4008 /***
4009 * DESCRIPTION:
4010 * Draws listview items when in owner draw mode.
4012 * PARAMETER(S):
4013 * [I] infoPtr : valid pointer to the listview structure
4014 * [I] hdc : device context handle
4016 * RETURN:
4017 * None
4019 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4021 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4022 DWORD cditemmode = CDRF_DODEFAULT;
4023 NMLVCUSTOMDRAW nmlvcd;
4024 POINT Origin, Position;
4025 DRAWITEMSTRUCT dis;
4026 LVITEMW item;
4028 TRACE("()\n");
4030 ZeroMemory(&dis, sizeof(dis));
4032 /* Get scroll info once before loop */
4033 LISTVIEW_GetOrigin(infoPtr, &Origin);
4035 /* iterate through the invalidated rows */
4036 while(iterator_next(i))
4038 item.iItem = i->nItem;
4039 item.iSubItem = 0;
4040 item.mask = LVIF_PARAM | LVIF_STATE;
4041 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4042 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4044 dis.CtlType = ODT_LISTVIEW;
4045 dis.CtlID = uID;
4046 dis.itemID = item.iItem;
4047 dis.itemAction = ODA_DRAWENTIRE;
4048 dis.itemState = 0;
4049 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4050 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4051 dis.hwndItem = infoPtr->hwndSelf;
4052 dis.hDC = hdc;
4053 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4054 dis.rcItem.left = Position.x + Origin.x;
4055 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4056 dis.rcItem.top = Position.y + Origin.y;
4057 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4058 dis.itemData = item.lParam;
4060 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4063 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4064 * structure for the rest. of the paint cycle
4066 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4067 if (cdmode & CDRF_NOTIFYITEMDRAW)
4068 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4070 if (!(cditemmode & CDRF_SKIPDEFAULT))
4072 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4073 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4076 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4077 notify_postpaint(infoPtr, &nmlvcd);
4081 /***
4082 * DESCRIPTION:
4083 * Draws listview items when in report display mode.
4085 * PARAMETER(S):
4086 * [I] infoPtr : valid pointer to the listview structure
4087 * [I] hdc : device context handle
4088 * [I] cdmode : custom draw mode
4090 * RETURN:
4091 * None
4093 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4095 INT rgntype;
4096 RECT rcClip, rcItem;
4097 POINT Origin, Position;
4098 RANGE colRange;
4099 ITERATOR j;
4101 TRACE("()\n");
4103 /* figure out what to draw */
4104 rgntype = GetClipBox(hdc, &rcClip);
4105 if (rgntype == NULLREGION) return;
4107 /* Get scroll info once before loop */
4108 LISTVIEW_GetOrigin(infoPtr, &Origin);
4110 /* narrow down the columns we need to paint */
4111 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4113 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4114 if (rcItem.right + Origin.x >= rcClip.left) break;
4116 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4118 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4119 if (rcItem.left + Origin.x < rcClip.right) break;
4121 iterator_rangeitems(&j, colRange);
4123 /* in full row select, we _have_ to draw the main item */
4124 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4125 j.nSpecial = 0;
4127 /* iterate through the invalidated rows */
4128 while(iterator_next(i))
4130 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4131 Position.x += Origin.x;
4132 Position.y += Origin.y;
4134 /* iterate through the invalidated columns */
4135 while(iterator_next(&j))
4137 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4139 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4140 rcItem.top = 0;
4141 rcItem.bottom = infoPtr->nItemHeight;
4142 OffsetRect(&rcItem, Position.x, Position.y);
4143 if (!RectVisible(hdc, &rcItem)) continue;
4146 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4149 iterator_destroy(&j);
4152 /***
4153 * DESCRIPTION:
4154 * Draws the gridlines if necessary when in report display mode.
4156 * PARAMETER(S):
4157 * [I] infoPtr : valid pointer to the listview structure
4158 * [I] hdc : device context handle
4160 * RETURN:
4161 * None
4163 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4165 INT rgntype;
4166 INT y, itemheight;
4167 HPEN hPen, hOldPen;
4168 RECT rcClip, rcItem = {0};
4169 POINT Origin;
4170 RANGE colRange;
4171 ITERATOR j;
4172 BOOL rmost = FALSE;
4174 TRACE("()\n");
4176 /* figure out what to draw */
4177 rgntype = GetClipBox(hdc, &rcClip);
4178 if (rgntype == NULLREGION) return;
4180 /* Get scroll info once before loop */
4181 LISTVIEW_GetOrigin(infoPtr, &Origin);
4183 /* narrow down the columns we need to paint */
4184 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4186 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4187 if (rcItem.right + Origin.x >= rcClip.left) break;
4189 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4191 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4192 if (rcItem.left + Origin.x < rcClip.right) break;
4194 /* is right most vertical line visible? */
4195 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4197 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcItem);
4198 rmost = (rcItem.right + Origin.x < rcClip.right);
4201 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4203 hOldPen = SelectObject ( hdc, hPen );
4205 /* draw the vertical lines for the columns */
4206 iterator_rangeitems(&j, colRange);
4207 while(iterator_next(&j))
4209 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4210 if (rcItem.left == 0) continue; /* skip first column */
4211 rcItem.left += Origin.x;
4212 rcItem.right += Origin.x;
4213 rcItem.top = infoPtr->rcList.top;
4214 rcItem.bottom = infoPtr->rcList.bottom;
4215 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4216 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4217 LineTo (hdc, rcItem.left, rcItem.bottom);
4219 iterator_destroy(&j);
4220 /* draw rightmost grid line if visible */
4221 if (rmost)
4223 MoveToEx (hdc, rcItem.right, rcItem.top, NULL);
4224 LineTo (hdc, rcItem.right, rcItem.bottom);
4227 /* draw the horizontial lines for the rows */
4228 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4229 rcItem.left = infoPtr->rcList.left;
4230 rcItem.right = infoPtr->rcList.right;
4231 rcItem.bottom = rcItem.top = Origin.y - 1;
4232 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4233 LineTo(hdc, rcItem.right, rcItem.top);
4234 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4236 rcItem.bottom = rcItem.top = y;
4237 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4238 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4239 LineTo (hdc, rcItem.right, rcItem.top);
4242 SelectObject( hdc, hOldPen );
4243 DeleteObject( hPen );
4247 /***
4248 * DESCRIPTION:
4249 * Draws listview items when in list display mode.
4251 * PARAMETER(S):
4252 * [I] infoPtr : valid pointer to the listview structure
4253 * [I] hdc : device context handle
4254 * [I] cdmode : custom draw mode
4256 * RETURN:
4257 * None
4259 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4261 POINT Origin, Position;
4263 /* Get scroll info once before loop */
4264 LISTVIEW_GetOrigin(infoPtr, &Origin);
4266 while(iterator_prev(i))
4268 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4269 Position.x += Origin.x;
4270 Position.y += Origin.y;
4272 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4277 /***
4278 * DESCRIPTION:
4279 * Draws listview items.
4281 * PARAMETER(S):
4282 * [I] infoPtr : valid pointer to the listview structure
4283 * [I] hdc : device context handle
4284 * [I] prcErase : rect to be erased before refresh (may be NULL)
4286 * RETURN:
4287 * NoneX
4289 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4291 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4292 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4293 NMLVCUSTOMDRAW nmlvcd;
4294 HFONT hOldFont = 0;
4295 DWORD cdmode;
4296 INT oldBkMode = 0;
4297 RECT rcClient;
4298 ITERATOR i;
4299 HDC hdcOrig = hdc;
4300 HBITMAP hbmp = NULL;
4301 RANGE range;
4303 LISTVIEW_DUMP(infoPtr);
4305 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4306 TRACE("double buffering\n");
4308 hdc = CreateCompatibleDC(hdcOrig);
4309 if (!hdc) {
4310 ERR("Failed to create DC for backbuffer\n");
4311 return;
4313 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4314 infoPtr->rcList.bottom);
4315 if (!hbmp) {
4316 ERR("Failed to create bitmap for backbuffer\n");
4317 DeleteDC(hdc);
4318 return;
4321 SelectObject(hdc, hbmp);
4322 SelectObject(hdc, infoPtr->hFont);
4323 } else {
4324 /* Save dc values we're gonna trash while drawing
4325 * FIXME: Should be done in LISTVIEW_DrawItem() */
4326 hOldFont = SelectObject(hdc, infoPtr->hFont);
4327 oldBkMode = GetBkMode(hdc);
4328 oldBkColor = GetBkColor(hdc);
4329 oldTextColor = GetTextColor(hdc);
4332 infoPtr->bIsDrawing = TRUE;
4334 if (prcErase) {
4335 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4336 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4337 /* If no erasing was done (usually because RedrawWindow was called
4338 * with RDW_INVALIDATE only) we need to copy the old contents into
4339 * the backbuffer before continuing. */
4340 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4341 infoPtr->rcList.right - infoPtr->rcList.left,
4342 infoPtr->rcList.bottom - infoPtr->rcList.top,
4343 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4346 /* FIXME: Shouldn't need to do this */
4347 oldClrTextBk = infoPtr->clrTextBk;
4348 oldClrText = infoPtr->clrText;
4350 infoPtr->cditemmode = CDRF_DODEFAULT;
4352 GetClientRect(infoPtr->hwndSelf, &rcClient);
4353 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4354 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4355 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4356 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4358 /* Use these colors to draw the items */
4359 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4360 infoPtr->clrText = nmlvcd.clrText;
4362 /* nothing to draw */
4363 if(infoPtr->nItemCount == 0) goto enddraw;
4365 /* figure out what we need to draw */
4366 iterator_visibleitems(&i, infoPtr, hdc);
4367 range = iterator_range(&i);
4369 /* send cache hint notification */
4370 if (infoPtr->dwStyle & LVS_OWNERDATA)
4372 NMLVCACHEHINT nmlv;
4374 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4375 nmlv.iFrom = range.lower;
4376 nmlv.iTo = range.upper - 1;
4377 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4380 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4381 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4382 else
4384 if (uView == LVS_REPORT)
4385 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4386 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4387 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4389 /* if we have a focus rect and it's visible, draw it */
4390 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
4391 (range.upper - 1) >= infoPtr->nFocusedItem)
4392 LISTVIEW_DrawFocusRect(infoPtr, hdc);
4394 iterator_destroy(&i);
4396 enddraw:
4397 /* For LVS_EX_GRIDLINES go and draw lines */
4398 /* This includes the case where there were *no* items */
4399 if ((uView == LVS_REPORT) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4400 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4402 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4403 notify_postpaint(infoPtr, &nmlvcd);
4405 infoPtr->clrTextBk = oldClrTextBk;
4406 infoPtr->clrText = oldClrText;
4408 if(hbmp) {
4409 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4410 infoPtr->rcList.right - infoPtr->rcList.left,
4411 infoPtr->rcList.bottom - infoPtr->rcList.top,
4412 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4414 DeleteObject(hbmp);
4415 DeleteDC(hdc);
4416 } else {
4417 SelectObject(hdc, hOldFont);
4418 SetBkMode(hdc, oldBkMode);
4419 SetBkColor(hdc, oldBkColor);
4420 SetTextColor(hdc, oldTextColor);
4423 infoPtr->bIsDrawing = FALSE;
4427 /***
4428 * DESCRIPTION:
4429 * Calculates the approximate width and height of a given number of items.
4431 * PARAMETER(S):
4432 * [I] infoPtr : valid pointer to the listview structure
4433 * [I] nItemCount : number of items
4434 * [I] wWidth : width
4435 * [I] wHeight : height
4437 * RETURN:
4438 * Returns a DWORD. The width in the low word and the height in high word.
4440 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4441 WORD wWidth, WORD wHeight)
4443 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4444 INT nItemCountPerColumn = 1;
4445 INT nColumnCount = 0;
4446 DWORD dwViewRect = 0;
4448 if (nItemCount == -1)
4449 nItemCount = infoPtr->nItemCount;
4451 if (uView == LVS_LIST)
4453 if (wHeight == 0xFFFF)
4455 /* use current height */
4456 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4459 if (wHeight < infoPtr->nItemHeight)
4460 wHeight = infoPtr->nItemHeight;
4462 if (nItemCount > 0)
4464 if (infoPtr->nItemHeight > 0)
4466 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4467 if (nItemCountPerColumn == 0)
4468 nItemCountPerColumn = 1;
4470 if (nItemCount % nItemCountPerColumn != 0)
4471 nColumnCount = nItemCount / nItemCountPerColumn;
4472 else
4473 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4477 /* Microsoft padding magic */
4478 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4479 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4481 dwViewRect = MAKELONG(wWidth, wHeight);
4483 else if (uView == LVS_REPORT)
4485 RECT rcBox;
4487 if (infoPtr->nItemCount > 0)
4489 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4490 wWidth = rcBox.right - rcBox.left;
4491 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4493 else
4495 /* use current height and width */
4496 if (wHeight == 0xffff)
4497 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4498 if (wWidth == 0xffff)
4499 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4502 dwViewRect = MAKELONG(wWidth, wHeight);
4504 else if (uView == LVS_SMALLICON)
4505 FIXME("uView == LVS_SMALLICON: not implemented\n");
4506 else if (uView == LVS_ICON)
4507 FIXME("uView == LVS_ICON: not implemented\n");
4509 return dwViewRect;
4513 /***
4514 * DESCRIPTION:
4515 * Create a drag image list for the specified item.
4517 * PARAMETER(S):
4518 * [I] infoPtr : valid pointer to the listview structure
4519 * [I] iItem : index of item
4520 * [O] lppt : Upper-left corner of the image
4522 * RETURN:
4523 * Returns a handle to the image list if successful, NULL otherwise.
4525 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4527 RECT rcItem;
4528 SIZE size;
4529 POINT pos;
4530 HDC hdc, hdcOrig;
4531 HBITMAP hbmp, hOldbmp;
4532 HIMAGELIST dragList = 0;
4533 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4535 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4536 return 0;
4538 rcItem.left = LVIR_BOUNDS;
4539 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4540 return 0;
4542 lppt->x = rcItem.left;
4543 lppt->y = rcItem.top;
4545 size.cx = rcItem.right - rcItem.left;
4546 size.cy = rcItem.bottom - rcItem.top;
4548 hdcOrig = GetDC(infoPtr->hwndSelf);
4549 hdc = CreateCompatibleDC(hdcOrig);
4550 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4551 hOldbmp = SelectObject(hdc, hbmp);
4553 rcItem.left = rcItem.top = 0;
4554 rcItem.right = size.cx;
4555 rcItem.bottom = size.cy;
4556 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4558 pos.x = pos.y = 0;
4559 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4561 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4562 SelectObject(hdc, hOldbmp);
4563 ImageList_Add(dragList, hbmp, 0);
4565 else
4566 SelectObject(hdc, hOldbmp);
4568 DeleteObject(hbmp);
4569 DeleteDC(hdc);
4570 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4572 TRACE("ret=%p\n", dragList);
4574 return dragList;
4578 /***
4579 * DESCRIPTION:
4580 * Removes all listview items and subitems.
4582 * PARAMETER(S):
4583 * [I] infoPtr : valid pointer to the listview structure
4585 * RETURN:
4586 * SUCCESS : TRUE
4587 * FAILURE : FALSE
4589 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4591 NMLISTVIEW nmlv;
4592 HDPA hdpaSubItems = NULL;
4593 BOOL bSuppress;
4594 ITEMHDR *hdrItem;
4595 INT i, j;
4597 TRACE("()\n");
4599 /* we do it directly, to avoid notifications */
4600 ranges_clear(infoPtr->selectionRanges);
4601 infoPtr->nSelectionMark = -1;
4602 infoPtr->nFocusedItem = -1;
4603 SetRectEmpty(&infoPtr->rcFocus);
4604 /* But we are supposed to leave nHotItem as is! */
4607 /* send LVN_DELETEALLITEMS notification */
4608 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4609 nmlv.iItem = -1;
4610 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4612 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4614 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4616 /* send LVN_DELETEITEM notification, if not suppressed
4617 and if it is not a virtual listview */
4618 if (!bSuppress) notify_deleteitem(infoPtr, i);
4619 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4620 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4622 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4623 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4624 Free(hdrItem);
4626 DPA_Destroy(hdpaSubItems);
4627 DPA_DeletePtr(infoPtr->hdpaItems, i);
4629 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4630 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4631 infoPtr->nItemCount --;
4634 if (!destroy)
4636 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4637 LISTVIEW_UpdateScroll(infoPtr);
4639 LISTVIEW_InvalidateList(infoPtr);
4641 return TRUE;
4644 /***
4645 * DESCRIPTION:
4646 * Scrolls, and updates the columns, when a column is changing width.
4648 * PARAMETER(S):
4649 * [I] infoPtr : valid pointer to the listview structure
4650 * [I] nColumn : column to scroll
4651 * [I] dx : amount of scroll, in pixels
4653 * RETURN:
4654 * None.
4656 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4658 COLUMN_INFO *lpColumnInfo;
4659 RECT rcOld, rcCol;
4660 POINT ptOrigin;
4661 INT nCol;
4663 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4664 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4665 rcCol = lpColumnInfo->rcHeader;
4666 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4667 rcCol.left = rcCol.right;
4669 /* adjust the other columns */
4670 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4672 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4673 lpColumnInfo->rcHeader.left += dx;
4674 lpColumnInfo->rcHeader.right += dx;
4677 /* do not update screen if not in report mode */
4678 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4680 /* Need to reset the item width when inserting a new column */
4681 infoPtr->nItemWidth += dx;
4683 LISTVIEW_UpdateScroll(infoPtr);
4684 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4686 /* scroll to cover the deleted column, and invalidate for redraw */
4687 rcOld = infoPtr->rcList;
4688 rcOld.left = ptOrigin.x + rcCol.left + dx;
4689 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4692 /***
4693 * DESCRIPTION:
4694 * Removes a column from the listview control.
4696 * PARAMETER(S):
4697 * [I] infoPtr : valid pointer to the listview structure
4698 * [I] nColumn : column index
4700 * RETURN:
4701 * SUCCESS : TRUE
4702 * FAILURE : FALSE
4704 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4706 RECT rcCol;
4708 TRACE("nColumn=%d\n", nColumn);
4710 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4711 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4713 /* While the MSDN specifically says that column zero should not be deleted,
4714 what actually happens is that the column itself is deleted but no items or subitems
4715 are removed.
4718 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4720 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
4721 return FALSE;
4723 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4724 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4726 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4728 SUBITEM_INFO *lpSubItem, *lpDelItem;
4729 HDPA hdpaSubItems;
4730 INT nItem, nSubItem, i;
4732 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4734 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4735 nSubItem = 0;
4736 lpDelItem = 0;
4737 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4739 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4740 if (lpSubItem->iSubItem == nColumn)
4742 nSubItem = i;
4743 lpDelItem = lpSubItem;
4745 else if (lpSubItem->iSubItem > nColumn)
4747 lpSubItem->iSubItem--;
4751 /* if we found our subitem, zapp it */
4752 if (nSubItem > 0)
4754 /* free string */
4755 if (is_textW(lpDelItem->hdr.pszText))
4756 Free(lpDelItem->hdr.pszText);
4758 /* free item */
4759 Free(lpDelItem);
4761 /* free dpa memory */
4762 DPA_DeletePtr(hdpaSubItems, nSubItem);
4767 /* update the other column info */
4768 LISTVIEW_UpdateItemSize(infoPtr);
4769 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4770 LISTVIEW_InvalidateList(infoPtr);
4771 else
4772 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4774 return TRUE;
4777 /***
4778 * DESCRIPTION:
4779 * Invalidates the listview after an item's insertion or deletion.
4781 * PARAMETER(S):
4782 * [I] infoPtr : valid pointer to the listview structure
4783 * [I] nItem : item index
4784 * [I] dir : -1 if deleting, 1 if inserting
4786 * RETURN:
4787 * None
4789 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4791 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4792 INT nPerCol, nItemCol, nItemRow;
4793 RECT rcScroll;
4794 POINT Origin;
4796 /* if we don't refresh, what's the point of scrolling? */
4797 if (!is_redrawing(infoPtr)) return;
4799 assert (abs(dir) == 1);
4801 /* arrange icons if autoarrange is on */
4802 if (is_autoarrange(infoPtr))
4804 BOOL arrange = TRUE;
4805 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4806 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4807 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4810 /* scrollbars need updating */
4811 LISTVIEW_UpdateScroll(infoPtr);
4813 /* figure out the item's position */
4814 if (uView == LVS_REPORT)
4815 nPerCol = infoPtr->nItemCount + 1;
4816 else if (uView == LVS_LIST)
4817 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4818 else /* LVS_ICON, or LVS_SMALLICON */
4819 return;
4821 nItemCol = nItem / nPerCol;
4822 nItemRow = nItem % nPerCol;
4823 LISTVIEW_GetOrigin(infoPtr, &Origin);
4825 /* move the items below up a slot */
4826 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4827 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4828 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4829 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4830 OffsetRect(&rcScroll, Origin.x, Origin.y);
4831 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4832 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4834 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4835 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4836 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4839 /* report has only that column, so we're done */
4840 if (uView == LVS_REPORT) return;
4842 /* now for LISTs, we have to deal with the columns to the right */
4843 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4844 rcScroll.top = 0;
4845 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4846 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4847 OffsetRect(&rcScroll, Origin.x, Origin.y);
4848 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4849 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4850 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4853 /***
4854 * DESCRIPTION:
4855 * Removes an item from the listview control.
4857 * PARAMETER(S):
4858 * [I] infoPtr : valid pointer to the listview structure
4859 * [I] nItem : item index
4861 * RETURN:
4862 * SUCCESS : TRUE
4863 * FAILURE : FALSE
4865 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4867 LVITEMW item;
4868 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4869 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4871 TRACE("(nItem=%d)\n", nItem);
4873 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4875 /* remove selection, and focus */
4876 item.state = 0;
4877 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4878 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4880 /* send LVN_DELETEITEM notification. */
4881 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4883 /* we need to do this here, because we'll be deleting stuff */
4884 if (is_icon)
4885 LISTVIEW_InvalidateItem(infoPtr, nItem);
4887 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4889 HDPA hdpaSubItems;
4890 ITEMHDR *hdrItem;
4891 INT i;
4893 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4894 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4896 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4897 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4898 Free(hdrItem);
4900 DPA_Destroy(hdpaSubItems);
4903 if (is_icon)
4905 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4906 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4909 infoPtr->nItemCount--;
4910 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4912 /* now is the invalidation fun */
4913 if (!is_icon)
4914 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4915 return TRUE;
4919 /***
4920 * DESCRIPTION:
4921 * Callback implementation for editlabel control
4923 * PARAMETER(S):
4924 * [I] infoPtr : valid pointer to the listview structure
4925 * [I] pszText : modified text
4926 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4928 * RETURN:
4929 * SUCCESS : TRUE
4930 * FAILURE : FALSE
4932 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4934 HWND hwndSelf = infoPtr->hwndSelf;
4935 NMLVDISPINFOW dispInfo;
4936 INT editedItem = infoPtr->nEditLabelItem;
4937 BOOL bSame;
4939 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4941 infoPtr->nEditLabelItem = -1;
4943 ZeroMemory(&dispInfo, sizeof(dispInfo));
4944 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4945 dispInfo.item.iItem = editedItem;
4946 dispInfo.item.iSubItem = 0;
4947 dispInfo.item.stateMask = ~0;
4948 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4950 if (isW)
4951 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4952 else
4954 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4955 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4956 textfreeT(tmp, FALSE);
4958 if (bSame) return TRUE;
4960 /* add the text from the edit in */
4961 dispInfo.item.mask |= LVIF_TEXT;
4962 dispInfo.item.pszText = pszText;
4963 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4965 /* Do we need to update the Item Text */
4966 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4967 if (!IsWindow(hwndSelf))
4968 return FALSE;
4969 if (!pszText) return TRUE;
4971 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4973 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4974 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4975 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4977 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4978 return TRUE;
4982 ZeroMemory(&dispInfo, sizeof(dispInfo));
4983 dispInfo.item.mask = LVIF_TEXT;
4984 dispInfo.item.iItem = editedItem;
4985 dispInfo.item.iSubItem = 0;
4986 dispInfo.item.pszText = pszText;
4987 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4988 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4991 /***
4992 * DESCRIPTION:
4993 * Begin in place editing of specified list view item
4995 * PARAMETER(S):
4996 * [I] infoPtr : valid pointer to the listview structure
4997 * [I] nItem : item index
4998 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
5000 * RETURN:
5001 * SUCCESS : TRUE
5002 * FAILURE : FALSE
5004 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
5006 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5007 NMLVDISPINFOW dispInfo;
5008 RECT rect;
5009 HWND hwndSelf = infoPtr->hwndSelf;
5011 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
5013 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
5014 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5016 infoPtr->nEditLabelItem = nItem;
5018 /* Is the EditBox still there, if so remove it */
5019 if(infoPtr->hwndEdit != 0)
5021 SetFocus(infoPtr->hwndSelf);
5022 infoPtr->hwndEdit = 0;
5025 LISTVIEW_SetSelection(infoPtr, nItem);
5026 LISTVIEW_SetItemFocus(infoPtr, nItem);
5027 LISTVIEW_InvalidateItem(infoPtr, nItem);
5029 rect.left = LVIR_LABEL;
5030 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5032 ZeroMemory(&dispInfo, sizeof(dispInfo));
5033 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5034 dispInfo.item.iItem = nItem;
5035 dispInfo.item.iSubItem = 0;
5036 dispInfo.item.stateMask = ~0;
5037 dispInfo.item.pszText = szDispText;
5038 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5039 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5041 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
5042 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
5043 if (!infoPtr->hwndEdit) return 0;
5045 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5047 if (!IsWindow(hwndSelf))
5048 return 0;
5049 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5050 infoPtr->hwndEdit = 0;
5051 return 0;
5054 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5055 SetFocus(infoPtr->hwndEdit);
5056 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5057 return infoPtr->hwndEdit;
5061 /***
5062 * DESCRIPTION:
5063 * Ensures the specified item is visible, scrolling into view if necessary.
5065 * PARAMETER(S):
5066 * [I] infoPtr : valid pointer to the listview structure
5067 * [I] nItem : item index
5068 * [I] bPartial : partially or entirely visible
5070 * RETURN:
5071 * SUCCESS : TRUE
5072 * FAILURE : FALSE
5074 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5076 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5077 INT nScrollPosHeight = 0;
5078 INT nScrollPosWidth = 0;
5079 INT nHorzAdjust = 0;
5080 INT nVertAdjust = 0;
5081 INT nHorzDiff = 0;
5082 INT nVertDiff = 0;
5083 RECT rcItem, rcTemp;
5085 rcItem.left = LVIR_BOUNDS;
5086 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5088 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5090 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5092 /* scroll left/right, but in LVS_REPORT mode */
5093 if (uView == LVS_LIST)
5094 nScrollPosWidth = infoPtr->nItemWidth;
5095 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5096 nScrollPosWidth = 1;
5098 if (rcItem.left < infoPtr->rcList.left)
5100 nHorzAdjust = -1;
5101 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5103 else
5105 nHorzAdjust = 1;
5106 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5110 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5112 /* scroll up/down, but not in LVS_LIST mode */
5113 if (uView == LVS_REPORT)
5114 nScrollPosHeight = infoPtr->nItemHeight;
5115 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5116 nScrollPosHeight = 1;
5118 if (rcItem.top < infoPtr->rcList.top)
5120 nVertAdjust = -1;
5121 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5123 else
5125 nVertAdjust = 1;
5126 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5130 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5132 if (nScrollPosWidth)
5134 INT diff = nHorzDiff / nScrollPosWidth;
5135 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5136 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5139 if (nScrollPosHeight)
5141 INT diff = nVertDiff / nScrollPosHeight;
5142 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5143 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5146 return TRUE;
5149 /***
5150 * DESCRIPTION:
5151 * Searches for an item with specific characteristics.
5153 * PARAMETER(S):
5154 * [I] hwnd : window handle
5155 * [I] nStart : base item index
5156 * [I] lpFindInfo : item information to look for
5158 * RETURN:
5159 * SUCCESS : index of item
5160 * FAILURE : -1
5162 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5163 const LVFINDINFOW *lpFindInfo)
5165 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5166 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5167 BOOL bWrap = FALSE, bNearest = FALSE;
5168 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5169 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5170 POINT Position, Destination;
5171 LVITEMW lvItem;
5173 /* Search in virtual listviews should be done by application, not by
5174 listview control, so we just send LVN_ODFINDITEMW and return the result */
5175 if (infoPtr->dwStyle & LVS_OWNERDATA)
5177 NMLVFINDITEMW nmlv;
5179 nmlv.iStart = nStart;
5180 nmlv.lvfi = *lpFindInfo;
5181 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5184 if (!lpFindInfo || nItem < 0) return -1;
5186 lvItem.mask = 0;
5187 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5189 lvItem.mask |= LVIF_TEXT;
5190 lvItem.pszText = szDispText;
5191 lvItem.cchTextMax = DISP_TEXT_SIZE;
5194 if (lpFindInfo->flags & LVFI_WRAP)
5195 bWrap = TRUE;
5197 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5198 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5200 POINT Origin;
5201 RECT rcArea;
5203 LISTVIEW_GetOrigin(infoPtr, &Origin);
5204 Destination.x = lpFindInfo->pt.x - Origin.x;
5205 Destination.y = lpFindInfo->pt.y - Origin.y;
5206 switch(lpFindInfo->vkDirection)
5208 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5209 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5210 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5211 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5212 case VK_HOME: Destination.x = Destination.y = 0; break;
5213 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5214 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5215 case VK_END:
5216 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5217 Destination.x = rcArea.right;
5218 Destination.y = rcArea.bottom;
5219 break;
5220 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5222 bNearest = TRUE;
5224 else Destination.x = Destination.y = 0;
5226 /* if LVFI_PARAM is specified, all other flags are ignored */
5227 if (lpFindInfo->flags & LVFI_PARAM)
5229 lvItem.mask |= LVIF_PARAM;
5230 bNearest = FALSE;
5231 lvItem.mask &= ~LVIF_TEXT;
5234 again:
5235 for (; nItem < nLast; nItem++)
5237 lvItem.iItem = nItem;
5238 lvItem.iSubItem = 0;
5239 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5241 if (lvItem.mask & LVIF_PARAM)
5243 if (lpFindInfo->lParam == lvItem.lParam)
5244 return nItem;
5245 else
5246 continue;
5249 if (lvItem.mask & LVIF_TEXT)
5251 if (lpFindInfo->flags & LVFI_PARTIAL)
5253 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5255 else
5257 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5261 if (!bNearest) return nItem;
5263 /* This is very inefficient. To do a good job here,
5264 * we need a sorted array of (x,y) item positions */
5265 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5267 /* compute the distance^2 to the destination */
5268 xdist = Destination.x - Position.x;
5269 ydist = Destination.y - Position.y;
5270 dist = xdist * xdist + ydist * ydist;
5272 /* remember the distance, and item if it's closer */
5273 if (dist < mindist)
5275 mindist = dist;
5276 nNearestItem = nItem;
5280 if (bWrap)
5282 nItem = 0;
5283 nLast = min(nStart + 1, infoPtr->nItemCount);
5284 bWrap = FALSE;
5285 goto again;
5288 return nNearestItem;
5291 /***
5292 * DESCRIPTION:
5293 * Searches for an item with specific characteristics.
5295 * PARAMETER(S):
5296 * [I] hwnd : window handle
5297 * [I] nStart : base item index
5298 * [I] lpFindInfo : item information to look for
5300 * RETURN:
5301 * SUCCESS : index of item
5302 * FAILURE : -1
5304 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5305 const LVFINDINFOA *lpFindInfo)
5307 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5308 LVFINDINFOW fiw;
5309 INT res;
5310 LPWSTR strW = NULL;
5312 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5313 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5314 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5315 textfreeT(strW, FALSE);
5316 return res;
5319 /***
5320 * DESCRIPTION:
5321 * Retrieves the background image of the listview control.
5323 * PARAMETER(S):
5324 * [I] infoPtr : valid pointer to the listview structure
5325 * [O] lpBkImage : background image attributes
5327 * RETURN:
5328 * SUCCESS : TRUE
5329 * FAILURE : FALSE
5331 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5332 /* { */
5333 /* FIXME (listview, "empty stub!\n"); */
5334 /* return FALSE; */
5335 /* } */
5337 /***
5338 * DESCRIPTION:
5339 * Retrieves column attributes.
5341 * PARAMETER(S):
5342 * [I] infoPtr : valid pointer to the listview structure
5343 * [I] nColumn : column index
5344 * [IO] lpColumn : column information
5345 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5346 * otherwise it is in fact a LPLVCOLUMNA
5348 * RETURN:
5349 * SUCCESS : TRUE
5350 * FAILURE : FALSE
5352 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5354 COLUMN_INFO *lpColumnInfo;
5355 HDITEMW hdi;
5357 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5358 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5360 /* initialize memory */
5361 ZeroMemory(&hdi, sizeof(hdi));
5363 if (lpColumn->mask & LVCF_TEXT)
5365 hdi.mask |= HDI_TEXT;
5366 hdi.pszText = lpColumn->pszText;
5367 hdi.cchTextMax = lpColumn->cchTextMax;
5370 if (lpColumn->mask & LVCF_IMAGE)
5371 hdi.mask |= HDI_IMAGE;
5373 if (lpColumn->mask & LVCF_ORDER)
5374 hdi.mask |= HDI_ORDER;
5376 if (lpColumn->mask & LVCF_SUBITEM)
5377 hdi.mask |= HDI_LPARAM;
5379 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5381 if (lpColumn->mask & LVCF_FMT)
5382 lpColumn->fmt = lpColumnInfo->fmt;
5384 if (lpColumn->mask & LVCF_WIDTH)
5385 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5387 if (lpColumn->mask & LVCF_IMAGE)
5388 lpColumn->iImage = hdi.iImage;
5390 if (lpColumn->mask & LVCF_ORDER)
5391 lpColumn->iOrder = hdi.iOrder;
5393 if (lpColumn->mask & LVCF_SUBITEM)
5394 lpColumn->iSubItem = hdi.lParam;
5396 return TRUE;
5400 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5402 TRACE("iCount=%d, lpiArray=%p\n", iCount, lpiArray);
5404 if (!lpiArray)
5405 return FALSE;
5407 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
5410 /***
5411 * DESCRIPTION:
5412 * Retrieves the column width.
5414 * PARAMETER(S):
5415 * [I] infoPtr : valid pointer to the listview structure
5416 * [I] int : column index
5418 * RETURN:
5419 * SUCCESS : column width
5420 * FAILURE : zero
5422 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5424 INT nColumnWidth = 0;
5425 HDITEMW hdItem;
5427 TRACE("nColumn=%d\n", nColumn);
5429 /* we have a 'column' in LIST and REPORT mode only */
5430 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5432 case LVS_LIST:
5433 nColumnWidth = infoPtr->nItemWidth;
5434 break;
5435 case LVS_REPORT:
5436 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5437 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5438 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5440 * TODO: should we do the same in LVM_GETCOLUMN?
5442 hdItem.mask = HDI_WIDTH;
5443 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5445 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5446 return 0;
5448 nColumnWidth = hdItem.cxy;
5449 break;
5452 TRACE("nColumnWidth=%d\n", nColumnWidth);
5453 return nColumnWidth;
5456 /***
5457 * DESCRIPTION:
5458 * In list or report display mode, retrieves the number of items that can fit
5459 * vertically in the visible area. In icon or small icon display mode,
5460 * retrieves the total number of visible items.
5462 * PARAMETER(S):
5463 * [I] infoPtr : valid pointer to the listview structure
5465 * RETURN:
5466 * Number of fully visible items.
5468 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5470 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5472 case LVS_ICON:
5473 case LVS_SMALLICON:
5474 return infoPtr->nItemCount;
5475 case LVS_REPORT:
5476 return LISTVIEW_GetCountPerColumn(infoPtr);
5477 case LVS_LIST:
5478 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5480 assert(FALSE);
5481 return 0;
5484 /***
5485 * DESCRIPTION:
5486 * Retrieves an image list handle.
5488 * PARAMETER(S):
5489 * [I] infoPtr : valid pointer to the listview structure
5490 * [I] nImageList : image list identifier
5492 * RETURN:
5493 * SUCCESS : image list handle
5494 * FAILURE : NULL
5496 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5498 switch (nImageList)
5500 case LVSIL_NORMAL: return infoPtr->himlNormal;
5501 case LVSIL_SMALL: return infoPtr->himlSmall;
5502 case LVSIL_STATE: return infoPtr->himlState;
5504 return NULL;
5507 /* LISTVIEW_GetISearchString */
5509 /***
5510 * DESCRIPTION:
5511 * Retrieves item attributes.
5513 * PARAMETER(S):
5514 * [I] hwnd : window handle
5515 * [IO] lpLVItem : item info
5516 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5517 * if FALSE, then lpLVItem is a LPLVITEMA.
5519 * NOTE:
5520 * This is the internal 'GetItem' interface -- it tries to
5521 * be smart and avoid text copies, if possible, by modifying
5522 * lpLVItem->pszText to point to the text string. Please note
5523 * that this is not always possible (e.g. OWNERDATA), so on
5524 * entry you *must* supply valid values for pszText, and cchTextMax.
5525 * The only difference to the documented interface is that upon
5526 * return, you should use *only* the lpLVItem->pszText, rather than
5527 * the buffer pointer you provided on input. Most code already does
5528 * that, so it's not a problem.
5529 * For the two cases when the text must be copied (that is,
5530 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5532 * RETURN:
5533 * SUCCESS : TRUE
5534 * FAILURE : FALSE
5536 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5538 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5539 NMLVDISPINFOW dispInfo;
5540 ITEM_INFO *lpItem;
5541 ITEMHDR* pItemHdr;
5542 HDPA hdpaSubItems;
5543 INT isubitem;
5545 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5547 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5548 return FALSE;
5550 if (lpLVItem->mask == 0) return TRUE;
5552 /* make a local copy */
5553 isubitem = lpLVItem->iSubItem;
5555 /* a quick optimization if all we're asked is the focus state
5556 * these queries are worth optimising since they are common,
5557 * and can be answered in constant time, without the heavy accesses */
5558 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5559 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5561 lpLVItem->state = 0;
5562 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5563 lpLVItem->state |= LVIS_FOCUSED;
5564 return TRUE;
5567 ZeroMemory(&dispInfo, sizeof(dispInfo));
5569 /* if the app stores all the data, handle it separately */
5570 if (infoPtr->dwStyle & LVS_OWNERDATA)
5572 dispInfo.item.state = 0;
5574 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5575 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
5576 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
5578 UINT mask = lpLVItem->mask;
5580 /* NOTE: copy only fields which we _know_ are initialized, some apps
5581 * depend on the uninitialized fields being 0 */
5582 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5583 dispInfo.item.iItem = lpLVItem->iItem;
5584 dispInfo.item.iSubItem = isubitem;
5585 if (lpLVItem->mask & LVIF_TEXT)
5587 if (lpLVItem->mask & LVIF_NORECOMPUTE)
5588 /* reset mask */
5589 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
5590 else
5592 dispInfo.item.pszText = lpLVItem->pszText;
5593 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5596 if (lpLVItem->mask & LVIF_STATE)
5597 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5598 /* could be zeroed on LVIF_NORECOMPUTE case */
5599 if (dispInfo.item.mask != 0)
5601 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5602 dispInfo.item.stateMask = lpLVItem->stateMask;
5603 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5605 /* full size structure expected - _WIN32IE >= 0x560 */
5606 *lpLVItem = dispInfo.item;
5608 else if (lpLVItem->mask & LVIF_INDENT)
5610 /* indent member expected - _WIN32IE >= 0x300 */
5611 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5613 else
5615 /* minimal structure expected */
5616 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5618 lpLVItem->mask = mask;
5619 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5623 /* make sure lParam is zeroed out */
5624 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5626 /* callback marked pointer required here */
5627 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
5628 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
5630 /* we store only a little state, so if we're not asked, we're done */
5631 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5633 /* if focus is handled by us, report it */
5634 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5636 lpLVItem->state &= ~LVIS_FOCUSED;
5637 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5638 lpLVItem->state |= LVIS_FOCUSED;
5641 /* and do the same for selection, if we handle it */
5642 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5644 lpLVItem->state &= ~LVIS_SELECTED;
5645 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5646 lpLVItem->state |= LVIS_SELECTED;
5649 return TRUE;
5652 /* find the item and subitem structures before we proceed */
5653 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5654 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5655 assert (lpItem);
5657 if (isubitem)
5659 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5660 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5661 if (!lpSubItem)
5663 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5664 isubitem = 0;
5667 else
5668 pItemHdr = &lpItem->hdr;
5670 /* Do we need to query the state from the app? */
5671 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5673 dispInfo.item.mask |= LVIF_STATE;
5674 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5677 /* Do we need to enquire about the image? */
5678 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5679 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5681 dispInfo.item.mask |= LVIF_IMAGE;
5682 dispInfo.item.iImage = I_IMAGECALLBACK;
5685 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5686 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
5687 !is_textW(pItemHdr->pszText))
5689 dispInfo.item.mask |= LVIF_TEXT;
5690 dispInfo.item.pszText = lpLVItem->pszText;
5691 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5692 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5693 *dispInfo.item.pszText = '\0';
5696 /* If we don't have all the requested info, query the application */
5697 if (dispInfo.item.mask != 0)
5699 dispInfo.item.iItem = lpLVItem->iItem;
5700 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5701 dispInfo.item.lParam = lpItem->lParam;
5702 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5703 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5706 /* we should not store values for subitems */
5707 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5709 /* Now, handle the iImage field */
5710 if (dispInfo.item.mask & LVIF_IMAGE)
5712 lpLVItem->iImage = dispInfo.item.iImage;
5713 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5714 pItemHdr->iImage = dispInfo.item.iImage;
5716 else if (lpLVItem->mask & LVIF_IMAGE)
5718 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5719 lpLVItem->iImage = pItemHdr->iImage;
5720 else
5721 lpLVItem->iImage = 0;
5724 /* The pszText field */
5725 if (dispInfo.item.mask & LVIF_TEXT)
5727 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5728 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5730 lpLVItem->pszText = dispInfo.item.pszText;
5732 else if (lpLVItem->mask & LVIF_TEXT)
5734 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
5735 if (isW || !is_textW(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
5736 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5739 /* Next is the lParam field */
5740 if (dispInfo.item.mask & LVIF_PARAM)
5742 lpLVItem->lParam = dispInfo.item.lParam;
5743 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5744 lpItem->lParam = dispInfo.item.lParam;
5746 else if (lpLVItem->mask & LVIF_PARAM)
5747 lpLVItem->lParam = lpItem->lParam;
5749 /* if this is a subitem, we're done */
5750 if (isubitem) return TRUE;
5752 /* ... the state field (this one is different due to uCallbackmask) */
5753 if (lpLVItem->mask & LVIF_STATE)
5755 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5756 if (dispInfo.item.mask & LVIF_STATE)
5758 lpLVItem->state &= ~dispInfo.item.stateMask;
5759 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5761 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5763 lpLVItem->state &= ~LVIS_FOCUSED;
5764 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5765 lpLVItem->state |= LVIS_FOCUSED;
5767 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5769 lpLVItem->state &= ~LVIS_SELECTED;
5770 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5771 lpLVItem->state |= LVIS_SELECTED;
5775 /* and last, but not least, the indent field */
5776 if (lpLVItem->mask & LVIF_INDENT)
5777 lpLVItem->iIndent = lpItem->iIndent;
5779 return TRUE;
5782 /***
5783 * DESCRIPTION:
5784 * Retrieves item attributes.
5786 * PARAMETER(S):
5787 * [I] hwnd : window handle
5788 * [IO] lpLVItem : item info
5789 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5790 * if FALSE, then lpLVItem is a LPLVITEMA.
5792 * NOTE:
5793 * This is the external 'GetItem' interface -- it properly copies
5794 * the text in the provided buffer.
5796 * RETURN:
5797 * SUCCESS : TRUE
5798 * FAILURE : FALSE
5800 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5802 LPWSTR pszText;
5803 BOOL bResult;
5805 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5806 return FALSE;
5808 pszText = lpLVItem->pszText;
5809 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5810 if (bResult && lpLVItem->pszText != pszText)
5812 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
5813 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5814 else
5815 pszText = LPSTR_TEXTCALLBACKW;
5817 lpLVItem->pszText = pszText;
5819 return bResult;
5823 /***
5824 * DESCRIPTION:
5825 * Retrieves the position (upper-left) of the listview control item.
5826 * Note that for LVS_ICON style, the upper-left is that of the icon
5827 * and not the bounding box.
5829 * PARAMETER(S):
5830 * [I] infoPtr : valid pointer to the listview structure
5831 * [I] nItem : item index
5832 * [O] lpptPosition : coordinate information
5834 * RETURN:
5835 * SUCCESS : TRUE
5836 * FAILURE : FALSE
5838 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5840 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5841 POINT Origin;
5843 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5845 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5847 LISTVIEW_GetOrigin(infoPtr, &Origin);
5848 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5850 if (uView == LVS_ICON)
5852 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5853 lpptPosition->y += ICON_TOP_PADDING;
5855 lpptPosition->x += Origin.x;
5856 lpptPosition->y += Origin.y;
5858 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5859 return TRUE;
5863 /***
5864 * DESCRIPTION:
5865 * Retrieves the bounding rectangle for a listview control item.
5867 * PARAMETER(S):
5868 * [I] infoPtr : valid pointer to the listview structure
5869 * [I] nItem : item index
5870 * [IO] lprc : bounding rectangle coordinates
5871 * lprc->left specifies the portion of the item for which the bounding
5872 * rectangle will be retrieved.
5874 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5875 * including the icon and label.
5877 * * For LVS_ICON
5878 * * Experiment shows that native control returns:
5879 * * width = min (48, length of text line)
5880 * * .left = position.x - (width - iconsize.cx)/2
5881 * * .right = .left + width
5882 * * height = #lines of text * ntmHeight + icon height + 8
5883 * * .top = position.y - 2
5884 * * .bottom = .top + height
5885 * * separation between items .y = itemSpacing.cy - height
5886 * * .x = itemSpacing.cx - width
5887 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5889 * * For LVS_ICON
5890 * * Experiment shows that native control returns:
5891 * * width = iconSize.cx + 16
5892 * * .left = position.x - (width - iconsize.cx)/2
5893 * * .right = .left + width
5894 * * height = iconSize.cy + 4
5895 * * .top = position.y - 2
5896 * * .bottom = .top + height
5897 * * separation between items .y = itemSpacing.cy - height
5898 * * .x = itemSpacing.cx - width
5899 * LVIR_LABEL Returns the bounding rectangle of the item text.
5901 * * For LVS_ICON
5902 * * Experiment shows that native control returns:
5903 * * width = text length
5904 * * .left = position.x - width/2
5905 * * .right = .left + width
5906 * * height = ntmH * linecount + 2
5907 * * .top = position.y + iconSize.cy + 6
5908 * * .bottom = .top + height
5909 * * separation between items .y = itemSpacing.cy - height
5910 * * .x = itemSpacing.cx - width
5911 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5912 * rectangles, but excludes columns in report view.
5914 * RETURN:
5915 * SUCCESS : TRUE
5916 * FAILURE : FALSE
5918 * NOTES
5919 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5920 * upon whether the window has the focus currently and on whether the item
5921 * is the one with the focus. Ensure that the control's record of which
5922 * item has the focus agrees with the items' records.
5924 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5926 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5927 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5928 BOOL doLabel = TRUE, oversizedBox = FALSE;
5929 POINT Position, Origin;
5930 LVITEMW lvItem;
5931 INT type;
5933 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5935 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5937 LISTVIEW_GetOrigin(infoPtr, &Origin);
5938 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5940 /* Be smart and try to figure out the minimum we have to do */
5941 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5942 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5943 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5944 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5945 oversizedBox = TRUE;
5947 /* get what we need from the item before hand, so we make
5948 * only one request. This can speed up things, if data
5949 * is stored on the app side */
5950 lvItem.mask = 0;
5951 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5952 if (doLabel) lvItem.mask |= LVIF_TEXT;
5953 lvItem.iItem = nItem;
5954 lvItem.iSubItem = 0;
5955 lvItem.pszText = szDispText;
5956 lvItem.cchTextMax = DISP_TEXT_SIZE;
5957 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5958 /* we got the state already up, simulate it here, to avoid a reget */
5959 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5961 lvItem.mask |= LVIF_STATE;
5962 lvItem.stateMask = LVIS_FOCUSED;
5963 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5966 type = lprc->left;
5967 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5968 lprc->left = LVIR_BOUNDS;
5969 switch(lprc->left)
5971 case LVIR_ICON:
5972 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5973 break;
5975 case LVIR_LABEL:
5976 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5977 break;
5979 case LVIR_BOUNDS:
5980 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5981 break;
5983 case LVIR_SELECTBOUNDS:
5984 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5985 break;
5987 default:
5988 WARN("Unknown value: %d\n", lprc->left);
5989 return FALSE;
5992 if ((uView == LVS_REPORT) && (type == LVIR_BOUNDS))
5993 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
5994 else
5995 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5997 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5999 return TRUE;
6002 /***
6003 * DESCRIPTION:
6004 * Retrieves the spacing between listview control items.
6006 * PARAMETER(S):
6007 * [I] infoPtr : valid pointer to the listview structure
6008 * [IO] lprc : rectangle to receive the output
6009 * on input, lprc->top = nSubItem
6010 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
6012 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
6013 * not only those of the first column.
6014 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
6016 * RETURN:
6017 * TRUE: success
6018 * FALSE: failure
6020 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
6022 POINT Position;
6023 LVITEMW lvItem;
6024 INT nColumn;
6026 if (!lprc) return FALSE;
6028 nColumn = lprc->top;
6030 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
6031 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
6032 if (lprc->top == 0)
6033 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
6035 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
6037 /* special case for header items */
6038 if (nItem == -1)
6040 if (lprc->left != LVIR_BOUNDS)
6042 FIXME("Only LVIR_BOUNDS is implemented for header, got %d\n", lprc->left);
6043 return FALSE;
6046 if (infoPtr->hwndHeader)
6047 return SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)lprc);
6048 else
6050 memset(lprc, 0, sizeof(RECT));
6051 return TRUE;
6055 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
6057 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6059 lvItem.mask = 0;
6060 lvItem.iItem = nItem;
6061 lvItem.iSubItem = nColumn;
6063 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
6064 switch(lprc->left)
6066 case LVIR_ICON:
6067 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
6068 break;
6070 case LVIR_LABEL:
6071 case LVIR_BOUNDS:
6072 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6073 break;
6075 default:
6076 ERR("Unknown bounds=%d\n", lprc->left);
6077 return FALSE;
6080 OffsetRect(lprc, 0, Position.y);
6081 return TRUE;
6085 /***
6086 * DESCRIPTION:
6087 * Retrieves the width of a label.
6089 * PARAMETER(S):
6090 * [I] infoPtr : valid pointer to the listview structure
6092 * RETURN:
6093 * SUCCESS : string width (in pixels)
6094 * FAILURE : zero
6096 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6098 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6099 LVITEMW lvItem;
6101 TRACE("(nItem=%d)\n", nItem);
6103 lvItem.mask = LVIF_TEXT;
6104 lvItem.iItem = nItem;
6105 lvItem.iSubItem = 0;
6106 lvItem.pszText = szDispText;
6107 lvItem.cchTextMax = DISP_TEXT_SIZE;
6108 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6110 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6113 /***
6114 * DESCRIPTION:
6115 * Retrieves the spacing between listview control items.
6117 * PARAMETER(S):
6118 * [I] infoPtr : valid pointer to the listview structure
6119 * [I] bSmall : flag for small or large icon
6121 * RETURN:
6122 * Horizontal + vertical spacing
6124 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6126 LONG lResult;
6128 if (!bSmall)
6130 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6132 else
6134 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6135 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6136 else
6137 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6139 return lResult;
6142 /***
6143 * DESCRIPTION:
6144 * Retrieves the state of a listview control item.
6146 * PARAMETER(S):
6147 * [I] infoPtr : valid pointer to the listview structure
6148 * [I] nItem : item index
6149 * [I] uMask : state mask
6151 * RETURN:
6152 * State specified by the mask.
6154 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6156 LVITEMW lvItem;
6158 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6160 lvItem.iItem = nItem;
6161 lvItem.iSubItem = 0;
6162 lvItem.mask = LVIF_STATE;
6163 lvItem.stateMask = uMask;
6164 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6166 return lvItem.state & uMask;
6169 /***
6170 * DESCRIPTION:
6171 * Retrieves the text of a listview control item or subitem.
6173 * PARAMETER(S):
6174 * [I] hwnd : window handle
6175 * [I] nItem : item index
6176 * [IO] lpLVItem : item information
6177 * [I] isW : TRUE if lpLVItem is Unicode
6179 * RETURN:
6180 * SUCCESS : string length
6181 * FAILURE : 0
6183 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6185 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6187 lpLVItem->mask = LVIF_TEXT;
6188 lpLVItem->iItem = nItem;
6189 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6191 return textlenT(lpLVItem->pszText, isW);
6194 /***
6195 * DESCRIPTION:
6196 * Searches for an item based on properties + relationships.
6198 * PARAMETER(S):
6199 * [I] infoPtr : valid pointer to the listview structure
6200 * [I] nItem : item index
6201 * [I] uFlags : relationship flag
6203 * RETURN:
6204 * SUCCESS : item index
6205 * FAILURE : -1
6207 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6209 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6210 UINT uMask = 0;
6211 LVFINDINFOW lvFindInfo;
6212 INT nCountPerColumn;
6213 INT nCountPerRow;
6214 INT i;
6216 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6217 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6219 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6221 if (uFlags & LVNI_CUT)
6222 uMask |= LVIS_CUT;
6224 if (uFlags & LVNI_DROPHILITED)
6225 uMask |= LVIS_DROPHILITED;
6227 if (uFlags & LVNI_FOCUSED)
6228 uMask |= LVIS_FOCUSED;
6230 if (uFlags & LVNI_SELECTED)
6231 uMask |= LVIS_SELECTED;
6233 /* if we're asked for the focused item, that's only one,
6234 * so it's worth optimizing */
6235 if (uFlags & LVNI_FOCUSED)
6237 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6238 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6241 if (uFlags & LVNI_ABOVE)
6243 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6245 while (nItem >= 0)
6247 nItem--;
6248 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6249 return nItem;
6252 else
6254 /* Special case for autoarrange - move 'til the top of a list */
6255 if (is_autoarrange(infoPtr))
6257 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6258 while (nItem - nCountPerRow >= 0)
6260 nItem -= nCountPerRow;
6261 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6262 return nItem;
6264 return -1;
6266 lvFindInfo.flags = LVFI_NEARESTXY;
6267 lvFindInfo.vkDirection = VK_UP;
6268 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6269 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6271 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6272 return nItem;
6276 else if (uFlags & LVNI_BELOW)
6278 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6280 while (nItem < infoPtr->nItemCount)
6282 nItem++;
6283 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6284 return nItem;
6287 else
6289 /* Special case for autoarrange - move 'til the bottom of a list */
6290 if (is_autoarrange(infoPtr))
6292 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6293 while (nItem + nCountPerRow < infoPtr->nItemCount )
6295 nItem += nCountPerRow;
6296 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6297 return nItem;
6299 return -1;
6301 lvFindInfo.flags = LVFI_NEARESTXY;
6302 lvFindInfo.vkDirection = VK_DOWN;
6303 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6304 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6306 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6307 return nItem;
6311 else if (uFlags & LVNI_TOLEFT)
6313 if (uView == LVS_LIST)
6315 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6316 while (nItem - nCountPerColumn >= 0)
6318 nItem -= nCountPerColumn;
6319 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6320 return nItem;
6323 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6325 /* Special case for autoarrange - move 'til the beginning of a row */
6326 if (is_autoarrange(infoPtr))
6328 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6329 while (nItem % nCountPerRow > 0)
6331 nItem --;
6332 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6333 return nItem;
6335 return -1;
6337 lvFindInfo.flags = LVFI_NEARESTXY;
6338 lvFindInfo.vkDirection = VK_LEFT;
6339 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6340 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6342 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6343 return nItem;
6347 else if (uFlags & LVNI_TORIGHT)
6349 if (uView == LVS_LIST)
6351 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6352 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6354 nItem += nCountPerColumn;
6355 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6356 return nItem;
6359 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6361 /* Special case for autoarrange - move 'til the end of a row */
6362 if (is_autoarrange(infoPtr))
6364 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6365 while (nItem % nCountPerRow < nCountPerRow - 1 )
6367 nItem ++;
6368 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6369 return nItem;
6371 return -1;
6373 lvFindInfo.flags = LVFI_NEARESTXY;
6374 lvFindInfo.vkDirection = VK_RIGHT;
6375 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
6376 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
6378 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6379 return nItem;
6383 else
6385 nItem++;
6387 /* search by index */
6388 for (i = nItem; i < infoPtr->nItemCount; i++)
6390 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6391 return i;
6395 return -1;
6398 /* LISTVIEW_GetNumberOfWorkAreas */
6400 /***
6401 * DESCRIPTION:
6402 * Retrieves the origin coordinates when in icon or small icon display mode.
6404 * PARAMETER(S):
6405 * [I] infoPtr : valid pointer to the listview structure
6406 * [O] lpptOrigin : coordinate information
6408 * RETURN:
6409 * None.
6411 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6413 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6414 INT nHorzPos = 0, nVertPos = 0;
6415 SCROLLINFO scrollInfo;
6417 scrollInfo.cbSize = sizeof(SCROLLINFO);
6418 scrollInfo.fMask = SIF_POS;
6420 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6421 nHorzPos = scrollInfo.nPos;
6422 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6423 nVertPos = scrollInfo.nPos;
6425 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6427 lpptOrigin->x = infoPtr->rcList.left;
6428 lpptOrigin->y = infoPtr->rcList.top;
6429 if (uView == LVS_LIST)
6430 nHorzPos *= infoPtr->nItemWidth;
6431 else if (uView == LVS_REPORT)
6432 nVertPos *= infoPtr->nItemHeight;
6434 lpptOrigin->x -= nHorzPos;
6435 lpptOrigin->y -= nVertPos;
6437 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6440 /***
6441 * DESCRIPTION:
6442 * Retrieves the width of a string.
6444 * PARAMETER(S):
6445 * [I] hwnd : window handle
6446 * [I] lpszText : text string to process
6447 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6449 * RETURN:
6450 * SUCCESS : string width (in pixels)
6451 * FAILURE : zero
6453 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6455 SIZE stringSize;
6457 stringSize.cx = 0;
6458 if (is_textT(lpszText, isW))
6460 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6461 HDC hdc = GetDC(infoPtr->hwndSelf);
6462 HFONT hOldFont = SelectObject(hdc, hFont);
6464 if (isW)
6465 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6466 else
6467 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6468 SelectObject(hdc, hOldFont);
6469 ReleaseDC(infoPtr->hwndSelf, hdc);
6471 return stringSize.cx;
6474 /***
6475 * DESCRIPTION:
6476 * Determines which listview item is located at the specified position.
6478 * PARAMETER(S):
6479 * [I] infoPtr : valid pointer to the listview structure
6480 * [IO] lpht : hit test information
6481 * [I] subitem : fill out iSubItem.
6482 * [I] select : return the index only if the hit selects the item
6484 * NOTE:
6485 * (mm 20001022): We must not allow iSubItem to be touched, for
6486 * an app might pass only a structure with space up to iItem!
6487 * (MS Office 97 does that for instance in the file open dialog)
6489 * RETURN:
6490 * SUCCESS : item index
6491 * FAILURE : -1
6493 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6495 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6496 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6497 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6498 POINT Origin, Position, opt;
6499 LVITEMW lvItem;
6500 ITERATOR i;
6501 INT iItem;
6503 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6505 lpht->flags = 0;
6506 lpht->iItem = -1;
6507 if (subitem) lpht->iSubItem = 0;
6509 if (infoPtr->rcList.left > lpht->pt.x)
6510 lpht->flags |= LVHT_TOLEFT;
6511 else if (infoPtr->rcList.right < lpht->pt.x)
6512 lpht->flags |= LVHT_TORIGHT;
6514 if (infoPtr->rcList.top > lpht->pt.y)
6515 lpht->flags |= LVHT_ABOVE;
6516 else if (infoPtr->rcList.bottom < lpht->pt.y)
6517 lpht->flags |= LVHT_BELOW;
6519 TRACE("lpht->flags=0x%x\n", lpht->flags);
6520 if (lpht->flags) return -1;
6522 lpht->flags |= LVHT_NOWHERE;
6524 LISTVIEW_GetOrigin(infoPtr, &Origin);
6526 /* first deal with the large items */
6527 rcSearch.left = lpht->pt.x;
6528 rcSearch.top = lpht->pt.y;
6529 rcSearch.right = rcSearch.left + 1;
6530 rcSearch.bottom = rcSearch.top + 1;
6532 iterator_frameditems(&i, infoPtr, &rcSearch);
6533 iterator_next(&i); /* go to first item in the sequence */
6534 iItem = i.nItem;
6535 iterator_destroy(&i);
6537 TRACE("lpht->iItem=%d\n", iItem);
6538 if (iItem == -1) return -1;
6540 if (uView == LVS_REPORT && subitem)
6542 RECT bounds, *pRect;
6543 INT j;
6545 /* for top/bottom only */
6546 bounds.left = LVIR_BOUNDS;
6547 LISTVIEW_GetItemRect(infoPtr, iItem, &bounds);
6549 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6551 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
6552 bounds.left = pRect->left;
6553 bounds.right = pRect->right;
6555 if (PtInRect(&bounds, lpht->pt))
6557 lpht->iSubItem = j;
6558 break;
6561 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
6564 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6565 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6566 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6567 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6568 lvItem.iItem = iItem;
6569 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
6570 lvItem.pszText = szDispText;
6571 lvItem.cchTextMax = DISP_TEXT_SIZE;
6572 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6573 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6575 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6576 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6577 opt.x = lpht->pt.x - Position.x - Origin.x;
6578 opt.y = lpht->pt.y - Position.y - Origin.y;
6580 if (uView == LVS_REPORT)
6581 rcBounds = rcBox;
6582 else
6584 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6585 UnionRect(&rcBounds, &rcBounds, &rcState);
6587 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6588 if (!PtInRect(&rcBounds, opt)) return -1;
6590 if (PtInRect(&rcIcon, opt))
6591 lpht->flags |= LVHT_ONITEMICON;
6592 else if (PtInRect(&rcLabel, opt))
6593 lpht->flags |= LVHT_ONITEMLABEL;
6594 else if (infoPtr->himlState && PtInRect(&rcState, opt))
6595 lpht->flags |= LVHT_ONITEMSTATEICON;
6596 /* special case for LVS_EX_FULLROWSELECT */
6597 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
6598 !(lpht->flags & LVHT_ONITEM))
6600 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
6602 if (lpht->flags & LVHT_ONITEM)
6603 lpht->flags &= ~LVHT_NOWHERE;
6604 TRACE("lpht->flags=0x%x\n", lpht->flags);
6606 if (select && !(uView == LVS_REPORT &&
6607 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6608 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6610 if (uView == LVS_REPORT)
6612 /* get main item bounds */
6613 lvItem.iSubItem = 0;
6614 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6615 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6616 UnionRect(&rcBounds, &rcBounds, &rcState);
6618 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6620 return lpht->iItem = iItem;
6623 /***
6624 * DESCRIPTION:
6625 * Inserts a new item in the listview control.
6627 * PARAMETER(S):
6628 * [I] infoPtr : valid pointer to the listview structure
6629 * [I] lpLVItem : item information
6630 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6632 * RETURN:
6633 * SUCCESS : new item index
6634 * FAILURE : -1
6636 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6638 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6639 INT nItem;
6640 HDPA hdpaSubItems;
6641 NMLISTVIEW nmlv;
6642 ITEM_INFO *lpItem;
6643 BOOL is_sorted, has_changed;
6644 LVITEMW item;
6645 HWND hwndSelf = infoPtr->hwndSelf;
6647 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6649 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6651 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6652 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6654 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6656 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6658 /* insert item in listview control data structure */
6659 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6660 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6662 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6663 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6665 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6667 /* calculate new item index */
6668 if (is_sorted)
6670 HDPA hItem;
6671 ITEM_INFO *item_s;
6672 INT i = 0, cmpv;
6674 while (i < infoPtr->nItemCount)
6676 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
6677 item_s = (ITEM_INFO*)DPA_GetPtr(hItem, 0);
6679 cmpv = textcmpWT(item_s->hdr.pszText, lpLVItem->pszText, TRUE);
6680 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
6682 if (cmpv >= 0) break;
6683 i++;
6685 nItem = i;
6687 else
6688 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
6690 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6691 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6692 if (nItem == -1) goto fail;
6693 infoPtr->nItemCount++;
6695 /* shift indices first so they don't get tangled */
6696 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6698 /* set the item attributes */
6699 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6701 /* full size structure expected - _WIN32IE >= 0x560 */
6702 item = *lpLVItem;
6704 else if (lpLVItem->mask & LVIF_INDENT)
6706 /* indent member expected - _WIN32IE >= 0x300 */
6707 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6709 else
6711 /* minimal structure expected */
6712 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6714 item.iItem = nItem;
6715 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6717 item.mask |= LVIF_STATE;
6718 item.stateMask |= LVIS_STATEIMAGEMASK;
6719 item.state &= ~LVIS_STATEIMAGEMASK;
6720 item.state |= INDEXTOSTATEIMAGEMASK(1);
6722 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6724 /* make room for the position, if we are in the right mode */
6725 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6727 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6728 goto undo;
6729 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6731 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6732 goto undo;
6736 /* send LVN_INSERTITEM notification */
6737 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6738 nmlv.iItem = nItem;
6739 nmlv.lParam = lpItem->lParam;
6740 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6741 if (!IsWindow(hwndSelf))
6742 return -1;
6744 /* align items (set position of each item) */
6745 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6747 POINT pt;
6749 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6750 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6751 else
6752 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6754 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6757 /* now is the invalidation fun */
6758 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6759 return nItem;
6761 undo:
6762 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6763 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6764 infoPtr->nItemCount--;
6765 fail:
6766 DPA_DeletePtr(hdpaSubItems, 0);
6767 DPA_Destroy (hdpaSubItems);
6768 Free (lpItem);
6769 return -1;
6772 /***
6773 * DESCRIPTION:
6774 * Redraws a range of items.
6776 * PARAMETER(S):
6777 * [I] infoPtr : valid pointer to the listview structure
6778 * [I] nFirst : first item
6779 * [I] nLast : last item
6781 * RETURN:
6782 * SUCCESS : TRUE
6783 * FAILURE : FALSE
6785 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6787 INT i;
6789 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6790 max(nFirst, nLast) >= infoPtr->nItemCount)
6791 return FALSE;
6793 for (i = nFirst; i <= nLast; i++)
6794 LISTVIEW_InvalidateItem(infoPtr, i);
6796 return TRUE;
6799 /***
6800 * DESCRIPTION:
6801 * Scroll the content of a listview.
6803 * PARAMETER(S):
6804 * [I] infoPtr : valid pointer to the listview structure
6805 * [I] dx : horizontal scroll amount in pixels
6806 * [I] dy : vertical scroll amount in pixels
6808 * RETURN:
6809 * SUCCESS : TRUE
6810 * FAILURE : FALSE
6812 * COMMENTS:
6813 * If the control is in report mode (LVS_REPORT) the control can
6814 * be scrolled only in line increments. "dy" will be rounded to the
6815 * nearest number of pixels that are a whole line. Ex: if line height
6816 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6817 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6819 * For: (per experimentation with native control and CSpy ListView)
6820 * LVS_ICON dy=1 = 1 pixel (vertical only)
6821 * dx ignored
6822 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6823 * dx ignored
6824 * LVS_LIST dx=1 = 1 column (horizontal only)
6825 * but will only scroll 1 column per message
6826 * no matter what the value.
6827 * dy must be 0 or FALSE returned.
6828 * LVS_REPORT dx=1 = 1 pixel
6829 * dy= see above
6832 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6834 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6835 case LVS_REPORT:
6836 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6837 dy /= infoPtr->nItemHeight;
6838 break;
6839 case LVS_LIST:
6840 if (dy != 0) return FALSE;
6841 break;
6842 default: /* icon */
6843 dx = 0;
6844 break;
6847 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6848 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6850 return TRUE;
6853 /***
6854 * DESCRIPTION:
6855 * Sets the background color.
6857 * PARAMETER(S):
6858 * [I] infoPtr : valid pointer to the listview structure
6859 * [I] clrBk : background color
6861 * RETURN:
6862 * SUCCESS : TRUE
6863 * FAILURE : FALSE
6865 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6867 TRACE("(clrBk=%x)\n", clrBk);
6869 if(infoPtr->clrBk != clrBk) {
6870 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6871 infoPtr->clrBk = clrBk;
6872 if (clrBk == CLR_NONE)
6873 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6874 else
6875 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6876 LISTVIEW_InvalidateList(infoPtr);
6879 return TRUE;
6882 /* LISTVIEW_SetBkImage */
6884 /*** Helper for {Insert,Set}ColumnT *only* */
6885 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6886 const LVCOLUMNW *lpColumn, BOOL isW)
6888 if (lpColumn->mask & LVCF_FMT)
6890 /* format member is valid */
6891 lphdi->mask |= HDI_FORMAT;
6893 /* set text alignment (leftmost column must be left-aligned) */
6894 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6895 lphdi->fmt |= HDF_LEFT;
6896 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6897 lphdi->fmt |= HDF_RIGHT;
6898 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6899 lphdi->fmt |= HDF_CENTER;
6901 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6902 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6904 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6906 lphdi->fmt |= HDF_IMAGE;
6907 lphdi->iImage = I_IMAGECALLBACK;
6911 if (lpColumn->mask & LVCF_WIDTH)
6913 lphdi->mask |= HDI_WIDTH;
6914 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6916 /* make it fill the remainder of the controls width */
6917 RECT rcHeader;
6918 INT item_index;
6920 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6922 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6923 lphdi->cxy += rcHeader.right - rcHeader.left;
6926 /* retrieve the layout of the header */
6927 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6928 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6930 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6932 else
6933 lphdi->cxy = lpColumn->cx;
6936 if (lpColumn->mask & LVCF_TEXT)
6938 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6939 lphdi->fmt |= HDF_STRING;
6940 lphdi->pszText = lpColumn->pszText;
6941 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6944 if (lpColumn->mask & LVCF_IMAGE)
6946 lphdi->mask |= HDI_IMAGE;
6947 lphdi->iImage = lpColumn->iImage;
6950 if (lpColumn->mask & LVCF_ORDER)
6952 lphdi->mask |= HDI_ORDER;
6953 lphdi->iOrder = lpColumn->iOrder;
6958 /***
6959 * DESCRIPTION:
6960 * Inserts a new column.
6962 * PARAMETER(S):
6963 * [I] infoPtr : valid pointer to the listview structure
6964 * [I] nColumn : column index
6965 * [I] lpColumn : column information
6966 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6968 * RETURN:
6969 * SUCCESS : new column index
6970 * FAILURE : -1
6972 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6973 const LVCOLUMNW *lpColumn, BOOL isW)
6975 COLUMN_INFO *lpColumnInfo;
6976 INT nNewColumn;
6977 HDITEMW hdi;
6978 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6980 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6982 if (!lpColumn || nColumn < 0) return -1;
6983 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6985 ZeroMemory(&hdi, sizeof(HDITEMW));
6986 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6989 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6990 * (can be seen in SPY) otherwise column never gets added.
6992 if (!(lpColumn->mask & LVCF_WIDTH)) {
6993 hdi.mask |= HDI_WIDTH;
6994 hdi.cxy = 10;
6998 * when the iSubItem is available Windows copies it to the header lParam. It seems
6999 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
7001 if (lpColumn->mask & LVCF_SUBITEM)
7003 hdi.mask |= HDI_LPARAM;
7004 hdi.lParam = lpColumn->iSubItem;
7007 /* create header if not present */
7008 LISTVIEW_CreateHeader(infoPtr);
7009 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
7010 (LVS_REPORT == uView) && (WS_VISIBLE & infoPtr->dwStyle))
7012 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7015 /* insert item in header control */
7016 nNewColumn = SendMessageW(infoPtr->hwndHeader,
7017 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
7018 (WPARAM)nColumn, (LPARAM)&hdi);
7019 if (nNewColumn == -1) return -1;
7020 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
7022 /* create our own column info */
7023 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
7024 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
7026 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
7027 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
7028 goto fail;
7030 /* now we have to actually adjust the data */
7031 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
7033 SUBITEM_INFO *lpSubItem;
7034 HDPA hdpaSubItems;
7035 INT nItem, i;
7037 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
7039 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
7040 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
7042 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
7043 if (lpSubItem->iSubItem >= nNewColumn)
7044 lpSubItem->iSubItem++;
7049 /* make space for the new column */
7050 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
7051 LISTVIEW_UpdateItemSize(infoPtr);
7053 return nNewColumn;
7055 fail:
7056 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
7057 if (lpColumnInfo)
7059 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
7060 Free(lpColumnInfo);
7062 return -1;
7065 /***
7066 * DESCRIPTION:
7067 * Sets the attributes of a header item.
7069 * PARAMETER(S):
7070 * [I] infoPtr : valid pointer to the listview structure
7071 * [I] nColumn : column index
7072 * [I] lpColumn : column attributes
7073 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
7075 * RETURN:
7076 * SUCCESS : TRUE
7077 * FAILURE : FALSE
7079 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7080 const LVCOLUMNW *lpColumn, BOOL isW)
7082 HDITEMW hdi, hdiget;
7083 BOOL bResult;
7085 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7087 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7089 ZeroMemory(&hdi, sizeof(HDITEMW));
7090 if (lpColumn->mask & LVCF_FMT)
7092 hdi.mask |= HDI_FORMAT;
7093 hdiget.mask = HDI_FORMAT;
7094 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7095 hdi.fmt = hdiget.fmt & HDF_STRING;
7097 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7099 /* set header item attributes */
7100 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7101 if (!bResult) return FALSE;
7103 if (lpColumn->mask & LVCF_FMT)
7105 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7106 int oldFmt = lpColumnInfo->fmt;
7108 lpColumnInfo->fmt = lpColumn->fmt;
7109 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7111 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7112 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7116 return TRUE;
7119 /***
7120 * DESCRIPTION:
7121 * Sets the column order array
7123 * PARAMETERS:
7124 * [I] infoPtr : valid pointer to the listview structure
7125 * [I] iCount : number of elements in column order array
7126 * [I] lpiArray : pointer to column order array
7128 * RETURN:
7129 * SUCCESS : TRUE
7130 * FAILURE : FALSE
7132 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7134 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7136 if (!lpiArray)
7137 return FALSE;
7139 return TRUE;
7143 /***
7144 * DESCRIPTION:
7145 * Sets the width of a column
7147 * PARAMETERS:
7148 * [I] infoPtr : valid pointer to the listview structure
7149 * [I] nColumn : column index
7150 * [I] cx : column width
7152 * RETURN:
7153 * SUCCESS : TRUE
7154 * FAILURE : FALSE
7156 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7158 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7159 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7160 INT max_cx = 0;
7161 HDITEMW hdi;
7163 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7165 /* set column width only if in report or list mode */
7166 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7168 /* take care of invalid cx values */
7169 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7170 else if (uView == LVS_LIST && cx < 1) return FALSE;
7172 /* resize all columns if in LVS_LIST mode */
7173 if(uView == LVS_LIST)
7175 infoPtr->nItemWidth = cx;
7176 LISTVIEW_InvalidateList(infoPtr);
7177 return TRUE;
7180 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7182 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7184 INT nLabelWidth;
7185 LVITEMW lvItem;
7187 lvItem.mask = LVIF_TEXT;
7188 lvItem.iItem = 0;
7189 lvItem.iSubItem = nColumn;
7190 lvItem.pszText = szDispText;
7191 lvItem.cchTextMax = DISP_TEXT_SIZE;
7192 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7194 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7195 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7196 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7198 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7199 max_cx += infoPtr->iconSize.cx;
7200 max_cx += TRAILING_LABEL_PADDING;
7203 /* autosize based on listview items width */
7204 if(cx == LVSCW_AUTOSIZE)
7205 cx = max_cx;
7206 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7208 /* if iCol is the last column make it fill the remainder of the controls width */
7209 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7211 RECT rcHeader;
7212 POINT Origin;
7214 LISTVIEW_GetOrigin(infoPtr, &Origin);
7215 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7217 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7219 else
7221 /* Despite what the MS docs say, if this is not the last
7222 column, then MS resizes the column to the width of the
7223 largest text string in the column, including headers
7224 and items. This is different from LVSCW_AUTOSIZE in that
7225 LVSCW_AUTOSIZE ignores the header string length. */
7226 cx = 0;
7228 /* retrieve header text */
7229 hdi.mask = HDI_TEXT;
7230 hdi.cchTextMax = DISP_TEXT_SIZE;
7231 hdi.pszText = szDispText;
7232 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7234 HDC hdc = GetDC(infoPtr->hwndSelf);
7235 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7236 SIZE size;
7238 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7239 cx = size.cx + TRAILING_HEADER_PADDING;
7240 /* FIXME: Take into account the header image, if one is present */
7241 SelectObject(hdc, old_font);
7242 ReleaseDC(infoPtr->hwndSelf, hdc);
7244 cx = max (cx, max_cx);
7248 if (cx < 0) return FALSE;
7250 /* call header to update the column change */
7251 hdi.mask = HDI_WIDTH;
7252 hdi.cxy = cx;
7253 TRACE("hdi.cxy=%d\n", hdi.cxy);
7254 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7257 /***
7258 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7261 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7263 HDC hdc_wnd, hdc;
7264 HBITMAP hbm_im, hbm_mask, hbm_orig;
7265 RECT rc;
7266 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7267 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7268 HIMAGELIST himl;
7270 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7271 ILC_COLOR | ILC_MASK, 2, 2);
7272 hdc_wnd = GetDC(infoPtr->hwndSelf);
7273 hdc = CreateCompatibleDC(hdc_wnd);
7274 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7275 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7276 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7278 rc.left = rc.top = 0;
7279 rc.right = GetSystemMetrics(SM_CXSMICON);
7280 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7282 hbm_orig = SelectObject(hdc, hbm_mask);
7283 FillRect(hdc, &rc, hbr_white);
7284 InflateRect(&rc, -2, -2);
7285 FillRect(hdc, &rc, hbr_black);
7287 SelectObject(hdc, hbm_im);
7288 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7289 SelectObject(hdc, hbm_orig);
7290 ImageList_Add(himl, hbm_im, hbm_mask);
7292 SelectObject(hdc, hbm_im);
7293 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7294 SelectObject(hdc, hbm_orig);
7295 ImageList_Add(himl, hbm_im, hbm_mask);
7297 DeleteObject(hbm_mask);
7298 DeleteObject(hbm_im);
7299 DeleteDC(hdc);
7301 return himl;
7304 /***
7305 * DESCRIPTION:
7306 * Sets the extended listview style.
7308 * PARAMETERS:
7309 * [I] infoPtr : valid pointer to the listview structure
7310 * [I] dwMask : mask
7311 * [I] dwStyle : style
7313 * RETURN:
7314 * SUCCESS : previous style
7315 * FAILURE : 0
7317 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7319 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7321 /* set new style */
7322 if (dwMask)
7323 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7324 else
7325 infoPtr->dwLvExStyle = dwExStyle;
7327 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7329 HIMAGELIST himl = 0;
7330 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7332 LVITEMW item;
7333 item.mask = LVIF_STATE;
7334 item.stateMask = LVIS_STATEIMAGEMASK;
7335 item.state = INDEXTOSTATEIMAGEMASK(1);
7336 LISTVIEW_SetItemState(infoPtr, -1, &item);
7338 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7340 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7343 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7345 DWORD dwStyle;
7347 /* if not already created */
7348 LISTVIEW_CreateHeader(infoPtr);
7350 dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7351 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7352 dwStyle |= HDS_DRAGDROP;
7353 else
7354 dwStyle &= ~HDS_DRAGDROP;
7355 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7358 /* GRIDLINES adds decoration at top so changes sizes */
7359 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7361 LISTVIEW_UpdateSize(infoPtr);
7365 LISTVIEW_InvalidateList(infoPtr);
7366 return dwOldExStyle;
7369 /***
7370 * DESCRIPTION:
7371 * Sets the new hot cursor used during hot tracking and hover selection.
7373 * PARAMETER(S):
7374 * [I] infoPtr : valid pointer to the listview structure
7375 * [I] hCursor : the new hot cursor handle
7377 * RETURN:
7378 * Returns the previous hot cursor
7380 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7382 HCURSOR oldCursor = infoPtr->hHotCursor;
7384 infoPtr->hHotCursor = hCursor;
7386 return oldCursor;
7390 /***
7391 * DESCRIPTION:
7392 * Sets the hot item index.
7394 * PARAMETERS:
7395 * [I] infoPtr : valid pointer to the listview structure
7396 * [I] iIndex : index
7398 * RETURN:
7399 * SUCCESS : previous hot item index
7400 * FAILURE : -1 (no hot item)
7402 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7404 INT iOldIndex = infoPtr->nHotItem;
7406 infoPtr->nHotItem = iIndex;
7408 return iOldIndex;
7412 /***
7413 * DESCRIPTION:
7414 * Sets the amount of time the cursor must hover over an item before it is selected.
7416 * PARAMETER(S):
7417 * [I] infoPtr : valid pointer to the listview structure
7418 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7420 * RETURN:
7421 * Returns the previous hover time
7423 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7425 DWORD oldHoverTime = infoPtr->dwHoverTime;
7427 infoPtr->dwHoverTime = dwHoverTime;
7429 return oldHoverTime;
7432 /***
7433 * DESCRIPTION:
7434 * Sets spacing for icons of LVS_ICON style.
7436 * PARAMETER(S):
7437 * [I] infoPtr : valid pointer to the listview structure
7438 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7439 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7441 * RETURN:
7442 * MAKELONG(oldcx, oldcy)
7444 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7446 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7447 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7449 TRACE("requested=(%d,%d)\n", cx, cy);
7451 /* this is supported only for LVS_ICON style */
7452 if (uView != LVS_ICON) return oldspacing;
7454 /* set to defaults, if instructed to */
7455 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7456 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7458 /* if 0 then compute width
7459 * FIXME: Should scan each item and determine max width of
7460 * icon or label, then make that the width */
7461 if (cx == 0)
7462 cx = infoPtr->iconSpacing.cx;
7464 /* if 0 then compute height */
7465 if (cy == 0)
7466 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7467 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7470 infoPtr->iconSpacing.cx = cx;
7471 infoPtr->iconSpacing.cy = cy;
7473 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7474 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7475 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7476 infoPtr->ntmHeight);
7478 /* these depend on the iconSpacing */
7479 LISTVIEW_UpdateItemSize(infoPtr);
7481 return oldspacing;
7484 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7486 INT cx, cy;
7488 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7490 size->cx = cx;
7491 size->cy = cy;
7493 else
7495 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7496 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7500 /***
7501 * DESCRIPTION:
7502 * Sets image lists.
7504 * PARAMETER(S):
7505 * [I] infoPtr : valid pointer to the listview structure
7506 * [I] nType : image list type
7507 * [I] himl : image list handle
7509 * RETURN:
7510 * SUCCESS : old image list
7511 * FAILURE : NULL
7513 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7515 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7516 INT oldHeight = infoPtr->nItemHeight;
7517 HIMAGELIST himlOld = 0;
7519 TRACE("(nType=%d, himl=%p\n", nType, himl);
7521 switch (nType)
7523 case LVSIL_NORMAL:
7524 himlOld = infoPtr->himlNormal;
7525 infoPtr->himlNormal = himl;
7526 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7527 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7528 break;
7530 case LVSIL_SMALL:
7531 himlOld = infoPtr->himlSmall;
7532 infoPtr->himlSmall = himl;
7533 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7534 break;
7536 case LVSIL_STATE:
7537 himlOld = infoPtr->himlState;
7538 infoPtr->himlState = himl;
7539 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7540 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7541 break;
7543 default:
7544 ERR("Unknown icon type=%d\n", nType);
7545 return NULL;
7548 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7549 if (infoPtr->nItemHeight != oldHeight)
7550 LISTVIEW_UpdateScroll(infoPtr);
7552 return himlOld;
7555 /***
7556 * DESCRIPTION:
7557 * Preallocates memory (does *not* set the actual count of items !)
7559 * PARAMETER(S):
7560 * [I] infoPtr : valid pointer to the listview structure
7561 * [I] nItems : item count (projected number of items to allocate)
7562 * [I] dwFlags : update flags
7564 * RETURN:
7565 * SUCCESS : TRUE
7566 * FAILURE : FALSE
7568 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7570 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7572 if (infoPtr->dwStyle & LVS_OWNERDATA)
7574 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7575 INT nOldCount = infoPtr->nItemCount;
7577 if (nItems < nOldCount)
7579 RANGE range = { nItems, nOldCount };
7580 ranges_del(infoPtr->selectionRanges, range);
7581 if (infoPtr->nFocusedItem >= nItems)
7583 LISTVIEW_SetItemFocus(infoPtr, -1);
7584 SetRectEmpty(&infoPtr->rcFocus);
7588 infoPtr->nItemCount = nItems;
7589 LISTVIEW_UpdateScroll(infoPtr);
7591 /* the flags are valid only in ownerdata report and list modes */
7592 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7594 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7595 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7597 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7598 LISTVIEW_InvalidateList(infoPtr);
7599 else
7601 INT nFrom, nTo;
7602 POINT Origin;
7603 RECT rcErase;
7605 LISTVIEW_GetOrigin(infoPtr, &Origin);
7606 nFrom = min(nOldCount, nItems);
7607 nTo = max(nOldCount, nItems);
7609 if (uView == LVS_REPORT)
7611 rcErase.left = 0;
7612 rcErase.top = nFrom * infoPtr->nItemHeight;
7613 rcErase.right = infoPtr->nItemWidth;
7614 rcErase.bottom = nTo * infoPtr->nItemHeight;
7615 OffsetRect(&rcErase, Origin.x, Origin.y);
7616 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7617 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7619 else /* LVS_LIST */
7621 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7623 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7624 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7625 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7626 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7627 OffsetRect(&rcErase, Origin.x, Origin.y);
7628 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7629 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7631 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7632 rcErase.top = 0;
7633 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7634 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7635 OffsetRect(&rcErase, Origin.x, Origin.y);
7636 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7637 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7641 else
7643 /* According to MSDN for non-LVS_OWNERDATA this is just
7644 * a performance issue. The control allocates its internal
7645 * data structures for the number of items specified. It
7646 * cuts down on the number of memory allocations. Therefore
7647 * we will just issue a WARN here
7649 WARN("for non-ownerdata performance option not implemented.\n");
7652 return TRUE;
7655 /***
7656 * DESCRIPTION:
7657 * Sets the position of an item.
7659 * PARAMETER(S):
7660 * [I] infoPtr : valid pointer to the listview structure
7661 * [I] nItem : item index
7662 * [I] pt : coordinate
7664 * RETURN:
7665 * SUCCESS : TRUE
7666 * FAILURE : FALSE
7668 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7670 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7671 POINT Origin;
7673 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7675 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7676 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7678 LISTVIEW_GetOrigin(infoPtr, &Origin);
7680 /* This point value seems to be an undocumented feature.
7681 * The best guess is that it means either at the origin,
7682 * or at true beginning of the list. I will assume the origin. */
7683 if ((pt.x == -1) && (pt.y == -1))
7684 pt = Origin;
7686 if (uView == LVS_ICON)
7688 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7689 pt.y -= ICON_TOP_PADDING;
7691 pt.x -= Origin.x;
7692 pt.y -= Origin.y;
7694 infoPtr->bAutoarrange = FALSE;
7696 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7699 /***
7700 * DESCRIPTION:
7701 * Sets the state of one or many items.
7703 * PARAMETER(S):
7704 * [I] infoPtr : valid pointer to the listview structure
7705 * [I] nItem : item index
7706 * [I] lpLVItem : item or subitem info
7708 * RETURN:
7709 * SUCCESS : TRUE
7710 * FAILURE : FALSE
7712 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7714 BOOL bResult = TRUE;
7715 LVITEMW lvItem;
7717 lvItem.iItem = nItem;
7718 lvItem.iSubItem = 0;
7719 lvItem.mask = LVIF_STATE;
7720 lvItem.state = lpLVItem->state;
7721 lvItem.stateMask = lpLVItem->stateMask;
7722 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7724 if (nItem == -1)
7726 /* select all isn't allowed in LVS_SINGLESEL */
7727 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
7728 return FALSE;
7730 /* apply to all items */
7731 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7732 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7734 else
7735 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7738 * Update selection mark
7740 * Investigation on windows 2k showed that selection mark was updated
7741 * whenever a new selection was made, but if the selected item was
7742 * unselected it was not updated.
7744 * we are probably still not 100% accurate, but this at least sets the
7745 * proper selection mark when it is needed
7748 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7749 (infoPtr->nSelectionMark == -1))
7751 int i;
7752 for (i = 0; i < infoPtr->nItemCount; i++)
7754 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7756 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7758 infoPtr->nSelectionMark = i;
7759 break;
7762 else if (ranges_contain(infoPtr->selectionRanges, i))
7764 infoPtr->nSelectionMark = i;
7765 break;
7770 return bResult;
7773 /***
7774 * DESCRIPTION:
7775 * Sets the text of an item or subitem.
7777 * PARAMETER(S):
7778 * [I] hwnd : window handle
7779 * [I] nItem : item index
7780 * [I] lpLVItem : item or subitem info
7781 * [I] isW : TRUE if input is Unicode
7783 * RETURN:
7784 * SUCCESS : TRUE
7785 * FAILURE : FALSE
7787 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7789 LVITEMW lvItem;
7791 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7793 lvItem.iItem = nItem;
7794 lvItem.iSubItem = lpLVItem->iSubItem;
7795 lvItem.mask = LVIF_TEXT;
7796 lvItem.pszText = lpLVItem->pszText;
7797 lvItem.cchTextMax = lpLVItem->cchTextMax;
7799 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7801 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7804 /***
7805 * DESCRIPTION:
7806 * Set item index that marks the start of a multiple selection.
7808 * PARAMETER(S):
7809 * [I] infoPtr : valid pointer to the listview structure
7810 * [I] nIndex : index
7812 * RETURN:
7813 * Index number or -1 if there is no selection mark.
7815 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7817 INT nOldIndex = infoPtr->nSelectionMark;
7819 TRACE("(nIndex=%d)\n", nIndex);
7821 infoPtr->nSelectionMark = nIndex;
7823 return nOldIndex;
7826 /***
7827 * DESCRIPTION:
7828 * Sets the text background color.
7830 * PARAMETER(S):
7831 * [I] infoPtr : valid pointer to the listview structure
7832 * [I] clrTextBk : text background color
7834 * RETURN:
7835 * SUCCESS : TRUE
7836 * FAILURE : FALSE
7838 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7840 TRACE("(clrTextBk=%x)\n", clrTextBk);
7842 if (infoPtr->clrTextBk != clrTextBk)
7844 infoPtr->clrTextBk = clrTextBk;
7845 LISTVIEW_InvalidateList(infoPtr);
7848 return TRUE;
7851 /***
7852 * DESCRIPTION:
7853 * Sets the text foreground color.
7855 * PARAMETER(S):
7856 * [I] infoPtr : valid pointer to the listview structure
7857 * [I] clrText : text color
7859 * RETURN:
7860 * SUCCESS : TRUE
7861 * FAILURE : FALSE
7863 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7865 TRACE("(clrText=%x)\n", clrText);
7867 if (infoPtr->clrText != clrText)
7869 infoPtr->clrText = clrText;
7870 LISTVIEW_InvalidateList(infoPtr);
7873 return TRUE;
7876 /***
7877 * DESCRIPTION:
7878 * Sets new ToolTip window to ListView control.
7880 * PARAMETER(S):
7881 * [I] infoPtr : valid pointer to the listview structure
7882 * [I] hwndNewToolTip : handle to new ToolTip
7884 * RETURN:
7885 * old tool tip
7887 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7889 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7890 infoPtr->hwndToolTip = hwndNewToolTip;
7891 return hwndOldToolTip;
7895 * DESCRIPTION:
7896 * sets the Unicode character format flag for the control
7897 * PARAMETER(S):
7898 * [I] infoPtr :valid pointer to the listview structure
7899 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7901 * RETURN:
7902 * Old Unicode Format
7904 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7906 BOOL rc = infoPtr->notifyFormat;
7907 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7908 return rc;
7911 /* LISTVIEW_SetWorkAreas */
7913 /***
7914 * DESCRIPTION:
7915 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
7917 * PARAMETER(S):
7918 * [I] first : pointer to first ITEM_INFO to compare
7919 * [I] second : pointer to second ITEM_INFO to compare
7920 * [I] lParam : HWND of control
7922 * RETURN:
7923 * if first comes before second : negative
7924 * if first comes after second : positive
7925 * if first and second are equivalent : zero
7927 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7929 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7930 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7931 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7933 /* Forward the call to the client defined callback */
7934 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7937 /***
7938 * DESCRIPTION:
7939 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
7941 * PARAMETER(S):
7942 * [I] first : pointer to first ITEM_INFO to compare
7943 * [I] second : pointer to second ITEM_INFO to compare
7944 * [I] lParam : HWND of control
7946 * RETURN:
7947 * if first comes before second : negative
7948 * if first comes after second : positive
7949 * if first and second are equivalent : zero
7951 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
7953 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7954 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
7955 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
7957 /* Forward the call to the client defined callback */
7958 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
7961 /***
7962 * DESCRIPTION:
7963 * Sorts the listview items.
7965 * PARAMETER(S):
7966 * [I] infoPtr : valid pointer to the listview structure
7967 * [I] pfnCompare : application-defined value
7968 * [I] lParamSort : pointer to comparison callback
7969 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
7971 * RETURN:
7972 * SUCCESS : TRUE
7973 * FAILURE : FALSE
7975 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
7976 LPARAM lParamSort, BOOL IsEx)
7978 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7979 HDPA hdpaSubItems;
7980 ITEM_INFO *lpItem;
7981 LPVOID selectionMarkItem = NULL;
7982 LPVOID focusedItem = NULL;
7983 int i;
7985 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7987 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7989 if (!pfnCompare) return FALSE;
7990 if (!infoPtr->hdpaItems) return FALSE;
7992 /* if there are 0 or 1 items, there is no need to sort */
7993 if (infoPtr->nItemCount < 2) return TRUE;
7995 /* clear selection */
7996 ranges_clear(infoPtr->selectionRanges);
7998 /* save selection mark and focused item */
7999 if (infoPtr->nSelectionMark >= 0)
8000 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
8001 if (infoPtr->nFocusedItem >= 0)
8002 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
8004 infoPtr->pfnCompare = pfnCompare;
8005 infoPtr->lParamSort = lParamSort;
8006 if (IsEx)
8007 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
8008 else
8009 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
8011 /* restore selection ranges */
8012 for (i=0; i < infoPtr->nItemCount; i++)
8014 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
8015 lpItem = DPA_GetPtr(hdpaSubItems, 0);
8017 if (lpItem->state & LVIS_SELECTED)
8018 ranges_additem(infoPtr->selectionRanges, i);
8020 /* restore selection mark and focused item */
8021 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
8022 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
8024 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
8026 /* refresh the display */
8027 if (uView != LVS_ICON && uView != LVS_SMALLICON)
8028 LISTVIEW_InvalidateList(infoPtr);
8030 return TRUE;
8033 /***
8034 * DESCRIPTION:
8035 * Update theme handle after a theme change.
8037 * PARAMETER(S):
8038 * [I] infoPtr : valid pointer to the listview structure
8040 * RETURN:
8041 * SUCCESS : 0
8042 * FAILURE : something else
8044 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
8046 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8047 CloseThemeData(theme);
8048 OpenThemeData(infoPtr->hwndSelf, themeClass);
8049 return 0;
8052 /***
8053 * DESCRIPTION:
8054 * Updates an items or rearranges the listview control.
8056 * PARAMETER(S):
8057 * [I] infoPtr : valid pointer to the listview structure
8058 * [I] nItem : item index
8060 * RETURN:
8061 * SUCCESS : TRUE
8062 * FAILURE : FALSE
8064 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
8066 TRACE("(nItem=%d)\n", nItem);
8068 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
8070 /* rearrange with default alignment style */
8071 if (is_autoarrange(infoPtr))
8072 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8073 else
8074 LISTVIEW_InvalidateItem(infoPtr, nItem);
8076 return TRUE;
8079 /***
8080 * DESCRIPTION:
8081 * Draw the track line at the place defined in the infoPtr structure.
8082 * The line is drawn with a XOR pen so drawing the line for the second time
8083 * in the same place erases the line.
8085 * PARAMETER(S):
8086 * [I] infoPtr : valid pointer to the listview structure
8088 * RETURN:
8089 * SUCCESS : TRUE
8090 * FAILURE : FALSE
8092 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8094 HPEN hOldPen;
8095 HDC hdc;
8096 INT oldROP;
8098 if (infoPtr->xTrackLine == -1)
8099 return FALSE;
8101 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8102 return FALSE;
8103 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8104 oldROP = SetROP2(hdc, R2_XORPEN);
8105 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8106 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8107 SetROP2(hdc, oldROP);
8108 SelectObject(hdc, hOldPen);
8109 ReleaseDC(infoPtr->hwndSelf, hdc);
8110 return TRUE;
8113 /***
8114 * DESCRIPTION:
8115 * Called when an edit control should be displayed. This function is called after
8116 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8118 * PARAMETER(S):
8119 * [I] hwnd : Handle to the listview
8120 * [I] uMsg : WM_TIMER (ignored)
8121 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8122 * [I] dwTimer : The elapsed time (ignored)
8124 * RETURN:
8125 * None.
8127 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8129 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8130 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8132 KillTimer(hwnd, idEvent);
8133 editItem->fEnabled = FALSE;
8134 /* check if the item is still selected */
8135 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8136 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8139 /***
8140 * DESCRIPTION:
8141 * Creates the listview control - the WM_NCCREATE phase.
8143 * PARAMETER(S):
8144 * [I] hwnd : window handle
8145 * [I] lpcs : the create parameters
8147 * RETURN:
8148 * Success: TRUE
8149 * Failure: FALSE
8151 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8153 LISTVIEW_INFO *infoPtr;
8154 LOGFONTW logFont;
8156 TRACE("(lpcs=%p)\n", lpcs);
8158 /* initialize info pointer */
8159 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8160 if (!infoPtr) return FALSE;
8162 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8164 infoPtr->hwndSelf = hwnd;
8165 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8166 /* determine the type of structures to use */
8167 infoPtr->hwndNotify = lpcs->hwndParent;
8168 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8170 /* initialize color information */
8171 infoPtr->clrBk = CLR_NONE;
8172 infoPtr->clrText = CLR_DEFAULT;
8173 infoPtr->clrTextBk = CLR_DEFAULT;
8174 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8176 /* set default values */
8177 infoPtr->nFocusedItem = -1;
8178 infoPtr->nSelectionMark = -1;
8179 infoPtr->nHotItem = -1;
8180 infoPtr->bRedraw = TRUE;
8181 infoPtr->bNoItemMetrics = TRUE;
8182 infoPtr->bDoChangeNotify = TRUE;
8183 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8184 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8185 infoPtr->nEditLabelItem = -1;
8186 infoPtr->nLButtonDownItem = -1;
8187 infoPtr->dwHoverTime = -1; /* default system hover time */
8188 infoPtr->nMeasureItemHeight = 0;
8189 infoPtr->xTrackLine = -1; /* no track line */
8190 infoPtr->itemEdit.fEnabled = FALSE;
8191 infoPtr->iVersion = COMCTL32_VERSION;
8193 /* get default font (icon title) */
8194 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8195 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8196 infoPtr->hFont = infoPtr->hDefaultFont;
8197 LISTVIEW_SaveTextMetrics(infoPtr);
8199 /* allocate memory for the data structure */
8200 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8201 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8202 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8203 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8204 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8205 return TRUE;
8207 fail:
8208 DestroyWindow(infoPtr->hwndHeader);
8209 ranges_destroy(infoPtr->selectionRanges);
8210 DPA_Destroy(infoPtr->hdpaItems);
8211 DPA_Destroy(infoPtr->hdpaPosX);
8212 DPA_Destroy(infoPtr->hdpaPosY);
8213 DPA_Destroy(infoPtr->hdpaColumns);
8214 Free(infoPtr);
8215 return FALSE;
8218 /***
8219 * DESCRIPTION:
8220 * Creates the listview control - the WM_CREATE phase. Most of the data is
8221 * already set up in LISTVIEW_NCCreate
8223 * PARAMETER(S):
8224 * [I] hwnd : window handle
8225 * [I] lpcs : the create parameters
8227 * RETURN:
8228 * Success: 0
8229 * Failure: -1
8231 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8233 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8234 UINT uView = lpcs->style & LVS_TYPEMASK;
8236 TRACE("(lpcs=%p)\n", lpcs);
8238 infoPtr->dwStyle = lpcs->style;
8239 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8240 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8242 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8244 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8246 else
8247 infoPtr->hwndHeader = 0;
8249 /* init item size to avoid division by 0 */
8250 LISTVIEW_UpdateItemSize (infoPtr);
8252 if (uView == LVS_REPORT)
8254 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8256 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8258 LISTVIEW_UpdateScroll(infoPtr);
8261 OpenThemeData(hwnd, themeClass);
8263 /* initialize the icon sizes */
8264 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8265 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8266 return 0;
8269 /***
8270 * DESCRIPTION:
8271 * Destroys the listview control.
8273 * PARAMETER(S):
8274 * [I] infoPtr : valid pointer to the listview structure
8276 * RETURN:
8277 * Success: 0
8278 * Failure: -1
8280 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8282 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8283 CloseThemeData(theme);
8284 return 0;
8287 /***
8288 * DESCRIPTION:
8289 * Enables the listview control.
8291 * PARAMETER(S):
8292 * [I] infoPtr : valid pointer to the listview structure
8293 * [I] bEnable : specifies whether to enable or disable the window
8295 * RETURN:
8296 * SUCCESS : TRUE
8297 * FAILURE : FALSE
8299 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8301 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8302 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8303 return TRUE;
8306 /***
8307 * DESCRIPTION:
8308 * Erases the background of the listview control.
8310 * PARAMETER(S):
8311 * [I] infoPtr : valid pointer to the listview structure
8312 * [I] hdc : device context handle
8314 * RETURN:
8315 * SUCCESS : TRUE
8316 * FAILURE : FALSE
8318 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8320 RECT rc;
8322 TRACE("(hdc=%p)\n", hdc);
8324 if (!GetClipBox(hdc, &rc)) return FALSE;
8326 if (infoPtr->clrBk == CLR_NONE)
8327 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
8329 /* for double buffered controls we need to do this during refresh */
8330 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8332 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8336 /***
8337 * DESCRIPTION:
8338 * Helper function for LISTVIEW_[HV]Scroll *only*.
8339 * Performs vertical/horizontal scrolling by a give amount.
8341 * PARAMETER(S):
8342 * [I] infoPtr : valid pointer to the listview structure
8343 * [I] dx : amount of horizontal scroll
8344 * [I] dy : amount of vertical scroll
8346 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8348 /* now we can scroll the list */
8349 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8350 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8351 /* if we have focus, adjust rect */
8352 OffsetRect(&infoPtr->rcFocus, dx, dy);
8353 UpdateWindow(infoPtr->hwndSelf);
8356 /***
8357 * DESCRIPTION:
8358 * Performs vertical scrolling.
8360 * PARAMETER(S):
8361 * [I] infoPtr : valid pointer to the listview structure
8362 * [I] nScrollCode : scroll code
8363 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8364 * [I] hScrollWnd : scrollbar control window handle
8366 * RETURN:
8367 * Zero
8369 * NOTES:
8370 * SB_LINEUP/SB_LINEDOWN:
8371 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8372 * for LVS_REPORT is 1 line
8373 * for LVS_LIST cannot occur
8376 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8377 INT nScrollDiff, HWND hScrollWnd)
8379 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8380 INT nOldScrollPos, nNewScrollPos;
8381 SCROLLINFO scrollInfo;
8382 BOOL is_an_icon;
8384 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8385 debugscrollcode(nScrollCode), nScrollDiff);
8387 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8389 scrollInfo.cbSize = sizeof(SCROLLINFO);
8390 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8392 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8394 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8396 nOldScrollPos = scrollInfo.nPos;
8397 switch (nScrollCode)
8399 case SB_INTERNAL:
8400 break;
8402 case SB_LINEUP:
8403 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8404 break;
8406 case SB_LINEDOWN:
8407 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8408 break;
8410 case SB_PAGEUP:
8411 nScrollDiff = -scrollInfo.nPage;
8412 break;
8414 case SB_PAGEDOWN:
8415 nScrollDiff = scrollInfo.nPage;
8416 break;
8418 case SB_THUMBPOSITION:
8419 case SB_THUMBTRACK:
8420 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8421 break;
8423 default:
8424 nScrollDiff = 0;
8427 /* quit right away if pos isn't changing */
8428 if (nScrollDiff == 0) return 0;
8430 /* calculate new position, and handle overflows */
8431 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8432 if (nScrollDiff > 0) {
8433 if (nNewScrollPos < nOldScrollPos ||
8434 nNewScrollPos > scrollInfo.nMax)
8435 nNewScrollPos = scrollInfo.nMax;
8436 } else {
8437 if (nNewScrollPos > nOldScrollPos ||
8438 nNewScrollPos < scrollInfo.nMin)
8439 nNewScrollPos = scrollInfo.nMin;
8442 /* set the new position, and reread in case it changed */
8443 scrollInfo.fMask = SIF_POS;
8444 scrollInfo.nPos = nNewScrollPos;
8445 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8447 /* carry on only if it really changed */
8448 if (nNewScrollPos == nOldScrollPos) return 0;
8450 /* now adjust to client coordinates */
8451 nScrollDiff = nOldScrollPos - nNewScrollPos;
8452 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8454 /* and scroll the window */
8455 scroll_list(infoPtr, 0, nScrollDiff);
8457 return 0;
8460 /***
8461 * DESCRIPTION:
8462 * Performs horizontal scrolling.
8464 * PARAMETER(S):
8465 * [I] infoPtr : valid pointer to the listview structure
8466 * [I] nScrollCode : scroll code
8467 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8468 * [I] hScrollWnd : scrollbar control window handle
8470 * RETURN:
8471 * Zero
8473 * NOTES:
8474 * SB_LINELEFT/SB_LINERIGHT:
8475 * for LVS_ICON, LVS_SMALLICON 1 pixel
8476 * for LVS_REPORT is 1 pixel
8477 * for LVS_LIST is 1 column --> which is a 1 because the
8478 * scroll is based on columns not pixels
8481 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8482 INT nScrollDiff, HWND hScrollWnd)
8484 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8485 INT nOldScrollPos, nNewScrollPos;
8486 SCROLLINFO scrollInfo;
8488 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8489 debugscrollcode(nScrollCode), nScrollDiff);
8491 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8493 scrollInfo.cbSize = sizeof(SCROLLINFO);
8494 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8496 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8498 nOldScrollPos = scrollInfo.nPos;
8500 switch (nScrollCode)
8502 case SB_INTERNAL:
8503 break;
8505 case SB_LINELEFT:
8506 nScrollDiff = -1;
8507 break;
8509 case SB_LINERIGHT:
8510 nScrollDiff = 1;
8511 break;
8513 case SB_PAGELEFT:
8514 nScrollDiff = -scrollInfo.nPage;
8515 break;
8517 case SB_PAGERIGHT:
8518 nScrollDiff = scrollInfo.nPage;
8519 break;
8521 case SB_THUMBPOSITION:
8522 case SB_THUMBTRACK:
8523 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8524 break;
8526 default:
8527 nScrollDiff = 0;
8530 /* quit right away if pos isn't changing */
8531 if (nScrollDiff == 0) return 0;
8533 /* calculate new position, and handle overflows */
8534 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8535 if (nScrollDiff > 0) {
8536 if (nNewScrollPos < nOldScrollPos ||
8537 nNewScrollPos > scrollInfo.nMax)
8538 nNewScrollPos = scrollInfo.nMax;
8539 } else {
8540 if (nNewScrollPos > nOldScrollPos ||
8541 nNewScrollPos < scrollInfo.nMin)
8542 nNewScrollPos = scrollInfo.nMin;
8545 /* set the new position, and reread in case it changed */
8546 scrollInfo.fMask = SIF_POS;
8547 scrollInfo.nPos = nNewScrollPos;
8548 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8550 /* carry on only if it really changed */
8551 if (nNewScrollPos == nOldScrollPos) return 0;
8553 if(uView == LVS_REPORT)
8554 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8556 /* now adjust to client coordinates */
8557 nScrollDiff = nOldScrollPos - nNewScrollPos;
8558 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8560 /* and scroll the window */
8561 scroll_list(infoPtr, nScrollDiff, 0);
8563 return 0;
8566 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8568 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8569 INT gcWheelDelta = 0;
8570 INT pulScrollLines = 3;
8571 SCROLLINFO scrollInfo;
8573 TRACE("(wheelDelta=%d)\n", wheelDelta);
8575 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8576 gcWheelDelta -= wheelDelta;
8578 scrollInfo.cbSize = sizeof(SCROLLINFO);
8579 scrollInfo.fMask = SIF_POS;
8581 switch(uView)
8583 case LVS_ICON:
8584 case LVS_SMALLICON:
8586 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8587 * should be fixed in the future.
8589 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8590 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8591 break;
8593 case LVS_REPORT:
8594 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8596 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8597 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8598 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8600 break;
8602 case LVS_LIST:
8603 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8604 break;
8606 return 0;
8609 /***
8610 * DESCRIPTION:
8611 * ???
8613 * PARAMETER(S):
8614 * [I] infoPtr : valid pointer to the listview structure
8615 * [I] nVirtualKey : virtual key
8616 * [I] lKeyData : key data
8618 * RETURN:
8619 * Zero
8621 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8623 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8624 HWND hwndSelf = infoPtr->hwndSelf;
8625 INT nItem = -1;
8626 NMLVKEYDOWN nmKeyDown;
8628 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8630 /* send LVN_KEYDOWN notification */
8631 nmKeyDown.wVKey = nVirtualKey;
8632 nmKeyDown.flags = 0;
8633 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8634 if (!IsWindow(hwndSelf))
8635 return 0;
8637 switch (nVirtualKey)
8639 case VK_SPACE:
8640 nItem = infoPtr->nFocusedItem;
8641 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8642 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8643 break;
8645 case VK_RETURN:
8646 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8648 if (!notify(infoPtr, NM_RETURN)) return 0;
8649 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8651 break;
8653 case VK_HOME:
8654 if (infoPtr->nItemCount > 0)
8655 nItem = 0;
8656 break;
8658 case VK_END:
8659 if (infoPtr->nItemCount > 0)
8660 nItem = infoPtr->nItemCount - 1;
8661 break;
8663 case VK_LEFT:
8664 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
8665 break;
8667 case VK_UP:
8668 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
8669 break;
8671 case VK_RIGHT:
8672 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
8673 break;
8675 case VK_DOWN:
8676 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
8677 break;
8679 case VK_PRIOR:
8680 if (uView == LVS_REPORT)
8682 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8683 if (infoPtr->nFocusedItem == topidx)
8684 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8685 else
8686 nItem = topidx;
8688 else
8689 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8690 * LISTVIEW_GetCountPerRow(infoPtr);
8691 if(nItem < 0) nItem = 0;
8692 break;
8694 case VK_NEXT:
8695 if (uView == LVS_REPORT)
8697 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8698 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8699 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8700 nItem = infoPtr->nFocusedItem + cnt - 1;
8701 else
8702 nItem = topidx + cnt - 1;
8704 else
8705 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8706 * LISTVIEW_GetCountPerRow(infoPtr);
8707 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8708 break;
8711 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8712 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8714 return 0;
8717 /***
8718 * DESCRIPTION:
8719 * Kills the focus.
8721 * PARAMETER(S):
8722 * [I] infoPtr : valid pointer to the listview structure
8724 * RETURN:
8725 * Zero
8727 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8729 TRACE("()\n");
8731 /* if we did not have the focus, there's nothing to do */
8732 if (!infoPtr->bFocus) return 0;
8734 /* send NM_KILLFOCUS notification */
8735 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8737 /* if we have a focus rectangle, get rid of it */
8738 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8740 /* set window focus flag */
8741 infoPtr->bFocus = FALSE;
8743 /* invalidate the selected items before resetting focus flag */
8744 LISTVIEW_InvalidateSelectedItems(infoPtr);
8746 return 0;
8749 /***
8750 * DESCRIPTION:
8751 * Processes double click messages (left mouse button).
8753 * PARAMETER(S):
8754 * [I] infoPtr : valid pointer to the listview structure
8755 * [I] wKey : key flag
8756 * [I] x,y : mouse coordinate
8758 * RETURN:
8759 * Zero
8761 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8763 LVHITTESTINFO htInfo;
8765 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8767 /* Cancel the item edition if any */
8768 if (infoPtr->itemEdit.fEnabled)
8770 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8771 infoPtr->itemEdit.fEnabled = FALSE;
8774 /* send NM_RELEASEDCAPTURE notification */
8775 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8777 htInfo.pt.x = x;
8778 htInfo.pt.y = y;
8780 /* send NM_DBLCLK notification */
8781 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8782 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8784 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8785 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8787 return 0;
8790 /***
8791 * DESCRIPTION:
8792 * Processes mouse down messages (left mouse button).
8794 * PARAMETERS:
8795 * infoPtr [I ] valid pointer to the listview structure
8796 * wKey [I ] key flag
8797 * x,y [I ] mouse coordinate
8799 * RETURN:
8800 * Zero
8802 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8804 LVHITTESTINFO lvHitTestInfo;
8805 static BOOL bGroupSelect = TRUE;
8806 POINT pt = { x, y };
8807 INT nItem;
8809 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8811 /* send NM_RELEASEDCAPTURE notification */
8812 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8814 /* set left button down flag and record the click position */
8815 infoPtr->bLButtonDown = TRUE;
8816 infoPtr->ptClickPos = pt;
8817 infoPtr->bDragging = FALSE;
8819 lvHitTestInfo.pt.x = x;
8820 lvHitTestInfo.pt.y = y;
8822 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8823 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8824 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8826 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8828 toggle_checkbox_state(infoPtr, nItem);
8829 return 0;
8832 if (infoPtr->dwStyle & LVS_SINGLESEL)
8834 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8835 infoPtr->nEditLabelItem = nItem;
8836 else
8837 LISTVIEW_SetSelection(infoPtr, nItem);
8839 else
8841 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8843 if (bGroupSelect)
8845 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8846 LISTVIEW_SetItemFocus(infoPtr, nItem);
8847 infoPtr->nSelectionMark = nItem;
8849 else
8851 LVITEMW item;
8853 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8854 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8856 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8857 infoPtr->nSelectionMark = nItem;
8860 else if (wKey & MK_CONTROL)
8862 LVITEMW item;
8864 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8866 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8867 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8868 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8869 infoPtr->nSelectionMark = nItem;
8871 else if (wKey & MK_SHIFT)
8873 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8875 else
8877 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8879 infoPtr->nEditLabelItem = nItem;
8880 infoPtr->nLButtonDownItem = nItem;
8882 LISTVIEW_SetItemFocus(infoPtr, nItem);
8884 else
8885 /* set selection (clears other pre-existing selections) */
8886 LISTVIEW_SetSelection(infoPtr, nItem);
8890 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8891 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8893 else
8895 /* remove all selections */
8896 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
8897 LISTVIEW_DeselectAll(infoPtr);
8898 ReleaseCapture();
8901 return 0;
8904 /***
8905 * DESCRIPTION:
8906 * Processes mouse up messages (left mouse button).
8908 * PARAMETERS:
8909 * infoPtr [I ] valid pointer to the listview structure
8910 * wKey [I ] key flag
8911 * x,y [I ] mouse coordinate
8913 * RETURN:
8914 * Zero
8916 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8918 LVHITTESTINFO lvHitTestInfo;
8920 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8922 if (!infoPtr->bLButtonDown) return 0;
8924 lvHitTestInfo.pt.x = x;
8925 lvHitTestInfo.pt.y = y;
8927 /* send NM_CLICK notification */
8928 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8929 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8931 /* set left button flag */
8932 infoPtr->bLButtonDown = FALSE;
8934 /* set a single selection, reset others */
8935 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
8936 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
8937 infoPtr->nLButtonDownItem = -1;
8939 if (infoPtr->bDragging)
8941 infoPtr->bDragging = FALSE;
8942 return 0;
8945 /* if we clicked on a selected item, edit the label */
8946 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8948 /* we want to make sure the user doesn't want to do a double click. So we will
8949 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8951 infoPtr->itemEdit.fEnabled = TRUE;
8952 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8953 SetTimer(infoPtr->hwndSelf,
8954 (UINT_PTR)&infoPtr->itemEdit,
8955 GetDoubleClickTime(),
8956 LISTVIEW_DelayedEditItem);
8959 if (!infoPtr->bFocus)
8960 SetFocus(infoPtr->hwndSelf);
8962 return 0;
8965 /***
8966 * DESCRIPTION:
8967 * Destroys the listview control (called after WM_DESTROY).
8969 * PARAMETER(S):
8970 * [I] infoPtr : valid pointer to the listview structure
8972 * RETURN:
8973 * Zero
8975 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8977 TRACE("()\n");
8979 /* delete all items */
8980 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8982 /* destroy data structure */
8983 DPA_Destroy(infoPtr->hdpaItems);
8984 DPA_Destroy(infoPtr->hdpaPosX);
8985 DPA_Destroy(infoPtr->hdpaPosY);
8986 DPA_Destroy(infoPtr->hdpaColumns);
8987 ranges_destroy(infoPtr->selectionRanges);
8989 /* destroy image lists */
8990 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8992 if (infoPtr->himlNormal)
8993 ImageList_Destroy(infoPtr->himlNormal);
8994 if (infoPtr->himlSmall)
8995 ImageList_Destroy(infoPtr->himlSmall);
8996 if (infoPtr->himlState)
8997 ImageList_Destroy(infoPtr->himlState);
9000 /* destroy font, bkgnd brush */
9001 infoPtr->hFont = 0;
9002 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
9003 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
9005 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
9007 /* free listview info pointer*/
9008 Free(infoPtr);
9010 return 0;
9013 /***
9014 * DESCRIPTION:
9015 * Handles notifications from header.
9017 * PARAMETER(S):
9018 * [I] infoPtr : valid pointer to the listview structure
9019 * [I] nCtrlId : control identifier
9020 * [I] lpnmh : notification information
9022 * RETURN:
9023 * Zero
9025 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
9027 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9028 HWND hwndSelf = infoPtr->hwndSelf;
9030 TRACE("(lpnmh=%p)\n", lpnmh);
9032 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
9034 switch (lpnmh->hdr.code)
9036 case HDN_TRACKW:
9037 case HDN_TRACKA:
9039 COLUMN_INFO *lpColumnInfo;
9040 POINT ptOrigin;
9041 INT x;
9043 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9044 break;
9046 /* remove the old line (if any) */
9047 LISTVIEW_DrawTrackLine(infoPtr);
9049 /* compute & draw the new line */
9050 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9051 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
9052 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9053 infoPtr->xTrackLine = x + ptOrigin.x;
9054 LISTVIEW_DrawTrackLine(infoPtr);
9055 break;
9058 case HDN_ENDTRACKA:
9059 case HDN_ENDTRACKW:
9060 /* remove the track line (if any) */
9061 LISTVIEW_DrawTrackLine(infoPtr);
9062 infoPtr->xTrackLine = -1;
9063 break;
9065 case HDN_ENDDRAG:
9066 FIXME("Changing column order not implemented\n");
9067 return TRUE;
9069 case HDN_ITEMCHANGINGW:
9070 case HDN_ITEMCHANGINGA:
9071 return notify_forward_header(infoPtr, lpnmh);
9073 case HDN_ITEMCHANGEDW:
9074 case HDN_ITEMCHANGEDA:
9076 COLUMN_INFO *lpColumnInfo;
9077 INT dx, cxy;
9079 notify_forward_header(infoPtr, lpnmh);
9080 if (!IsWindow(hwndSelf))
9081 break;
9083 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
9085 HDITEMW hdi;
9087 hdi.mask = HDI_WIDTH;
9088 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
9089 cxy = hdi.cxy;
9091 else
9092 cxy = lpnmh->pitem->cxy;
9094 /* determine how much we change since the last know position */
9095 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
9096 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
9097 if (dx != 0)
9099 lpColumnInfo->rcHeader.right += dx;
9100 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9101 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9102 else
9104 /* only needs to update the scrolls */
9105 infoPtr->nItemWidth += dx;
9106 LISTVIEW_UpdateScroll(infoPtr);
9108 LISTVIEW_UpdateItemSize(infoPtr);
9109 if (uView == LVS_REPORT && is_redrawing(infoPtr))
9111 POINT ptOrigin;
9112 RECT rcCol = lpColumnInfo->rcHeader;
9114 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9115 OffsetRect(&rcCol, ptOrigin.x, 0);
9117 rcCol.top = infoPtr->rcList.top;
9118 rcCol.bottom = infoPtr->rcList.bottom;
9120 /* resizing left-aligned columns leaves most of the left side untouched */
9121 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9123 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9124 if (dx > 0)
9125 nMaxDirty += dx;
9126 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9129 /* when shrinking the last column clear the now unused field */
9130 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
9132 RECT right;
9134 rcCol.right -= dx;
9136 /* deal with right from rightmost column area */
9137 right.left = rcCol.right;
9138 right.top = rcCol.top;
9139 right.bottom = rcCol.bottom;
9140 right.right = infoPtr->rcList.right;
9142 LISTVIEW_InvalidateRect(infoPtr, &right);
9145 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9149 break;
9151 case HDN_ITEMCLICKW:
9152 case HDN_ITEMCLICKA:
9154 /* Handle sorting by Header Column */
9155 NMLISTVIEW nmlv;
9157 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9158 nmlv.iItem = -1;
9159 nmlv.iSubItem = lpnmh->iItem;
9160 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9161 notify_forward_header(infoPtr, lpnmh);
9163 break;
9165 case HDN_DIVIDERDBLCLICKW:
9166 case HDN_DIVIDERDBLCLICKA:
9167 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9168 break;
9171 return 0;
9174 /***
9175 * DESCRIPTION:
9176 * Paint non-client area of control.
9178 * PARAMETER(S):
9179 * [I] infoPtr : valid pointer to the listview structureof the sender
9180 * [I] region : update region
9182 * RETURN:
9183 * TRUE - frame was painted
9184 * FALSE - call default window proc
9186 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9188 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9189 HDC dc;
9190 RECT r;
9191 HRGN cliprgn;
9192 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9193 cyEdge = GetSystemMetrics (SM_CYEDGE);
9195 if (!theme) return FALSE;
9197 GetWindowRect(infoPtr->hwndSelf, &r);
9199 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9200 r.right - cxEdge, r.bottom - cyEdge);
9201 if (region != (HRGN)1)
9202 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9203 OffsetRect(&r, -r.left, -r.top);
9205 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9206 OffsetRect(&r, -r.left, -r.top);
9208 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9209 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9210 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9211 ReleaseDC(infoPtr->hwndSelf, dc);
9213 /* Call default proc to get the scrollbars etc. painted */
9214 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9216 return TRUE;
9219 /***
9220 * DESCRIPTION:
9221 * Determines the type of structure to use.
9223 * PARAMETER(S):
9224 * [I] infoPtr : valid pointer to the listview structureof the sender
9225 * [I] hwndFrom : listview window handle
9226 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9228 * RETURN:
9229 * Zero
9231 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9233 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9235 if (nCommand == NF_REQUERY)
9236 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9238 return infoPtr->notifyFormat;
9241 /***
9242 * DESCRIPTION:
9243 * Paints/Repaints the listview control.
9245 * PARAMETER(S):
9246 * [I] infoPtr : valid pointer to the listview structure
9247 * [I] hdc : device context handle
9249 * RETURN:
9250 * Zero
9252 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9254 TRACE("(hdc=%p)\n", hdc);
9256 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9258 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9260 infoPtr->bNoItemMetrics = FALSE;
9261 LISTVIEW_UpdateItemSize(infoPtr);
9262 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9263 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9264 LISTVIEW_UpdateScroll(infoPtr);
9267 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9269 if (hdc)
9270 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9271 else
9273 PAINTSTRUCT ps;
9275 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9276 if (!hdc) return 1;
9277 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9278 EndPaint(infoPtr->hwndSelf, &ps);
9281 return 0;
9285 /***
9286 * DESCRIPTION:
9287 * Paints/Repaints the listview control.
9289 * PARAMETER(S):
9290 * [I] infoPtr : valid pointer to the listview structure
9291 * [I] hdc : device context handle
9292 * [I] options : drawing options
9294 * RETURN:
9295 * Zero
9297 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9299 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9301 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9302 return 0;
9304 if (options & PRF_ERASEBKGND)
9305 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9307 if (options & PRF_CLIENT)
9308 LISTVIEW_Paint(infoPtr, hdc);
9310 return 0;
9314 /***
9315 * DESCRIPTION:
9316 * Processes double click messages (right mouse button).
9318 * PARAMETER(S):
9319 * [I] infoPtr : valid pointer to the listview structure
9320 * [I] wKey : key flag
9321 * [I] x,y : mouse coordinate
9323 * RETURN:
9324 * Zero
9326 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9328 LVHITTESTINFO lvHitTestInfo;
9330 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9332 /* send NM_RELEASEDCAPTURE notification */
9333 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9335 /* send NM_RDBLCLK notification */
9336 lvHitTestInfo.pt.x = x;
9337 lvHitTestInfo.pt.y = y;
9338 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9339 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9341 return 0;
9344 /***
9345 * DESCRIPTION:
9346 * Processes mouse down messages (right mouse button).
9348 * PARAMETER(S):
9349 * [I] infoPtr : valid pointer to the listview structure
9350 * [I] wKey : key flag
9351 * [I] x,y : mouse coordinate
9353 * RETURN:
9354 * Zero
9356 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9358 LVHITTESTINFO lvHitTestInfo;
9359 INT nItem;
9361 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9363 /* send NM_RELEASEDCAPTURE notification */
9364 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9366 /* make sure the listview control window has the focus */
9367 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9369 /* set right button down flag */
9370 infoPtr->bRButtonDown = TRUE;
9372 /* determine the index of the selected item */
9373 lvHitTestInfo.pt.x = x;
9374 lvHitTestInfo.pt.y = y;
9375 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9377 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9379 LISTVIEW_SetItemFocus(infoPtr, nItem);
9380 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9381 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9382 LISTVIEW_SetSelection(infoPtr, nItem);
9384 else
9386 LISTVIEW_DeselectAll(infoPtr);
9389 return 0;
9392 /***
9393 * DESCRIPTION:
9394 * Processes mouse up messages (right mouse button).
9396 * PARAMETER(S):
9397 * [I] infoPtr : valid pointer to the listview structure
9398 * [I] wKey : key flag
9399 * [I] x,y : mouse coordinate
9401 * RETURN:
9402 * Zero
9404 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9406 LVHITTESTINFO lvHitTestInfo;
9407 POINT pt;
9409 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9411 if (!infoPtr->bRButtonDown) return 0;
9413 /* set button flag */
9414 infoPtr->bRButtonDown = FALSE;
9416 /* Send NM_RCLICK notification */
9417 lvHitTestInfo.pt.x = x;
9418 lvHitTestInfo.pt.y = y;
9419 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9420 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9422 /* Change to screen coordinate for WM_CONTEXTMENU */
9423 pt = lvHitTestInfo.pt;
9424 ClientToScreen(infoPtr->hwndSelf, &pt);
9426 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9427 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9428 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9430 return 0;
9434 /***
9435 * DESCRIPTION:
9436 * Sets the cursor.
9438 * PARAMETER(S):
9439 * [I] infoPtr : valid pointer to the listview structure
9440 * [I] hwnd : window handle of window containing the cursor
9441 * [I] nHittest : hit-test code
9442 * [I] wMouseMsg : ideintifier of the mouse message
9444 * RETURN:
9445 * TRUE if cursor is set
9446 * FALSE otherwise
9448 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9450 LVHITTESTINFO lvHitTestInfo;
9452 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9454 if(!infoPtr->hHotCursor) return FALSE;
9456 GetCursorPos(&lvHitTestInfo.pt);
9457 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9459 SetCursor(infoPtr->hHotCursor);
9461 return TRUE;
9464 /***
9465 * DESCRIPTION:
9466 * Sets the focus.
9468 * PARAMETER(S):
9469 * [I] infoPtr : valid pointer to the listview structure
9470 * [I] hwndLoseFocus : handle of previously focused window
9472 * RETURN:
9473 * Zero
9475 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9477 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9479 /* if we have the focus already, there's nothing to do */
9480 if (infoPtr->bFocus) return 0;
9482 /* send NM_SETFOCUS notification */
9483 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9485 /* set window focus flag */
9486 infoPtr->bFocus = TRUE;
9488 /* put the focus rect back on */
9489 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9491 /* redraw all visible selected items */
9492 LISTVIEW_InvalidateSelectedItems(infoPtr);
9494 return 0;
9497 /***
9498 * DESCRIPTION:
9499 * Sets the font.
9501 * PARAMETER(S):
9502 * [I] infoPtr : valid pointer to the listview structure
9503 * [I] fRedraw : font handle
9504 * [I] fRedraw : redraw flag
9506 * RETURN:
9507 * Zero
9509 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9511 HFONT oldFont = infoPtr->hFont;
9513 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9515 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9516 if (infoPtr->hFont == oldFont) return 0;
9518 LISTVIEW_SaveTextMetrics(infoPtr);
9520 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9522 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9523 LISTVIEW_UpdateSize(infoPtr);
9524 LISTVIEW_UpdateScroll(infoPtr);
9527 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9529 return 0;
9532 /***
9533 * DESCRIPTION:
9534 * Message handling for WM_SETREDRAW.
9535 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9537 * PARAMETER(S):
9538 * [I] infoPtr : valid pointer to the listview structure
9539 * [I] bRedraw: state of redraw flag
9541 * RETURN:
9542 * DefWinProc return value
9544 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9546 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9548 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9549 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9551 infoPtr->bRedraw = bRedraw;
9553 if(!bRedraw) return 0;
9555 if (is_autoarrange(infoPtr))
9556 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9557 LISTVIEW_UpdateScroll(infoPtr);
9559 /* despite what the WM_SETREDRAW docs says, apps expect us
9560 * to invalidate the listview here... stupid! */
9561 LISTVIEW_InvalidateList(infoPtr);
9563 return 0;
9566 /***
9567 * DESCRIPTION:
9568 * Resizes the listview control. This function processes WM_SIZE
9569 * messages. At this time, the width and height are not used.
9571 * PARAMETER(S):
9572 * [I] infoPtr : valid pointer to the listview structure
9573 * [I] Width : new width
9574 * [I] Height : new height
9576 * RETURN:
9577 * Zero
9579 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9581 RECT rcOld = infoPtr->rcList;
9583 TRACE("(width=%d, height=%d)\n", Width, Height);
9585 LISTVIEW_UpdateSize(infoPtr);
9586 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9588 /* do not bother with display related stuff if we're not redrawing */
9589 if (!is_redrawing(infoPtr)) return 0;
9591 if (is_autoarrange(infoPtr))
9592 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9594 LISTVIEW_UpdateScroll(infoPtr);
9596 /* refresh all only for lists whose height changed significantly */
9597 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9598 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9599 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9600 LISTVIEW_InvalidateList(infoPtr);
9602 return 0;
9605 /***
9606 * DESCRIPTION:
9607 * Sets the size information.
9609 * PARAMETER(S):
9610 * [I] infoPtr : valid pointer to the listview structure
9612 * RETURN:
9613 * None
9615 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9617 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9619 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9621 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9623 if (uView == LVS_LIST)
9625 /* Apparently the "LIST" style is supposed to have the same
9626 * number of items in a column even if there is no scroll bar.
9627 * Since if a scroll bar already exists then the bottom is already
9628 * reduced, only reduce if the scroll bar does not currently exist.
9629 * The "2" is there to mimic the native control. I think it may be
9630 * related to either padding or edges. (GLA 7/2002)
9632 if (!(infoPtr->dwStyle & WS_HSCROLL))
9633 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9634 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9636 else if (uView == LVS_REPORT)
9638 HDLAYOUT hl;
9639 WINDOWPOS wp;
9641 hl.prc = &infoPtr->rcList;
9642 hl.pwpos = &wp;
9643 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9644 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9645 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9646 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9647 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9648 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9650 infoPtr->rcList.top = max(wp.cy, 0);
9651 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9654 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9657 /***
9658 * DESCRIPTION:
9659 * Processes WM_STYLECHANGED messages.
9661 * PARAMETER(S):
9662 * [I] infoPtr : valid pointer to the listview structure
9663 * [I] wStyleType : window style type (normal or extended)
9664 * [I] lpss : window style information
9666 * RETURN:
9667 * Zero
9669 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9670 const STYLESTRUCT *lpss)
9672 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9673 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9674 UINT style;
9676 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9677 wStyleType, lpss->styleOld, lpss->styleNew);
9679 if (wStyleType != GWL_STYLE) return 0;
9681 infoPtr->dwStyle = lpss->styleNew;
9683 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9684 ((lpss->styleNew & WS_HSCROLL) == 0))
9685 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9687 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9688 ((lpss->styleNew & WS_VSCROLL) == 0))
9689 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9691 if (uNewView != uOldView)
9693 SIZE oldIconSize = infoPtr->iconSize;
9694 HIMAGELIST himl;
9696 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9697 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9699 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9700 SetRectEmpty(&infoPtr->rcFocus);
9702 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9703 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9705 if (uNewView == LVS_ICON)
9707 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9709 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9710 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9711 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9714 else if (uNewView == LVS_REPORT)
9716 HDLAYOUT hl;
9717 WINDOWPOS wp;
9719 LISTVIEW_CreateHeader( infoPtr );
9721 hl.prc = &infoPtr->rcList;
9722 hl.pwpos = &wp;
9723 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9724 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9725 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9726 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9729 LISTVIEW_UpdateItemSize(infoPtr);
9732 if (uNewView == LVS_REPORT)
9734 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9736 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9738 /* Turn off the header control */
9739 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9740 TRACE("Hide header control, was 0x%08x\n", style);
9741 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9742 } else {
9743 /* Turn on the header control */
9744 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9746 TRACE("Show header control, was 0x%08x\n", style);
9747 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9753 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9754 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9755 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9757 /* update the size of the client area */
9758 LISTVIEW_UpdateSize(infoPtr);
9760 /* add scrollbars if needed */
9761 LISTVIEW_UpdateScroll(infoPtr);
9763 /* invalidate client area + erase background */
9764 LISTVIEW_InvalidateList(infoPtr);
9766 return 0;
9769 /***
9770 * DESCRIPTION:
9771 * Processes WM_STYLECHANGING messages.
9773 * PARAMETER(S):
9774 * [I] infoPtr : valid pointer to the listview structure
9775 * [I] wStyleType : window style type (normal or extended)
9776 * [I0] lpss : window style information
9778 * RETURN:
9779 * Zero
9781 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9782 STYLESTRUCT *lpss)
9784 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9785 wStyleType, lpss->styleOld, lpss->styleNew);
9787 /* don't forward LVS_OWNERDATA only if not already set to */
9788 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9790 if (lpss->styleOld & LVS_OWNERDATA)
9791 lpss->styleNew |= LVS_OWNERDATA;
9792 else
9793 lpss->styleNew &= ~LVS_OWNERDATA;
9796 return 0;
9799 /***
9800 * DESCRIPTION:
9801 * Processes WM_SHOWWINDOW messages.
9803 * PARAMETER(S):
9804 * [I] infoPtr : valid pointer to the listview structure
9805 * [I] bShown : window is being shown (FALSE when hidden)
9806 * [I] iStatus : window show status
9808 * RETURN:
9809 * Zero
9811 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9813 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9815 /* header delayed creation */
9816 if ((uView == LVS_REPORT) && bShown)
9818 LISTVIEW_CreateHeader(infoPtr);
9820 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9821 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9824 return 0;
9827 /***
9828 * DESCRIPTION:
9829 * Processes CCM_GETVERSION messages.
9831 * PARAMETER(S):
9832 * [I] infoPtr : valid pointer to the listview structure
9834 * RETURN:
9835 * Current version
9837 static inline LRESULT LISTVIEW_GetVersion(LISTVIEW_INFO *infoPtr)
9839 return infoPtr->iVersion;
9842 /***
9843 * DESCRIPTION:
9844 * Processes CCM_SETVERSION messages.
9846 * PARAMETER(S):
9847 * [I] infoPtr : valid pointer to the listview structure
9848 * [I] iVersion : version to be set
9850 * RETURN:
9851 * -1 when requested version is greater than DLL version;
9852 * previous version otherwise
9854 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
9856 INT iOldVersion = infoPtr->iVersion;
9858 if (iVersion > COMCTL32_VERSION)
9859 return -1;
9861 infoPtr->iVersion = iVersion;
9863 TRACE("new version %d\n", iVersion);
9865 return iOldVersion;
9868 /***
9869 * DESCRIPTION:
9870 * Window procedure of the listview control.
9873 static LRESULT WINAPI
9874 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9876 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9878 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9880 if (!infoPtr && (uMsg != WM_NCCREATE))
9881 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9883 switch (uMsg)
9885 case LVM_APPROXIMATEVIEWRECT:
9886 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9887 LOWORD(lParam), HIWORD(lParam));
9888 case LVM_ARRANGE:
9889 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9891 /* case LVM_CANCELEDITLABEL: */
9893 case LVM_CREATEDRAGIMAGE:
9894 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9896 case LVM_DELETEALLITEMS:
9897 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9899 case LVM_DELETECOLUMN:
9900 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9902 case LVM_DELETEITEM:
9903 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9905 case LVM_EDITLABELW:
9906 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9908 case LVM_EDITLABELA:
9909 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9911 /* case LVM_ENABLEGROUPVIEW: */
9913 case LVM_ENSUREVISIBLE:
9914 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9916 case LVM_FINDITEMW:
9917 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9919 case LVM_FINDITEMA:
9920 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9922 case LVM_GETBKCOLOR:
9923 return infoPtr->clrBk;
9925 /* case LVM_GETBKIMAGE: */
9927 case LVM_GETCALLBACKMASK:
9928 return infoPtr->uCallbackMask;
9930 case LVM_GETCOLUMNA:
9931 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9933 case LVM_GETCOLUMNW:
9934 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9936 case LVM_GETCOLUMNORDERARRAY:
9937 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9939 case LVM_GETCOLUMNWIDTH:
9940 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9942 case LVM_GETCOUNTPERPAGE:
9943 return LISTVIEW_GetCountPerPage(infoPtr);
9945 case LVM_GETEDITCONTROL:
9946 return (LRESULT)infoPtr->hwndEdit;
9948 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9949 return infoPtr->dwLvExStyle;
9951 /* case LVM_GETGROUPINFO: */
9953 /* case LVM_GETGROUPMETRICS: */
9955 case LVM_GETHEADER:
9956 return (LRESULT)infoPtr->hwndHeader;
9958 case LVM_GETHOTCURSOR:
9959 return (LRESULT)infoPtr->hHotCursor;
9961 case LVM_GETHOTITEM:
9962 return infoPtr->nHotItem;
9964 case LVM_GETHOVERTIME:
9965 return infoPtr->dwHoverTime;
9967 case LVM_GETIMAGELIST:
9968 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9970 /* case LVM_GETINSERTMARK: */
9972 /* case LVM_GETINSERTMARKCOLOR: */
9974 /* case LVM_GETINSERTMARKRECT: */
9976 case LVM_GETISEARCHSTRINGA:
9977 case LVM_GETISEARCHSTRINGW:
9978 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9979 return FALSE;
9981 case LVM_GETITEMA:
9982 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9984 case LVM_GETITEMW:
9985 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9987 case LVM_GETITEMCOUNT:
9988 return infoPtr->nItemCount;
9990 case LVM_GETITEMPOSITION:
9991 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9993 case LVM_GETITEMRECT:
9994 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9996 case LVM_GETITEMSPACING:
9997 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9999 case LVM_GETITEMSTATE:
10000 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
10002 case LVM_GETITEMTEXTA:
10003 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10005 case LVM_GETITEMTEXTW:
10006 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10008 case LVM_GETNEXTITEM:
10009 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
10011 case LVM_GETNUMBEROFWORKAREAS:
10012 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
10013 return 1;
10015 case LVM_GETORIGIN:
10016 if (!lParam) return FALSE;
10017 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
10018 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
10019 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
10020 return TRUE;
10022 /* case LVM_GETOUTLINECOLOR: */
10024 /* case LVM_GETSELECTEDCOLUMN: */
10026 case LVM_GETSELECTEDCOUNT:
10027 return LISTVIEW_GetSelectedCount(infoPtr);
10029 case LVM_GETSELECTIONMARK:
10030 return infoPtr->nSelectionMark;
10032 case LVM_GETSTRINGWIDTHA:
10033 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
10035 case LVM_GETSTRINGWIDTHW:
10036 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
10038 case LVM_GETSUBITEMRECT:
10039 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
10041 case LVM_GETTEXTBKCOLOR:
10042 return infoPtr->clrTextBk;
10044 case LVM_GETTEXTCOLOR:
10045 return infoPtr->clrText;
10047 /* case LVM_GETTILEINFO: */
10049 /* case LVM_GETTILEVIEWINFO: */
10051 case LVM_GETTOOLTIPS:
10052 if( !infoPtr->hwndToolTip )
10053 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
10054 return (LRESULT)infoPtr->hwndToolTip;
10056 case LVM_GETTOPINDEX:
10057 return LISTVIEW_GetTopIndex(infoPtr);
10059 case LVM_GETUNICODEFORMAT:
10060 return (infoPtr->notifyFormat == NFR_UNICODE);
10062 /* case LVM_GETVIEW: */
10064 case LVM_GETVIEWRECT:
10065 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
10067 case LVM_GETWORKAREAS:
10068 FIXME("LVM_GETWORKAREAS: unimplemented\n");
10069 return FALSE;
10071 /* case LVM_HASGROUP: */
10073 case LVM_HITTEST:
10074 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
10076 case LVM_INSERTCOLUMNA:
10077 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10079 case LVM_INSERTCOLUMNW:
10080 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10082 /* case LVM_INSERTGROUP: */
10084 /* case LVM_INSERTGROUPSORTED: */
10086 case LVM_INSERTITEMA:
10087 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10089 case LVM_INSERTITEMW:
10090 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10092 /* case LVM_INSERTMARKHITTEST: */
10094 /* case LVM_ISGROUPVIEWENABLED: */
10096 /* case LVM_MAPIDTOINDEX: */
10098 /* case LVM_MAPINDEXTOID: */
10100 /* case LVM_MOVEGROUP: */
10102 /* case LVM_MOVEITEMTOGROUP: */
10104 case LVM_REDRAWITEMS:
10105 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
10107 /* case LVM_REMOVEALLGROUPS: */
10109 /* case LVM_REMOVEGROUP: */
10111 case LVM_SCROLL:
10112 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
10114 case LVM_SETBKCOLOR:
10115 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
10117 /* case LVM_SETBKIMAGE: */
10119 case LVM_SETCALLBACKMASK:
10120 infoPtr->uCallbackMask = (UINT)wParam;
10121 return TRUE;
10123 case LVM_SETCOLUMNA:
10124 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
10126 case LVM_SETCOLUMNW:
10127 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
10129 case LVM_SETCOLUMNORDERARRAY:
10130 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
10132 case LVM_SETCOLUMNWIDTH:
10133 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
10135 case LVM_SETEXTENDEDLISTVIEWSTYLE:
10136 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
10138 /* case LVM_SETGROUPINFO: */
10140 /* case LVM_SETGROUPMETRICS: */
10142 case LVM_SETHOTCURSOR:
10143 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
10145 case LVM_SETHOTITEM:
10146 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
10148 case LVM_SETHOVERTIME:
10149 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10151 case LVM_SETICONSPACING:
10152 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10154 case LVM_SETIMAGELIST:
10155 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10157 /* case LVM_SETINFOTIP: */
10159 /* case LVM_SETINSERTMARK: */
10161 /* case LVM_SETINSERTMARKCOLOR: */
10163 case LVM_SETITEMA:
10164 case LVM_SETITEMW:
10166 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
10167 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
10170 case LVM_SETITEMCOUNT:
10171 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10173 case LVM_SETITEMPOSITION:
10175 POINT pt;
10176 pt.x = (short)LOWORD(lParam);
10177 pt.y = (short)HIWORD(lParam);
10178 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10181 case LVM_SETITEMPOSITION32:
10182 if (lParam == 0) return FALSE;
10183 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10185 case LVM_SETITEMSTATE:
10186 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10188 case LVM_SETITEMTEXTA:
10189 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10191 case LVM_SETITEMTEXTW:
10192 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10194 /* case LVM_SETOUTLINECOLOR: */
10196 /* case LVM_SETSELECTEDCOLUMN: */
10198 case LVM_SETSELECTIONMARK:
10199 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10201 case LVM_SETTEXTBKCOLOR:
10202 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10204 case LVM_SETTEXTCOLOR:
10205 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10207 /* case LVM_SETTILEINFO: */
10209 /* case LVM_SETTILEVIEWINFO: */
10211 /* case LVM_SETTILEWIDTH: */
10213 case LVM_SETTOOLTIPS:
10214 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10216 case LVM_SETUNICODEFORMAT:
10217 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10219 /* case LVM_SETVIEW: */
10221 /* case LVM_SETWORKAREAS: */
10223 /* case LVM_SORTGROUPS: */
10225 case LVM_SORTITEMS:
10226 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10228 case LVM_SORTITEMSEX:
10229 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10231 case LVM_SUBITEMHITTEST:
10232 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10234 case LVM_UPDATE:
10235 return LISTVIEW_Update(infoPtr, (INT)wParam);
10237 case CCM_GETVERSION:
10238 return LISTVIEW_GetVersion(infoPtr);
10240 case CCM_SETVERSION:
10241 return LISTVIEW_SetVersion(infoPtr, wParam);
10243 case WM_CHAR:
10244 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10246 case WM_COMMAND:
10247 return LISTVIEW_Command(infoPtr, wParam, lParam);
10249 case WM_NCCREATE:
10250 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10252 case WM_CREATE:
10253 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10255 case WM_DESTROY:
10256 return LISTVIEW_Destroy(infoPtr);
10258 case WM_ENABLE:
10259 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10261 case WM_ERASEBKGND:
10262 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10264 case WM_GETDLGCODE:
10265 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10267 case WM_GETFONT:
10268 return (LRESULT)infoPtr->hFont;
10270 case WM_HSCROLL:
10271 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10273 case WM_KEYDOWN:
10274 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10276 case WM_KILLFOCUS:
10277 return LISTVIEW_KillFocus(infoPtr);
10279 case WM_LBUTTONDBLCLK:
10280 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10282 case WM_LBUTTONDOWN:
10283 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10285 case WM_LBUTTONUP:
10286 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10288 case WM_MOUSEMOVE:
10289 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10291 case WM_MOUSEHOVER:
10292 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10294 case WM_NCDESTROY:
10295 return LISTVIEW_NCDestroy(infoPtr);
10297 case WM_NCPAINT:
10298 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10299 return 0;
10300 goto fwd_msg;
10302 case WM_NOTIFY:
10303 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10304 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10305 else return 0;
10307 case WM_NOTIFYFORMAT:
10308 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10310 case WM_PRINTCLIENT:
10311 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10313 case WM_PAINT:
10314 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10316 case WM_RBUTTONDBLCLK:
10317 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10319 case WM_RBUTTONDOWN:
10320 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10322 case WM_RBUTTONUP:
10323 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10325 case WM_SETCURSOR:
10326 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10327 return TRUE;
10328 goto fwd_msg;
10330 case WM_SETFOCUS:
10331 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10333 case WM_SETFONT:
10334 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10336 case WM_SETREDRAW:
10337 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10339 case WM_SHOWWINDOW:
10340 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10341 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10343 case WM_SIZE:
10344 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10346 case WM_STYLECHANGED:
10347 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10349 case WM_STYLECHANGING:
10350 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10352 case WM_SYSCOLORCHANGE:
10353 COMCTL32_RefreshSysColors();
10354 return 0;
10356 /* case WM_TIMER: */
10357 case WM_THEMECHANGED:
10358 return LISTVIEW_ThemeChanged(infoPtr);
10360 case WM_VSCROLL:
10361 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10363 case WM_MOUSEWHEEL:
10364 if (wParam & (MK_SHIFT | MK_CONTROL))
10365 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10366 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10368 case WM_WINDOWPOSCHANGED:
10369 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10371 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10372 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10373 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10375 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10377 MEASUREITEMSTRUCT mis;
10378 mis.CtlType = ODT_LISTVIEW;
10379 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10380 mis.itemID = -1;
10381 mis.itemWidth = 0;
10382 mis.itemData = 0;
10383 mis.itemHeight= infoPtr->nItemHeight;
10384 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10385 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10386 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10389 LISTVIEW_UpdateSize(infoPtr);
10390 LISTVIEW_UpdateScroll(infoPtr);
10392 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10394 /* case WM_WININICHANGE: */
10396 default:
10397 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10398 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10400 fwd_msg:
10401 /* call default window procedure */
10402 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10407 /***
10408 * DESCRIPTION:
10409 * Registers the window class.
10411 * PARAMETER(S):
10412 * None
10414 * RETURN:
10415 * None
10417 void LISTVIEW_Register(void)
10419 WNDCLASSW wndClass;
10421 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10422 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10423 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10424 wndClass.cbClsExtra = 0;
10425 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10426 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10427 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10428 wndClass.lpszClassName = WC_LISTVIEWW;
10429 RegisterClassW(&wndClass);
10432 /***
10433 * DESCRIPTION:
10434 * Unregisters the window class.
10436 * PARAMETER(S):
10437 * None
10439 * RETURN:
10440 * None
10442 void LISTVIEW_Unregister(void)
10444 UnregisterClassW(WC_LISTVIEWW, NULL);
10447 /***
10448 * DESCRIPTION:
10449 * Handle any WM_COMMAND messages
10451 * PARAMETER(S):
10452 * [I] infoPtr : valid pointer to the listview structure
10453 * [I] wParam : the first message parameter
10454 * [I] lParam : the second message parameter
10456 * RETURN:
10457 * Zero.
10459 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10461 switch (HIWORD(wParam))
10463 case EN_UPDATE:
10466 * Adjust the edit window size
10468 WCHAR buffer[1024];
10469 HDC hdc = GetDC(infoPtr->hwndEdit);
10470 HFONT hFont, hOldFont = 0;
10471 RECT rect;
10472 SIZE sz;
10474 if (!infoPtr->hwndEdit || !hdc) return 0;
10475 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10476 GetWindowRect(infoPtr->hwndEdit, &rect);
10478 /* Select font to get the right dimension of the string */
10479 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10480 if(hFont != 0)
10482 hOldFont = SelectObject(hdc, hFont);
10485 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10487 TEXTMETRICW textMetric;
10489 /* Add Extra spacing for the next character */
10490 GetTextMetricsW(hdc, &textMetric);
10491 sz.cx += (textMetric.tmMaxCharWidth * 2);
10493 SetWindowPos (
10494 infoPtr->hwndEdit,
10495 HWND_TOP,
10498 sz.cx,
10499 rect.bottom - rect.top,
10500 SWP_DRAWFRAME|SWP_NOMOVE);
10502 if(hFont != 0)
10503 SelectObject(hdc, hOldFont);
10505 ReleaseDC(infoPtr->hwndEdit, hdc);
10507 break;
10510 default:
10511 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10514 return 0;
10518 /***
10519 * DESCRIPTION:
10520 * Subclassed edit control windproc function
10522 * PARAMETER(S):
10523 * [I] hwnd : the edit window handle
10524 * [I] uMsg : the message that is to be processed
10525 * [I] wParam : first message parameter
10526 * [I] lParam : second message parameter
10527 * [I] isW : TRUE if input is Unicode
10529 * RETURN:
10530 * Zero.
10532 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10534 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10535 BOOL cancel = FALSE;
10537 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10538 hwnd, uMsg, wParam, lParam, isW);
10540 switch (uMsg)
10542 case WM_GETDLGCODE:
10543 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10545 case WM_KILLFOCUS:
10546 break;
10548 case WM_DESTROY:
10550 WNDPROC editProc = infoPtr->EditWndProc;
10551 infoPtr->EditWndProc = 0;
10552 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10553 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10556 case WM_KEYDOWN:
10557 if (VK_ESCAPE == (INT)wParam)
10559 cancel = TRUE;
10560 break;
10562 else if (VK_RETURN == (INT)wParam)
10563 break;
10565 default:
10566 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10569 /* kill the edit */
10570 if (infoPtr->hwndEdit)
10572 LPWSTR buffer = NULL;
10574 infoPtr->hwndEdit = 0;
10575 if (!cancel)
10577 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10579 if (len)
10581 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10583 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10584 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10588 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10590 Free(buffer);
10593 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10594 return 0;
10597 /***
10598 * DESCRIPTION:
10599 * Subclassed edit control Unicode windproc function
10601 * PARAMETER(S):
10602 * [I] hwnd : the edit window handle
10603 * [I] uMsg : the message that is to be processed
10604 * [I] wParam : first message parameter
10605 * [I] lParam : second message parameter
10607 * RETURN:
10609 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10611 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10614 /***
10615 * DESCRIPTION:
10616 * Subclassed edit control ANSI windproc function
10618 * PARAMETER(S):
10619 * [I] hwnd : the edit window handle
10620 * [I] uMsg : the message that is to be processed
10621 * [I] wParam : first message parameter
10622 * [I] lParam : second message parameter
10624 * RETURN:
10626 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10628 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10631 /***
10632 * DESCRIPTION:
10633 * Creates a subclassed edit control
10635 * PARAMETER(S):
10636 * [I] infoPtr : valid pointer to the listview structure
10637 * [I] text : initial text for the edit
10638 * [I] style : the window style
10639 * [I] isW : TRUE if input is Unicode
10641 * RETURN:
10643 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10644 INT x, INT y, INT width, INT height, BOOL isW)
10646 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10647 HWND hedit;
10648 SIZE sz;
10649 HDC hdc;
10650 HDC hOldFont=0;
10651 TEXTMETRICW textMetric;
10652 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10654 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10656 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10657 hdc = GetDC(infoPtr->hwndSelf);
10659 /* Select the font to get appropriate metric dimensions */
10660 if(infoPtr->hFont != 0)
10661 hOldFont = SelectObject(hdc, infoPtr->hFont);
10663 /*Get String Length in pixels */
10664 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10666 /*Add Extra spacing for the next character */
10667 GetTextMetricsW(hdc, &textMetric);
10668 sz.cx += (textMetric.tmMaxCharWidth * 2);
10670 if(infoPtr->hFont != 0)
10671 SelectObject(hdc, hOldFont);
10673 ReleaseDC(infoPtr->hwndSelf, hdc);
10674 if (isW)
10675 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10676 else
10677 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10679 if (!hedit) return 0;
10681 infoPtr->EditWndProc = (WNDPROC)
10682 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10683 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10685 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10687 return hedit;