comctl32/listview: Fix a regression caused by 9c1a0e468f5cfbe9d863852ed5a42390f2cbfeb4.
[wine.git] / dlls / comctl32 / listview.c
blobd249cff9cfa8c80ea4170ab3855056583067f8f7
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
62 * Speedups
63 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
64 * linear in the number of items in the list, and this is
65 * unacceptable for large lists.
66 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
67 * instead of inserting in the right spot
68 * -- we should keep an ordered array of coordinates in iconic mode
69 * this would allow to frame items (iterator_frameditems),
70 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
72 * Flags
73 * -- LVIF_COLUMNS
74 * -- LVIF_GROUPID
75 * -- LVIF_NORECOMPUTE
77 * States
78 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
79 * -- LVIS_CUT
80 * -- LVIS_DROPHILITED
81 * -- LVIS_OVERLAYMASK
83 * Styles
84 * -- LVS_NOLABELWRAP
85 * -- LVS_NOSCROLL (see Q137520)
86 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
87 * -- LVS_ALIGNTOP
88 * -- LVS_TYPESTYLEMASK
90 * Extended Styles
91 * -- LVS_EX_BORDERSELECT
92 * -- LVS_EX_FLATSB
93 * -- LVS_EX_HEADERDRAGDROP
94 * -- LVS_EX_INFOTIP
95 * -- LVS_EX_LABELTIP
96 * -- LVS_EX_MULTIWORKAREAS
97 * -- LVS_EX_REGIONAL
98 * -- LVS_EX_SIMPLESELECT
99 * -- LVS_EX_TWOCLICKACTIVATE
100 * -- LVS_EX_UNDERLINECOLD
101 * -- LVS_EX_UNDERLINEHOT
103 * Notifications:
104 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
105 * -- LVN_GETINFOTIP
106 * -- LVN_HOTTRACK
107 * -- LVN_MARQUEEBEGIN
108 * -- LVN_SETDISPINFO
109 * -- NM_HOVER
110 * -- LVN_BEGINRDRAG
112 * Messages:
113 * -- LVM_CANCELEDITLABEL
114 * -- LVM_ENABLEGROUPVIEW
115 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
116 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
117 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
118 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
119 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
120 * -- LVM_GETINSERTMARKRECT
121 * -- LVM_GETNUMBEROFWORKAREAS
122 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
123 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
124 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
125 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
126 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
127 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
128 * -- LVM_GETVIEW, LVM_SETVIEW
129 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
130 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
131 * -- LVM_INSERTGROUPSORTED
132 * -- LVM_INSERTMARKHITTEST
133 * -- LVM_ISGROUPVIEWENABLED
134 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
135 * -- LVM_MOVEGROUP
136 * -- LVM_MOVEITEMTOGROUP
137 * -- LVM_SETINFOTIP
138 * -- LVM_SETTILEWIDTH
139 * -- LVM_SORTGROUPS
141 * Macros:
142 * -- ListView_GetCheckSate, ListView_SetCheckState
143 * -- ListView_GetHoverTime, ListView_SetHoverTime
144 * -- ListView_GetISearchString
145 * -- ListView_GetNumberOfWorkAreas
146 * -- ListView_GetOrigin
147 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
148 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
150 * Functions:
151 * -- LVGroupComparE
153 * Known differences in message stream from native control (not known if
154 * these differences cause problems):
155 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
156 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
157 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
158 * processing for "USEDOUBLECLICKTIME".
161 #include "config.h"
162 #include "wine/port.h"
164 #include <assert.h>
165 #include <ctype.h>
166 #include <string.h>
167 #include <stdlib.h>
168 #include <stdarg.h>
169 #include <stdio.h>
171 #include "windef.h"
172 #include "winbase.h"
173 #include "winnt.h"
174 #include "wingdi.h"
175 #include "winuser.h"
176 #include "winnls.h"
177 #include "commctrl.h"
178 #include "comctl32.h"
179 #include "uxtheme.h"
181 #include "wine/debug.h"
182 #include "wine/unicode.h"
184 WINE_DEFAULT_DEBUG_CHANNEL(listview);
186 /* make sure you set this to 0 for production use! */
187 #define DEBUG_RANGES 1
189 typedef struct tagCOLUMN_INFO
191 RECT rcHeader; /* tracks the header's rectangle */
192 int fmt; /* same as LVCOLUMN.fmt */
193 } COLUMN_INFO;
195 typedef struct tagITEMHDR
197 LPWSTR pszText;
198 INT iImage;
199 } ITEMHDR, *LPITEMHDR;
201 typedef struct tagSUBITEM_INFO
203 ITEMHDR hdr;
204 INT iSubItem;
205 } SUBITEM_INFO;
207 typedef struct tagITEM_INFO
209 ITEMHDR hdr;
210 UINT state;
211 LPARAM lParam;
212 INT iIndent;
213 } ITEM_INFO;
215 typedef struct tagRANGE
217 INT lower;
218 INT upper;
219 } RANGE;
221 typedef struct tagRANGES
223 HDPA hdpa;
224 } *RANGES;
226 typedef struct tagITERATOR
228 INT nItem;
229 INT nSpecial;
230 RANGE range;
231 RANGES ranges;
232 INT index;
233 } ITERATOR;
235 typedef struct tagDELAYED_ITEM_EDIT
237 BOOL fEnabled;
238 INT iItem;
239 } DELAYED_ITEM_EDIT;
241 typedef struct tagLISTVIEW_INFO
243 HWND hwndSelf;
244 HBRUSH hBkBrush;
245 COLORREF clrBk;
246 COLORREF clrText;
247 COLORREF clrTextBk;
248 HIMAGELIST himlNormal;
249 HIMAGELIST himlSmall;
250 HIMAGELIST himlState;
251 BOOL bLButtonDown;
252 BOOL bRButtonDown;
253 BOOL bDragging;
254 POINT ptClickPos; /* point where the user clicked */
255 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
256 INT nItemHeight;
257 INT nItemWidth;
258 RANGES selectionRanges;
259 INT nSelectionMark;
260 INT nHotItem;
261 SHORT notifyFormat;
262 HWND hwndNotify;
263 RECT rcList; /* This rectangle is really the window
264 * client rectangle possibly reduced by the
265 * horizontal scroll bar and/or header - see
266 * LISTVIEW_UpdateSize. This rectangle offset
267 * by the LISTVIEW_GetOrigin value is in
268 * client coordinates */
269 SIZE iconSize;
270 SIZE iconSpacing;
271 SIZE iconStateSize;
272 UINT uCallbackMask;
273 HWND hwndHeader;
274 HCURSOR hHotCursor;
275 HFONT hDefaultFont;
276 HFONT hFont;
277 INT ntmHeight; /* Some cached metrics of the font used */
278 INT ntmMaxCharWidth; /* by the listview to draw items */
279 INT nEllipsisWidth;
280 BOOL bRedraw; /* Turns on/off repaints & invalidations */
281 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
282 BOOL bFocus;
283 BOOL bDoChangeNotify; /* send change notification messages? */
284 INT nFocusedItem;
285 RECT rcFocus;
286 DWORD dwStyle; /* the cached window GWL_STYLE */
287 DWORD dwLvExStyle; /* extended listview style */
288 INT nItemCount; /* the number of items in the list */
289 HDPA hdpaItems; /* array ITEM_INFO pointers */
290 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
291 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
292 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
293 POINT currIconPos; /* this is the position next icon will be placed */
294 PFNLVCOMPARE pfnCompare;
295 LPARAM lParamSort;
296 HWND hwndEdit;
297 WNDPROC EditWndProc;
298 INT nEditLabelItem;
299 DWORD dwHoverTime;
300 HWND hwndToolTip;
302 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
304 DWORD lastKeyPressTimestamp;
305 WPARAM charCode;
306 INT nSearchParamLength;
307 WCHAR szSearchParam[ MAX_PATH ];
308 BOOL bIsDrawing;
309 INT nMeasureItemHeight;
310 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
311 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
312 } LISTVIEW_INFO;
315 * constants
317 /* How many we debug buffer to allocate */
318 #define DEBUG_BUFFERS 20
319 /* The size of a single debug bbuffer */
320 #define DEBUG_BUFFER_SIZE 256
322 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
323 #define SB_INTERNAL -1
325 /* maximum size of a label */
326 #define DISP_TEXT_SIZE 512
328 /* padding for items in list and small icon display modes */
329 #define WIDTH_PADDING 12
331 /* padding for items in list, report and small icon display modes */
332 #define HEIGHT_PADDING 1
334 /* offset of items in report display mode */
335 #define REPORT_MARGINX 2
337 /* padding for icon in large icon display mode
338 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
339 * that HITTEST will see.
340 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
341 * ICON_TOP_PADDING - sum of the two above.
342 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
343 * LABEL_HOR_PADDING - between text and sides of box
344 * LABEL_VERT_PADDING - between bottom of text and end of box
346 * ICON_LR_PADDING - additional width above icon size.
347 * ICON_LR_HALF - half of the above value
349 #define ICON_TOP_PADDING_NOTHITABLE 2
350 #define ICON_TOP_PADDING_HITABLE 2
351 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
352 #define ICON_BOTTOM_PADDING 4
353 #define LABEL_HOR_PADDING 5
354 #define LABEL_VERT_PADDING 7
355 #define ICON_LR_PADDING 16
356 #define ICON_LR_HALF (ICON_LR_PADDING/2)
358 /* default label width for items in list and small icon display modes */
359 #define DEFAULT_LABEL_WIDTH 40
361 /* default column width for items in list display mode */
362 #define DEFAULT_COLUMN_WIDTH 128
364 /* Size of "line" scroll for V & H scrolls */
365 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
367 /* Padding between image and label */
368 #define IMAGE_PADDING 2
370 /* Padding behind the label */
371 #define TRAILING_LABEL_PADDING 12
372 #define TRAILING_HEADER_PADDING 11
374 /* Border for the icon caption */
375 #define CAPTION_BORDER 2
377 /* Standard DrawText flags */
378 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
379 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
380 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
382 /* Image index from state */
383 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
385 /* The time in milliseconds to reset the search in the list */
386 #define KEY_DELAY 450
388 /* Dump the LISTVIEW_INFO structure to the debug channel */
389 #define LISTVIEW_DUMP(iP) do { \
390 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
391 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
392 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
393 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
394 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
395 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
396 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
397 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
398 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
399 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
400 } while(0)
402 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
405 * forward declarations
407 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
408 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
409 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
410 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
411 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
412 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
413 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
414 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
415 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
416 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
417 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
418 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
419 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
420 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
421 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
422 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
423 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
424 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
425 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
426 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
427 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
428 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
429 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
430 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
431 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
432 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
434 /******** Text handling functions *************************************/
436 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
437 * text string. The string may be ANSI or Unicode, in which case
438 * the boolean isW tells us the type of the string.
440 * The name of the function tell what type of strings it expects:
441 * W: Unicode, T: ANSI/Unicode - function of isW
444 static inline BOOL is_textW(LPCWSTR text)
446 return text != NULL && text != LPSTR_TEXTCALLBACKW;
449 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
451 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
452 return is_textW(text);
455 static inline int textlenT(LPCWSTR text, BOOL isW)
457 return !is_textT(text, isW) ? 0 :
458 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
461 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
463 if (isDestW)
464 if (isSrcW) lstrcpynW(dest, src, max);
465 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
466 else
467 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
468 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
471 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
473 LPWSTR wstr = (LPWSTR)text;
475 if (!isW && is_textT(text, isW))
477 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
478 wstr = Alloc(len * sizeof(WCHAR));
479 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
481 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
482 return wstr;
485 static inline void textfreeT(LPWSTR wstr, BOOL isW)
487 if (!isW && is_textT(wstr, isW)) Free (wstr);
491 * dest is a pointer to a Unicode string
492 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
494 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
496 BOOL bResult = TRUE;
498 if (src == LPSTR_TEXTCALLBACKW)
500 if (is_textW(*dest)) Free(*dest);
501 *dest = LPSTR_TEXTCALLBACKW;
503 else
505 LPWSTR pszText = textdupTtoW(src, isW);
506 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
507 bResult = Str_SetPtrW(dest, pszText);
508 textfreeT(pszText, isW);
510 return bResult;
514 * compares a Unicode to a Unicode/ANSI text string
516 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
518 if (!aw) return bt ? -1 : 0;
519 if (!bt) return aw ? 1 : 0;
520 if (aw == LPSTR_TEXTCALLBACKW)
521 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
522 if (bt != LPSTR_TEXTCALLBACKW)
524 LPWSTR bw = textdupTtoW(bt, isW);
525 int r = bw ? lstrcmpW(aw, bw) : 1;
526 textfreeT(bw, isW);
527 return r;
530 return 1;
533 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
535 int res;
537 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
538 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
539 return res ? res - sizeof(WCHAR) : res;
542 /******** Debugging functions *****************************************/
544 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
546 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
547 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
550 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
552 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
553 n = min(textlenT(text, isW), n);
554 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
557 static char* debug_getbuf(void)
559 static int index = 0;
560 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
561 return buffers[index++ % DEBUG_BUFFERS];
564 static inline const char* debugrange(const RANGE *lprng)
566 if (!lprng) return "(null)";
567 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
570 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
572 char* buf = debug_getbuf(), *text = buf;
573 int len, size = DEBUG_BUFFER_SIZE;
575 if (pScrollInfo == NULL) return "(null)";
576 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
577 if (len == -1) goto end; buf += len; size -= len;
578 if (pScrollInfo->fMask & SIF_RANGE)
579 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
580 else len = 0;
581 if (len == -1) goto end; buf += len; size -= len;
582 if (pScrollInfo->fMask & SIF_PAGE)
583 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
584 else len = 0;
585 if (len == -1) goto end; buf += len; size -= len;
586 if (pScrollInfo->fMask & SIF_POS)
587 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
588 else len = 0;
589 if (len == -1) goto end; buf += len; size -= len;
590 if (pScrollInfo->fMask & SIF_TRACKPOS)
591 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
592 else len = 0;
593 if (len == -1) goto end; buf += len; size -= len;
594 goto undo;
595 end:
596 buf = text + strlen(text);
597 undo:
598 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
599 return text;
602 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
604 if (!plvnm) return "(null)";
605 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
606 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
607 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
608 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
611 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
613 char* buf = debug_getbuf(), *text = buf;
614 int len, size = DEBUG_BUFFER_SIZE;
616 if (lpLVItem == NULL) return "(null)";
617 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
618 if (len == -1) goto end; buf += len; size -= len;
619 if (lpLVItem->mask & LVIF_STATE)
620 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
621 else len = 0;
622 if (len == -1) goto end; buf += len; size -= len;
623 if (lpLVItem->mask & LVIF_TEXT)
624 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
625 else len = 0;
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_IMAGE)
628 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
629 else len = 0;
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_PARAM)
632 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
633 else len = 0;
634 if (len == -1) goto end; buf += len; size -= len;
635 if (lpLVItem->mask & LVIF_INDENT)
636 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
637 else len = 0;
638 if (len == -1) goto end; buf += len; size -= len;
639 goto undo;
640 end:
641 buf = text + strlen(text);
642 undo:
643 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
644 return text;
647 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
649 char* buf = debug_getbuf(), *text = buf;
650 int len, size = DEBUG_BUFFER_SIZE;
652 if (lpColumn == NULL) return "(null)";
653 len = snprintf(buf, size, "{");
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpColumn->mask & LVCF_SUBITEM)
656 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
657 else len = 0;
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpColumn->mask & LVCF_FMT)
660 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
661 else len = 0;
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_WIDTH)
664 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
665 else len = 0;
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_TEXT)
668 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
669 else len = 0;
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_IMAGE)
672 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
673 else len = 0;
674 if (len == -1) goto end; buf += len; size -= len;
675 if (lpColumn->mask & LVCF_ORDER)
676 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
677 else len = 0;
678 if (len == -1) goto end; buf += len; size -= len;
679 goto undo;
680 end:
681 buf = text + strlen(text);
682 undo:
683 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
684 return text;
687 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
689 if (!lpht) return "(null)";
691 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
692 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
695 /* Return the corresponding text for a given scroll value */
696 static inline LPCSTR debugscrollcode(int nScrollCode)
698 switch(nScrollCode)
700 case SB_LINELEFT: return "SB_LINELEFT";
701 case SB_LINERIGHT: return "SB_LINERIGHT";
702 case SB_PAGELEFT: return "SB_PAGELEFT";
703 case SB_PAGERIGHT: return "SB_PAGERIGHT";
704 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
705 case SB_THUMBTRACK: return "SB_THUMBTRACK";
706 case SB_ENDSCROLL: return "SB_ENDSCROLL";
707 case SB_INTERNAL: return "SB_INTERNAL";
708 default: return "unknown";
713 /******** Notification functions ************************************/
715 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
717 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
718 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
721 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
723 LRESULT result;
725 TRACE("(code=%d)\n", code);
727 pnmh->hwndFrom = infoPtr->hwndSelf;
728 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
729 pnmh->code = code;
730 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
732 TRACE(" <= %ld\n", result);
734 return result;
737 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
739 NMHDR nmh;
740 HWND hwnd = infoPtr->hwndSelf;
741 notify_hdr(infoPtr, code, &nmh);
742 return IsWindow(hwnd);
745 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
747 NMITEMACTIVATE nmia;
748 LVITEMW item;
750 if (htInfo) {
751 nmia.uNewState = 0;
752 nmia.uOldState = 0;
753 nmia.uChanged = 0;
754 nmia.uKeyFlags = 0;
756 item.mask = LVIF_PARAM|LVIF_STATE;
757 item.iItem = htInfo->iItem;
758 item.iSubItem = 0;
759 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
760 nmia.lParam = item.lParam;
761 nmia.uOldState = item.state;
762 nmia.uNewState = item.state | LVIS_ACTIVATING;
763 nmia.uChanged = LVIF_STATE;
766 nmia.iItem = htInfo->iItem;
767 nmia.iSubItem = htInfo->iSubItem;
768 nmia.ptAction = htInfo->pt;
770 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
771 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
772 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
774 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
777 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
779 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
780 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
783 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
785 NMLISTVIEW nmlv;
786 LVITEMW item;
787 HWND hwnd = infoPtr->hwndSelf;
789 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
790 ZeroMemory(&nmlv, sizeof(nmlv));
791 nmlv.iItem = lvht->iItem;
792 nmlv.iSubItem = lvht->iSubItem;
793 nmlv.ptAction = lvht->pt;
794 item.mask = LVIF_PARAM;
795 item.iItem = lvht->iItem;
796 item.iSubItem = 0;
797 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
798 notify_listview(infoPtr, code, &nmlv);
799 return IsWindow(hwnd);
802 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
804 NMLISTVIEW nmlv;
805 LVITEMW item;
806 HWND hwnd = infoPtr->hwndSelf;
808 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
809 nmlv.iItem = nItem;
810 item.mask = LVIF_PARAM;
811 item.iItem = nItem;
812 item.iSubItem = 0;
813 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
814 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
815 return IsWindow(hwnd);
818 static int get_ansi_notification(UINT unicodeNotificationCode)
820 switch (unicodeNotificationCode)
822 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
823 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
824 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
825 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
826 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
827 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
829 ERR("unknown notification %x\n", unicodeNotificationCode);
830 assert(FALSE);
831 return 0;
835 Send notification. depends on dispinfoW having same
836 structure as dispinfoA.
837 infoPtr : listview struct
838 notificationCode : *Unicode* notification code
839 pdi : dispinfo structure (can be unicode or ansi)
840 isW : TRUE if dispinfo is Unicode
842 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
844 BOOL bResult = FALSE;
845 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
846 INT cchTempBufMax = 0, savCchTextMax = 0;
847 UINT realNotifCode;
848 LPWSTR pszTempBuf = NULL, savPszText = NULL;
850 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
852 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
853 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
856 if (convertToAnsi || convertToUnicode)
858 if (notificationCode != LVN_GETDISPINFOW)
860 cchTempBufMax = convertToUnicode ?
861 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
862 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
864 else
866 cchTempBufMax = pdi->item.cchTextMax;
867 *pdi->item.pszText = 0; /* make sure we don't process garbage */
870 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
871 if (!pszTempBuf) return FALSE;
873 if (convertToUnicode)
874 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
875 pszTempBuf, cchTempBufMax);
876 else
877 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
878 cchTempBufMax, NULL, NULL);
880 savCchTextMax = pdi->item.cchTextMax;
881 savPszText = pdi->item.pszText;
882 pdi->item.pszText = pszTempBuf;
883 pdi->item.cchTextMax = cchTempBufMax;
886 if (infoPtr->notifyFormat == NFR_ANSI)
887 realNotifCode = get_ansi_notification(notificationCode);
888 else
889 realNotifCode = notificationCode;
890 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
891 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
893 if (convertToUnicode || convertToAnsi)
895 if (convertToUnicode) /* note : pointer can be changed by app ! */
896 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
897 savCchTextMax, NULL, NULL);
898 else
899 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
900 savPszText, savCchTextMax);
901 pdi->item.pszText = savPszText; /* restores our buffer */
902 pdi->item.cchTextMax = savCchTextMax;
903 Free (pszTempBuf);
905 return bResult;
908 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
909 const RECT *rcBounds, const LVITEMW *lplvItem)
911 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
912 lpnmlvcd->nmcd.hdc = hdc;
913 lpnmlvcd->nmcd.rc = *rcBounds;
914 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
915 lpnmlvcd->clrText = infoPtr->clrText;
916 if (!lplvItem) return;
917 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
918 lpnmlvcd->iSubItem = lplvItem->iSubItem;
919 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
920 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
921 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
922 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
925 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
927 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
928 DWORD result;
930 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
931 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
932 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
933 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
934 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
935 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
936 return result;
939 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
941 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
942 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
943 if (lpnmlvcd->clrText == CLR_DEFAULT)
944 lpnmlvcd->clrText = comctl32_color.clrWindowText;
946 /* apparently, for selected items, we have to override the returned values */
947 if (!SubItem)
949 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
951 if (infoPtr->bFocus)
953 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
954 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
956 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
958 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
959 lpnmlvcd->clrText = comctl32_color.clrBtnText;
964 /* Set the text attributes */
965 if (lpnmlvcd->clrTextBk != CLR_NONE)
967 SetBkMode(hdc, OPAQUE);
968 SetBkColor(hdc,lpnmlvcd->clrTextBk);
970 else
971 SetBkMode(hdc, TRANSPARENT);
972 SetTextColor(hdc, lpnmlvcd->clrText);
975 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
977 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
980 /******** Item iterator functions **********************************/
982 static RANGES ranges_create(int count);
983 static void ranges_destroy(RANGES ranges);
984 static BOOL ranges_add(RANGES ranges, RANGE range);
985 static BOOL ranges_del(RANGES ranges, RANGE range);
986 static void ranges_dump(RANGES ranges);
988 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
990 RANGE range = { nItem, nItem + 1 };
992 return ranges_add(ranges, range);
995 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
997 RANGE range = { nItem, nItem + 1 };
999 return ranges_del(ranges, range);
1002 /***
1003 * ITERATOR DOCUMENTATION
1005 * The iterator functions allow for easy, and convenient iteration
1006 * over items of interest in the list. Typically, you create a
1007 * iterator, use it, and destroy it, as such:
1008 * ITERATOR i;
1010 * iterator_xxxitems(&i, ...);
1011 * while (iterator_{prev,next}(&i)
1013 * //code which uses i.nItem
1015 * iterator_destroy(&i);
1017 * where xxx is either: framed, or visible.
1018 * Note that it is important that the code destroys the iterator
1019 * after it's done with it, as the creation of the iterator may
1020 * allocate memory, which thus needs to be freed.
1022 * You can iterate both forwards, and backwards through the list,
1023 * by using iterator_next or iterator_prev respectively.
1025 * Lower numbered items are draw on top of higher number items in
1026 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1027 * items may overlap). So, to test items, you should use
1028 * iterator_next
1029 * which lists the items top to bottom (in Z-order).
1030 * For drawing items, you should use
1031 * iterator_prev
1032 * which lists the items bottom to top (in Z-order).
1033 * If you keep iterating over the items after the end-of-items
1034 * marker (-1) is returned, the iterator will start from the
1035 * beginning. Typically, you don't need to test for -1,
1036 * because iterator_{next,prev} will return TRUE if more items
1037 * are to be iterated over, or FALSE otherwise.
1039 * Note: the iterator is defined to be bidirectional. That is,
1040 * any number of prev followed by any number of next, or
1041 * five versa, should leave the iterator at the same item:
1042 * prev * n, next * n = next * n, prev * n
1044 * The iterator has a notion of an out-of-order, special item,
1045 * which sits at the start of the list. This is used in
1046 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1047 * which needs to be first, as it may overlap other items.
1049 * The code is a bit messy because we have:
1050 * - a special item to deal with
1051 * - simple range, or composite range
1052 * - empty range.
1053 * If you find bugs, or want to add features, please make sure you
1054 * always check/modify *both* iterator_prev, and iterator_next.
1057 /****
1058 * This function iterates through the items in increasing order,
1059 * but prefixed by the special item, then -1. That is:
1060 * special, 1, 2, 3, ..., n, -1.
1061 * Each item is listed only once.
1063 static inline BOOL iterator_next(ITERATOR* i)
1065 if (i->nItem == -1)
1067 i->nItem = i->nSpecial;
1068 if (i->nItem != -1) return TRUE;
1070 if (i->nItem == i->nSpecial)
1072 if (i->ranges) i->index = 0;
1073 goto pickarange;
1076 i->nItem++;
1077 testitem:
1078 if (i->nItem == i->nSpecial) i->nItem++;
1079 if (i->nItem < i->range.upper) return TRUE;
1081 pickarange:
1082 if (i->ranges)
1084 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1085 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1086 else goto end;
1088 else if (i->nItem >= i->range.upper) goto end;
1090 i->nItem = i->range.lower;
1091 if (i->nItem >= 0) goto testitem;
1092 end:
1093 i->nItem = -1;
1094 return FALSE;
1097 /****
1098 * This function iterates through the items in decreasing order,
1099 * followed by the special item, then -1. That is:
1100 * n, n-1, ..., 3, 2, 1, special, -1.
1101 * Each item is listed only once.
1103 static inline BOOL iterator_prev(ITERATOR* i)
1105 BOOL start = FALSE;
1107 if (i->nItem == -1)
1109 start = TRUE;
1110 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1111 goto pickarange;
1113 if (i->nItem == i->nSpecial)
1115 i->nItem = -1;
1116 return FALSE;
1119 testitem:
1120 i->nItem--;
1121 if (i->nItem == i->nSpecial) i->nItem--;
1122 if (i->nItem >= i->range.lower) return TRUE;
1124 pickarange:
1125 if (i->ranges)
1127 if (i->index > 0)
1128 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1129 else goto end;
1131 else if (!start && i->nItem < i->range.lower) goto end;
1133 i->nItem = i->range.upper;
1134 if (i->nItem > 0) goto testitem;
1135 end:
1136 return (i->nItem = i->nSpecial) != -1;
1139 static RANGE iterator_range(const ITERATOR *i)
1141 RANGE range;
1143 if (!i->ranges) return i->range;
1145 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1147 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1148 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1150 else range.lower = range.upper = 0;
1152 return range;
1155 /***
1156 * Releases resources associated with this ierator.
1158 static inline void iterator_destroy(const ITERATOR *i)
1160 ranges_destroy(i->ranges);
1163 /***
1164 * Create an empty iterator.
1166 static inline BOOL iterator_empty(ITERATOR* i)
1168 ZeroMemory(i, sizeof(*i));
1169 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1170 return TRUE;
1173 /***
1174 * Create an iterator over a range.
1176 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1178 iterator_empty(i);
1179 i->range = range;
1180 return TRUE;
1183 /***
1184 * Create an iterator over a bunch of ranges.
1185 * Please note that the iterator will take ownership of the ranges,
1186 * and will free them upon destruction.
1188 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1190 iterator_empty(i);
1191 i->ranges = ranges;
1192 return TRUE;
1195 /***
1196 * Creates an iterator over the items which intersect lprc.
1198 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1200 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1201 RECT frame = *lprc, rcItem, rcTemp;
1202 POINT Origin;
1204 /* in case we fail, we want to return an empty iterator */
1205 if (!iterator_empty(i)) return FALSE;
1207 LISTVIEW_GetOrigin(infoPtr, &Origin);
1209 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1210 OffsetRect(&frame, -Origin.x, -Origin.y);
1212 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1214 INT nItem;
1216 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1218 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1219 if (IntersectRect(&rcTemp, &rcItem, lprc))
1220 i->nSpecial = infoPtr->nFocusedItem;
1222 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1223 /* to do better here, we need to have PosX, and PosY sorted */
1224 TRACE("building icon ranges:\n");
1225 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1227 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1228 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1229 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1230 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1231 if (IntersectRect(&rcTemp, &rcItem, &frame))
1232 ranges_additem(i->ranges, nItem);
1234 return TRUE;
1236 else if (uView == LVS_REPORT)
1238 RANGE range;
1240 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1241 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1243 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1244 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1245 if (range.upper <= range.lower) return TRUE;
1246 if (!iterator_rangeitems(i, range)) return FALSE;
1247 TRACE(" report=%s\n", debugrange(&i->range));
1249 else
1251 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1252 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1253 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1254 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1255 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1256 INT lower = nFirstCol * nPerCol + nFirstRow;
1257 RANGE item_range;
1258 INT nCol;
1260 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1261 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1263 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1265 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1266 TRACE("building list ranges:\n");
1267 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1269 item_range.lower = nCol * nPerCol + nFirstRow;
1270 if(item_range.lower >= infoPtr->nItemCount) break;
1271 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1272 TRACE(" list=%s\n", debugrange(&item_range));
1273 ranges_add(i->ranges, item_range);
1277 return TRUE;
1280 /***
1281 * Creates an iterator over the items which intersect the visible region of hdc.
1283 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1285 POINT Origin, Position;
1286 RECT rcItem, rcClip;
1287 INT rgntype;
1289 rgntype = GetClipBox(hdc, &rcClip);
1290 if (rgntype == NULLREGION) return iterator_empty(i);
1291 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1292 if (rgntype == SIMPLEREGION) return TRUE;
1294 /* first deal with the special item */
1295 if (i->nSpecial != -1)
1297 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1298 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1301 /* if we can't deal with the region, we'll just go with the simple range */
1302 LISTVIEW_GetOrigin(infoPtr, &Origin);
1303 TRACE("building visible range:\n");
1304 if (!i->ranges && i->range.lower < i->range.upper)
1306 if (!(i->ranges = ranges_create(50))) return TRUE;
1307 if (!ranges_add(i->ranges, i->range))
1309 ranges_destroy(i->ranges);
1310 i->ranges = 0;
1311 return TRUE;
1315 /* now delete the invisible items from the list */
1316 while(iterator_next(i))
1318 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1319 rcItem.left = Position.x + Origin.x;
1320 rcItem.top = Position.y + Origin.y;
1321 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1322 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1323 if (!RectVisible(hdc, &rcItem))
1324 ranges_delitem(i->ranges, i->nItem);
1326 /* the iterator should restart on the next iterator_next */
1327 TRACE("done\n");
1329 return TRUE;
1332 /******** Misc helper functions ************************************/
1334 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1335 WPARAM wParam, LPARAM lParam, BOOL isW)
1337 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1338 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1341 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1343 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1345 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1346 (uView == LVS_ICON || uView == LVS_SMALLICON);
1349 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1351 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1352 if(state == 1 || state == 2)
1354 LVITEMW lvitem;
1355 state ^= 3;
1356 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1357 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1358 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1362 /******** Internal API functions ************************************/
1364 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1366 static COLUMN_INFO mainItem;
1368 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1369 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1370 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1373 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1375 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1376 HINSTANCE hInst;
1378 if (infoPtr->hwndHeader) return 0;
1380 /* setup creation flags */
1381 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1382 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1384 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1386 /* create header */
1387 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1388 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1389 if (!infoPtr->hwndHeader) return -1;
1391 /* set header unicode format */
1392 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1394 /* set header font */
1395 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1397 LISTVIEW_UpdateSize(infoPtr);
1399 return 0;
1402 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1404 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1407 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1409 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1412 /* Listview invalidation functions: use _only_ these functions to invalidate */
1414 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1416 return infoPtr->bRedraw;
1419 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1421 if(!is_redrawing(infoPtr)) return;
1422 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1423 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1426 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1428 RECT rcBox;
1430 if(!is_redrawing(infoPtr)) return;
1431 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1432 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1435 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1437 POINT Origin, Position;
1438 RECT rcBox;
1440 if(!is_redrawing(infoPtr)) return;
1441 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1442 LISTVIEW_GetOrigin(infoPtr, &Origin);
1443 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1444 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1445 rcBox.top = 0;
1446 rcBox.bottom = infoPtr->nItemHeight;
1447 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1448 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1451 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1453 LISTVIEW_InvalidateRect(infoPtr, NULL);
1456 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1458 RECT rcCol;
1460 if(!is_redrawing(infoPtr)) return;
1461 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1462 rcCol.top = infoPtr->rcList.top;
1463 rcCol.bottom = infoPtr->rcList.bottom;
1464 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1467 /***
1468 * DESCRIPTION:
1469 * Retrieves the number of items that can fit vertically in the client area.
1471 * PARAMETER(S):
1472 * [I] infoPtr : valid pointer to the listview structure
1474 * RETURN:
1475 * Number of items per row.
1477 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1479 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1481 return max(nListWidth/infoPtr->nItemWidth, 1);
1484 /***
1485 * DESCRIPTION:
1486 * Retrieves the number of items that can fit horizontally in the client
1487 * area.
1489 * PARAMETER(S):
1490 * [I] infoPtr : valid pointer to the listview structure
1492 * RETURN:
1493 * Number of items per column.
1495 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1497 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1499 return max(nListHeight / infoPtr->nItemHeight, 1);
1503 /*************************************************************************
1504 * LISTVIEW_ProcessLetterKeys
1506 * Processes keyboard messages generated by pressing the letter keys
1507 * on the keyboard.
1508 * What this does is perform a case insensitive search from the
1509 * current position with the following quirks:
1510 * - If two chars or more are pressed in quick succession we search
1511 * for the corresponding string (e.g. 'abc').
1512 * - If there is a delay we wipe away the current search string and
1513 * restart with just that char.
1514 * - If the user keeps pressing the same character, whether slowly or
1515 * fast, so that the search string is entirely composed of this
1516 * character ('aaaaa' for instance), then we search for first item
1517 * that starting with that character.
1518 * - If the user types the above character in quick succession, then
1519 * we must also search for the corresponding string ('aaaaa'), and
1520 * go to that string if there is a match.
1522 * PARAMETERS
1523 * [I] hwnd : handle to the window
1524 * [I] charCode : the character code, the actual character
1525 * [I] keyData : key data
1527 * RETURNS
1529 * Zero.
1531 * BUGS
1533 * - The current implementation has a list of characters it will
1534 * accept and it ignores everything else. In particular it will
1535 * ignore accentuated characters which seems to match what
1536 * Windows does. But I'm not sure it makes sense to follow
1537 * Windows there.
1538 * - We don't sound a beep when the search fails.
1540 * SEE ALSO
1542 * TREEVIEW_ProcessLetterKeys
1544 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1546 INT nItem;
1547 INT endidx,idx;
1548 LVITEMW item;
1549 WCHAR buffer[MAX_PATH];
1550 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1552 /* simple parameter checking */
1553 if (!charCode || !keyData) return 0;
1555 /* only allow the valid WM_CHARs through */
1556 if (!isalnumW(charCode) &&
1557 charCode != '.' && charCode != '`' && charCode != '!' &&
1558 charCode != '@' && charCode != '#' && 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 return 0;
1568 /* if there's one item or less, there is no where to go */
1569 if (infoPtr->nItemCount <= 1) return 0;
1571 /* update the search parameters */
1572 infoPtr->lastKeyPressTimestamp = GetTickCount();
1573 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1574 if (infoPtr->nSearchParamLength < MAX_PATH-1)
1575 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1576 if (infoPtr->charCode != charCode)
1577 infoPtr->charCode = charCode = 0;
1578 } else {
1579 infoPtr->charCode=charCode;
1580 infoPtr->szSearchParam[0]=charCode;
1581 infoPtr->nSearchParamLength=1;
1582 /* Redundant with the 1 char string */
1583 charCode=0;
1586 /* and search from the current position */
1587 nItem=-1;
1588 if (infoPtr->nFocusedItem >= 0) {
1589 endidx=infoPtr->nFocusedItem;
1590 idx=endidx;
1591 /* if looking for single character match,
1592 * then we must always move forward
1594 if (infoPtr->nSearchParamLength == 1)
1595 idx++;
1596 } else {
1597 endidx=infoPtr->nItemCount;
1598 idx=0;
1601 /* Let application handle this for virtual listview */
1602 if (infoPtr->dwStyle & LVS_OWNERDATA)
1604 NMLVFINDITEMW nmlv;
1605 LVFINDINFOW lvfi;
1607 ZeroMemory(&lvfi, sizeof(lvfi));
1608 lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1609 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1610 lvfi.psz = infoPtr->szSearchParam;
1611 nmlv.iStart = idx;
1612 nmlv.lvfi = lvfi;
1614 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1616 if (nItem != -1)
1617 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1619 return 0;
1622 do {
1623 if (idx == infoPtr->nItemCount) {
1624 if (endidx == infoPtr->nItemCount || endidx == 0)
1625 break;
1626 idx=0;
1629 /* get item */
1630 item.mask = LVIF_TEXT;
1631 item.iItem = idx;
1632 item.iSubItem = 0;
1633 item.pszText = buffer;
1634 item.cchTextMax = MAX_PATH;
1635 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1637 /* check for a match */
1638 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1639 nItem=idx;
1640 break;
1641 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1642 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1643 /* This would work but we must keep looking for a longer match */
1644 nItem=idx;
1646 idx++;
1647 } while (idx != endidx);
1649 if (nItem != -1)
1650 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1652 return 0;
1655 /*************************************************************************
1656 * LISTVIEW_UpdateHeaderSize [Internal]
1658 * Function to resize the header control
1660 * PARAMS
1661 * [I] hwnd : handle to a window
1662 * [I] nNewScrollPos : scroll pos to set
1664 * RETURNS
1665 * None.
1667 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1669 RECT winRect;
1670 POINT point[2];
1672 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1674 if (!infoPtr->hwndHeader) return;
1676 GetWindowRect(infoPtr->hwndHeader, &winRect);
1677 point[0].x = winRect.left;
1678 point[0].y = winRect.top;
1679 point[1].x = winRect.right;
1680 point[1].y = winRect.bottom;
1682 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1683 point[0].x = -nNewScrollPos;
1684 point[1].x += nNewScrollPos;
1686 SetWindowPos(infoPtr->hwndHeader,0,
1687 point[0].x,point[0].y,point[1].x,point[1].y,
1688 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1689 SWP_NOZORDER | SWP_NOACTIVATE);
1692 /***
1693 * DESCRIPTION:
1694 * Update the scrollbars. This functions should be called whenever
1695 * the content, size or view changes.
1697 * PARAMETER(S):
1698 * [I] infoPtr : valid pointer to the listview structure
1700 * RETURN:
1701 * None
1703 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1705 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1706 SCROLLINFO horzInfo, vertInfo;
1707 INT dx, dy;
1709 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1711 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1712 horzInfo.cbSize = sizeof(SCROLLINFO);
1713 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1715 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1716 if (uView == LVS_LIST)
1718 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1719 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1721 /* scroll by at least one column per page */
1722 if(horzInfo.nPage < infoPtr->nItemWidth)
1723 horzInfo.nPage = infoPtr->nItemWidth;
1725 horzInfo.nPage /= infoPtr->nItemWidth;
1727 else if (uView == LVS_REPORT)
1729 horzInfo.nMax = infoPtr->nItemWidth;
1731 else /* LVS_ICON, or LVS_SMALLICON */
1733 RECT rcView;
1735 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1738 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1739 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1740 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1741 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1742 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1744 /* Setting the horizontal scroll can change the listview size
1745 * (and potentially everything else) so we need to recompute
1746 * everything again for the vertical scroll
1749 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1750 vertInfo.cbSize = sizeof(SCROLLINFO);
1751 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1753 if (uView == LVS_REPORT)
1755 vertInfo.nMax = infoPtr->nItemCount;
1757 /* scroll by at least one page */
1758 if(vertInfo.nPage < infoPtr->nItemHeight)
1759 vertInfo.nPage = infoPtr->nItemHeight;
1761 if (infoPtr->nItemHeight > 0)
1762 vertInfo.nPage /= infoPtr->nItemHeight;
1764 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1766 RECT rcView;
1768 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1771 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1772 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1773 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1774 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1775 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1777 /* Change of the range may have changed the scroll pos. If so move the content */
1778 if (dx != 0 || dy != 0)
1780 RECT listRect;
1781 listRect = infoPtr->rcList;
1782 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1783 SW_ERASE | SW_INVALIDATE);
1786 /* Update the Header Control */
1787 if (uView == LVS_REPORT)
1789 horzInfo.fMask = SIF_POS;
1790 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1791 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1796 /***
1797 * DESCRIPTION:
1798 * Shows/hides the focus rectangle.
1800 * PARAMETER(S):
1801 * [I] infoPtr : valid pointer to the listview structure
1802 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1804 * RETURN:
1805 * None
1807 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1809 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1810 HDC hdc;
1812 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1814 if (infoPtr->nFocusedItem < 0) return;
1816 /* we need some gymnastics in ICON mode to handle large items */
1817 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1819 RECT rcBox;
1821 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1822 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1824 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1825 return;
1829 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1831 /* for some reason, owner draw should work only in report mode */
1832 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1834 DRAWITEMSTRUCT dis;
1835 LVITEMW item;
1837 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1838 HFONT hOldFont = SelectObject(hdc, hFont);
1840 item.iItem = infoPtr->nFocusedItem;
1841 item.iSubItem = 0;
1842 item.mask = LVIF_PARAM;
1843 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1845 ZeroMemory(&dis, sizeof(dis));
1846 dis.CtlType = ODT_LISTVIEW;
1847 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1848 dis.itemID = item.iItem;
1849 dis.itemAction = ODA_FOCUS;
1850 if (fShow) dis.itemState |= ODS_FOCUS;
1851 dis.hwndItem = infoPtr->hwndSelf;
1852 dis.hDC = hdc;
1853 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1854 dis.itemData = item.lParam;
1856 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1858 SelectObject(hdc, hOldFont);
1860 else
1862 DrawFocusRect(hdc, &infoPtr->rcFocus);
1864 done:
1865 ReleaseDC(infoPtr->hwndSelf, hdc);
1868 /***
1869 * Invalidates all visible selected items.
1871 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1873 ITERATOR i;
1875 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1876 while(iterator_next(&i))
1878 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1879 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1881 iterator_destroy(&i);
1885 /***
1886 * DESCRIPTION: [INTERNAL]
1887 * Computes an item's (left,top) corner, relative to rcView.
1888 * That is, the position has NOT been made relative to the Origin.
1889 * This is deliberate, to avoid computing the Origin over, and
1890 * over again, when this function is called in a loop. Instead,
1891 * one can factor the computation of the Origin before the loop,
1892 * and offset the value returned by this function, on every iteration.
1894 * PARAMETER(S):
1895 * [I] infoPtr : valid pointer to the listview structure
1896 * [I] nItem : item number
1897 * [O] lpptOrig : item top, left corner
1899 * RETURN:
1900 * None.
1902 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1904 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1906 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1908 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1910 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1911 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1913 else if (uView == LVS_LIST)
1915 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1916 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1917 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1919 else /* LVS_REPORT */
1921 lpptPosition->x = 0;
1922 lpptPosition->y = nItem * infoPtr->nItemHeight;
1926 /***
1927 * DESCRIPTION: [INTERNAL]
1928 * Compute the rectangles of an item. This is to localize all
1929 * the computations in one place. If you are not interested in some
1930 * of these values, simply pass in a NULL -- the function is smart
1931 * enough to compute only what's necessary. The function computes
1932 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1933 * one, the BOX rectangle. This rectangle is very cheap to compute,
1934 * and is guaranteed to contain all the other rectangles. Computing
1935 * the ICON rect is also cheap, but all the others are potentially
1936 * expensive. This gives an easy and effective optimization when
1937 * searching (like point inclusion, or rectangle intersection):
1938 * first test against the BOX, and if TRUE, test against the desired
1939 * rectangle.
1940 * If the function does not have all the necessary information
1941 * to computed the requested rectangles, will crash with a
1942 * failed assertion. This is done so we catch all programming
1943 * errors, given that the function is called only from our code.
1945 * We have the following 'special' meanings for a few fields:
1946 * * If LVIS_FOCUSED is set, we assume the item has the focus
1947 * This is important in ICON mode, where it might get a larger
1948 * then usual rectangle
1950 * Please note that subitem support works only in REPORT mode.
1952 * PARAMETER(S):
1953 * [I] infoPtr : valid pointer to the listview structure
1954 * [I] lpLVItem : item to compute the measures for
1955 * [O] lprcBox : ptr to Box rectangle
1956 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1957 * [0] lprcSelectBox : ptr to select box rectangle
1958 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1959 * [O] lprcIcon : ptr to Icon rectangle
1960 * Same as LVM_GETITEMRECT with LVIR_ICON
1961 * [O] lprcStateIcon: ptr to State Icon rectangle
1962 * [O] lprcLabel : ptr to Label rectangle
1963 * Same as LVM_GETITEMRECT with LVIR_LABEL
1965 * RETURN:
1966 * None.
1968 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1969 LPRECT lprcBox, LPRECT lprcSelectBox,
1970 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1972 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1973 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1974 RECT Box, SelectBox, Icon, Label;
1975 COLUMN_INFO *lpColumnInfo = NULL;
1976 SIZE labelSize = { 0, 0 };
1978 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1980 /* Be smart and try to figure out the minimum we have to do */
1981 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1982 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1984 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1985 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1987 if (lprcSelectBox) doSelectBox = TRUE;
1988 if (lprcLabel) doLabel = TRUE;
1989 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1990 if (doSelectBox)
1992 doIcon = TRUE;
1993 doLabel = TRUE;
1996 /************************************************************/
1997 /* compute the box rectangle (it should be cheap to do) */
1998 /************************************************************/
1999 if (lpLVItem->iSubItem || uView == LVS_REPORT)
2000 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2002 if (lpLVItem->iSubItem)
2004 Box = lpColumnInfo->rcHeader;
2006 else
2008 Box.left = 0;
2009 Box.right = infoPtr->nItemWidth;
2011 Box.top = 0;
2012 Box.bottom = infoPtr->nItemHeight;
2014 /******************************************************************/
2015 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2016 /******************************************************************/
2017 if (doIcon)
2019 LONG state_width = 0;
2021 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2022 state_width = infoPtr->iconStateSize.cx;
2024 if (uView == LVS_ICON)
2026 Icon.left = Box.left + state_width;
2027 if (infoPtr->himlNormal)
2028 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2029 Icon.top = Box.top + ICON_TOP_PADDING;
2030 Icon.right = Icon.left;
2031 Icon.bottom = Icon.top;
2032 if (infoPtr->himlNormal)
2034 Icon.right += infoPtr->iconSize.cx;
2035 Icon.bottom += infoPtr->iconSize.cy;
2038 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2040 Icon.left = Box.left + state_width;
2042 if (uView == LVS_REPORT)
2043 Icon.left += REPORT_MARGINX;
2045 Icon.top = Box.top;
2046 Icon.right = Icon.left;
2047 if (infoPtr->himlSmall &&
2048 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2049 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2050 Icon.right += infoPtr->iconSize.cx;
2051 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2053 if(lprcIcon) *lprcIcon = Icon;
2054 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2056 /* TODO: is this correct? */
2057 if (lprcStateIcon)
2059 lprcStateIcon->left = Icon.left - state_width;
2060 lprcStateIcon->right = Icon.left;
2061 lprcStateIcon->top = Icon.top;
2062 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2063 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2066 else Icon.right = 0;
2068 /************************************************************/
2069 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2070 /************************************************************/
2071 if (doLabel)
2073 /* calculate how far to the right can the label stretch */
2074 Label.right = Box.right;
2075 if (uView == LVS_REPORT)
2077 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2080 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2082 labelSize.cx = infoPtr->nItemWidth;
2083 labelSize.cy = infoPtr->nItemHeight;
2084 goto calc_label;
2087 /* we need the text in non owner draw mode */
2088 assert(lpLVItem->mask & LVIF_TEXT);
2089 if (is_textT(lpLVItem->pszText, TRUE))
2091 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2092 HDC hdc = GetDC(infoPtr->hwndSelf);
2093 HFONT hOldFont = SelectObject(hdc, hFont);
2094 UINT uFormat;
2095 RECT rcText;
2097 /* compute rough rectangle where the label will go */
2098 SetRectEmpty(&rcText);
2099 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2100 rcText.bottom = infoPtr->nItemHeight;
2101 if (uView == LVS_ICON)
2102 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2104 /* now figure out the flags */
2105 if (uView == LVS_ICON)
2106 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2107 else
2108 uFormat = LV_SL_DT_FLAGS;
2110 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2112 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2113 labelSize.cy = rcText.bottom - rcText.top;
2115 SelectObject(hdc, hOldFont);
2116 ReleaseDC(infoPtr->hwndSelf, hdc);
2119 calc_label:
2120 if (uView == LVS_ICON)
2122 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2123 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2124 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2125 Label.right = Label.left + labelSize.cx;
2126 Label.bottom = Label.top + infoPtr->nItemHeight;
2127 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2129 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2130 labelSize.cy /= infoPtr->ntmHeight;
2131 labelSize.cy = max(labelSize.cy, 1);
2132 labelSize.cy *= infoPtr->ntmHeight;
2134 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2136 else if (uView == LVS_REPORT)
2138 Label.left = Icon.right;
2139 Label.top = Box.top;
2140 Label.right = lpColumnInfo->rcHeader.right;
2141 Label.bottom = Label.top + infoPtr->nItemHeight;
2143 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2145 Label.left = Icon.right;
2146 Label.top = Box.top;
2147 Label.right = min(Label.left + labelSize.cx, Label.right);
2148 Label.bottom = Label.top + infoPtr->nItemHeight;
2151 if (lprcLabel) *lprcLabel = Label;
2152 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2155 /************************************************************/
2156 /* compute STATEICON bounding box */
2157 /************************************************************/
2158 if (doSelectBox)
2160 if (uView == LVS_REPORT)
2162 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2163 SelectBox.top = Box.top;
2164 SelectBox.bottom = Box.bottom;
2165 if (lpLVItem->iSubItem == 0)
2167 /* we need the indent in report mode */
2168 assert(lpLVItem->mask & LVIF_INDENT);
2169 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2171 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2173 else
2175 UnionRect(&SelectBox, &Icon, &Label);
2177 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2178 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2181 /* Fix the Box if necessary */
2182 if (lprcBox)
2184 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2185 else *lprcBox = Box;
2187 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2190 /***
2191 * DESCRIPTION: [INTERNAL]
2193 * PARAMETER(S):
2194 * [I] infoPtr : valid pointer to the listview structure
2195 * [I] nItem : item number
2196 * [O] lprcBox : ptr to Box rectangle
2198 * RETURN:
2199 * None.
2201 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2203 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2204 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2205 POINT Position, Origin;
2206 LVITEMW lvItem;
2208 LISTVIEW_GetOrigin(infoPtr, &Origin);
2209 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2211 /* Be smart and try to figure out the minimum we have to do */
2212 lvItem.mask = 0;
2213 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2214 lvItem.mask |= LVIF_TEXT;
2215 lvItem.iItem = nItem;
2216 lvItem.iSubItem = 0;
2217 lvItem.pszText = szDispText;
2218 lvItem.cchTextMax = DISP_TEXT_SIZE;
2219 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2220 if (uView == LVS_ICON)
2222 lvItem.mask |= LVIF_STATE;
2223 lvItem.stateMask = LVIS_FOCUSED;
2224 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2226 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2228 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2232 /***
2233 * DESCRIPTION:
2234 * Returns the current icon position, and advances it along the top.
2235 * The returned position is not offset by Origin.
2237 * PARAMETER(S):
2238 * [I] infoPtr : valid pointer to the listview structure
2239 * [O] lpPos : will get the current icon position
2241 * RETURN:
2242 * None
2244 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2246 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2248 *lpPos = infoPtr->currIconPos;
2250 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2251 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2253 infoPtr->currIconPos.x = 0;
2254 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2258 /***
2259 * DESCRIPTION:
2260 * Returns the current icon position, and advances it down the left edge.
2261 * The returned position is not offset by Origin.
2263 * PARAMETER(S):
2264 * [I] infoPtr : valid pointer to the listview structure
2265 * [O] lpPos : will get the current icon position
2267 * RETURN:
2268 * None
2270 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2272 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2274 *lpPos = infoPtr->currIconPos;
2276 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2277 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2279 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2280 infoPtr->currIconPos.y = 0;
2284 /***
2285 * DESCRIPTION:
2286 * Moves an icon to the specified position.
2287 * It takes care of invalidating the item, etc.
2289 * PARAMETER(S):
2290 * [I] infoPtr : valid pointer to the listview structure
2291 * [I] nItem : the item to move
2292 * [I] lpPos : the new icon position
2293 * [I] isNew : flags the item as being new
2295 * RETURN:
2296 * Success: TRUE
2297 * Failure: FALSE
2299 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2301 POINT old;
2303 if (!isNew)
2305 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2306 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2308 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2309 LISTVIEW_InvalidateItem(infoPtr, nItem);
2312 /* Allocating a POINTER for every item is too resource intensive,
2313 * so we'll keep the (x,y) in different arrays */
2314 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2315 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2317 LISTVIEW_InvalidateItem(infoPtr, nItem);
2319 return TRUE;
2322 /***
2323 * DESCRIPTION:
2324 * Arranges listview items in icon display mode.
2326 * PARAMETER(S):
2327 * [I] infoPtr : valid pointer to the listview structure
2328 * [I] nAlignCode : alignment code
2330 * RETURN:
2331 * SUCCESS : TRUE
2332 * FAILURE : FALSE
2334 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2336 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2337 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2338 POINT pos;
2339 INT i;
2341 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2343 TRACE("nAlignCode=%d\n", nAlignCode);
2345 if (nAlignCode == LVA_DEFAULT)
2347 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2348 else nAlignCode = LVA_ALIGNTOP;
2351 switch (nAlignCode)
2353 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2354 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2355 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2356 default: return FALSE;
2359 infoPtr->bAutoarrange = TRUE;
2360 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2361 for (i = 0; i < infoPtr->nItemCount; i++)
2363 next_pos(infoPtr, &pos);
2364 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2367 return TRUE;
2370 /***
2371 * DESCRIPTION:
2372 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2374 * PARAMETER(S):
2375 * [I] infoPtr : valid pointer to the listview structure
2376 * [O] lprcView : bounding rectangle
2378 * RETURN:
2379 * SUCCESS : TRUE
2380 * FAILURE : FALSE
2382 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2384 INT i, x, y;
2386 SetRectEmpty(lprcView);
2388 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2390 case LVS_ICON:
2391 case LVS_SMALLICON:
2392 for (i = 0; i < infoPtr->nItemCount; i++)
2394 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2395 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2396 lprcView->right = max(lprcView->right, x);
2397 lprcView->bottom = max(lprcView->bottom, y);
2399 if (infoPtr->nItemCount > 0)
2401 lprcView->right += infoPtr->nItemWidth;
2402 lprcView->bottom += infoPtr->nItemHeight;
2404 break;
2406 case LVS_LIST:
2407 y = LISTVIEW_GetCountPerColumn(infoPtr);
2408 x = infoPtr->nItemCount / y;
2409 if (infoPtr->nItemCount % y) x++;
2410 lprcView->right = x * infoPtr->nItemWidth;
2411 lprcView->bottom = y * infoPtr->nItemHeight;
2412 break;
2414 case LVS_REPORT:
2415 lprcView->right = infoPtr->nItemWidth;
2416 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2417 break;
2421 /***
2422 * DESCRIPTION:
2423 * Retrieves the bounding rectangle of all the items.
2425 * PARAMETER(S):
2426 * [I] infoPtr : valid pointer to the listview structure
2427 * [O] lprcView : bounding rectangle
2429 * RETURN:
2430 * SUCCESS : TRUE
2431 * FAILURE : FALSE
2433 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2435 POINT ptOrigin;
2437 TRACE("(lprcView=%p)\n", lprcView);
2439 if (!lprcView) return FALSE;
2441 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2442 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2443 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2445 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2447 return TRUE;
2450 /***
2451 * DESCRIPTION:
2452 * Retrieves the subitem pointer associated with the subitem index.
2454 * PARAMETER(S):
2455 * [I] hdpaSubItems : DPA handle for a specific item
2456 * [I] nSubItem : index of subitem
2458 * RETURN:
2459 * SUCCESS : subitem pointer
2460 * FAILURE : NULL
2462 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2464 SUBITEM_INFO *lpSubItem;
2465 INT i;
2467 /* we should binary search here if need be */
2468 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2470 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2471 if (lpSubItem->iSubItem == nSubItem)
2472 return lpSubItem;
2475 return NULL;
2479 /***
2480 * DESCRIPTION:
2481 * Calculates the desired item width.
2483 * PARAMETER(S):
2484 * [I] infoPtr : valid pointer to the listview structure
2486 * RETURN:
2487 * The desired item width.
2489 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2491 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2492 INT nItemWidth = 0;
2494 TRACE("uView=%d\n", uView);
2496 if (uView == LVS_ICON)
2497 nItemWidth = infoPtr->iconSpacing.cx;
2498 else if (uView == LVS_REPORT)
2500 RECT rcHeader;
2502 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2504 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2505 nItemWidth = rcHeader.right;
2508 else /* LVS_SMALLICON, or LVS_LIST */
2510 INT i;
2512 for (i = 0; i < infoPtr->nItemCount; i++)
2513 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2515 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2516 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2518 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2521 return max(nItemWidth, 1);
2524 /***
2525 * DESCRIPTION:
2526 * Calculates the desired item height.
2528 * PARAMETER(S):
2529 * [I] infoPtr : valid pointer to the listview structure
2531 * RETURN:
2532 * The desired item height.
2534 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2536 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2537 INT nItemHeight;
2539 TRACE("uView=%d\n", uView);
2541 if (uView == LVS_ICON)
2542 nItemHeight = infoPtr->iconSpacing.cy;
2543 else
2545 nItemHeight = infoPtr->ntmHeight;
2546 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2547 nItemHeight++;
2548 if (infoPtr->himlState)
2549 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2550 if (infoPtr->himlSmall)
2551 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2552 if (infoPtr->himlState || infoPtr->himlSmall)
2553 nItemHeight += HEIGHT_PADDING;
2554 if (infoPtr->nMeasureItemHeight > 0)
2555 nItemHeight = infoPtr->nMeasureItemHeight;
2558 return max(nItemHeight, 1);
2561 /***
2562 * DESCRIPTION:
2563 * Updates the width, and height of an item.
2565 * PARAMETER(S):
2566 * [I] infoPtr : valid pointer to the listview structure
2568 * RETURN:
2569 * None.
2571 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2573 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2574 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2578 /***
2579 * DESCRIPTION:
2580 * Retrieves and saves important text metrics info for the current
2581 * Listview font.
2583 * PARAMETER(S):
2584 * [I] infoPtr : valid pointer to the listview structure
2587 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2589 HDC hdc = GetDC(infoPtr->hwndSelf);
2590 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2591 HFONT hOldFont = SelectObject(hdc, hFont);
2592 TEXTMETRICW tm;
2593 SIZE sz;
2595 if (GetTextMetricsW(hdc, &tm))
2597 infoPtr->ntmHeight = tm.tmHeight;
2598 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2601 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2602 infoPtr->nEllipsisWidth = sz.cx;
2604 SelectObject(hdc, hOldFont);
2605 ReleaseDC(infoPtr->hwndSelf, hdc);
2607 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2610 /***
2611 * DESCRIPTION:
2612 * A compare function for ranges
2614 * PARAMETER(S)
2615 * [I] range1 : pointer to range 1;
2616 * [I] range2 : pointer to range 2;
2617 * [I] flags : flags
2619 * RETURNS:
2620 * > 0 : if range 1 > range 2
2621 * < 0 : if range 2 > range 1
2622 * = 0 : if range intersects range 2
2624 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2626 INT cmp;
2628 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2629 cmp = -1;
2630 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2631 cmp = 1;
2632 else
2633 cmp = 0;
2635 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2637 return cmp;
2640 #if DEBUG_RANGES
2641 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2642 #else
2643 #define ranges_check(ranges, desc) do { } while(0)
2644 #endif
2646 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2648 INT i;
2649 RANGE *prev, *curr;
2651 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2652 assert (ranges);
2653 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2654 ranges_dump(ranges);
2655 prev = DPA_GetPtr(ranges->hdpa, 0);
2656 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2657 assert (prev->lower >= 0 && prev->lower < prev->upper);
2658 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2660 curr = DPA_GetPtr(ranges->hdpa, i);
2661 assert (prev->upper <= curr->lower);
2662 assert (curr->lower < curr->upper);
2663 prev = curr;
2665 TRACE("--- Done checking---\n");
2668 static RANGES ranges_create(int count)
2670 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2671 if (!ranges) return NULL;
2672 ranges->hdpa = DPA_Create(count);
2673 if (ranges->hdpa) return ranges;
2674 Free(ranges);
2675 return NULL;
2678 static void ranges_clear(RANGES ranges)
2680 INT i;
2682 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2683 Free(DPA_GetPtr(ranges->hdpa, i));
2684 DPA_DeleteAllPtrs(ranges->hdpa);
2688 static void ranges_destroy(RANGES ranges)
2690 if (!ranges) return;
2691 ranges_clear(ranges);
2692 DPA_Destroy(ranges->hdpa);
2693 Free(ranges);
2696 static RANGES ranges_clone(RANGES ranges)
2698 RANGES clone;
2699 INT i;
2701 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2703 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2705 RANGE *newrng = Alloc(sizeof(RANGE));
2706 if (!newrng) goto fail;
2707 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2708 DPA_SetPtr(clone->hdpa, i, newrng);
2710 return clone;
2712 fail:
2713 TRACE ("clone failed\n");
2714 ranges_destroy(clone);
2715 return NULL;
2718 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2720 INT i;
2722 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2723 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2725 return ranges;
2728 static void ranges_dump(RANGES ranges)
2730 INT i;
2732 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2733 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2736 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2738 RANGE srchrng = { nItem, nItem + 1 };
2740 TRACE("(nItem=%d)\n", nItem);
2741 ranges_check(ranges, "before contain");
2742 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2745 static INT ranges_itemcount(RANGES ranges)
2747 INT i, count = 0;
2749 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2751 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2752 count += sel->upper - sel->lower;
2755 return count;
2758 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2760 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2761 INT index;
2763 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2764 if (index == -1) return TRUE;
2766 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2768 chkrng = DPA_GetPtr(ranges->hdpa, index);
2769 if (chkrng->lower >= nItem)
2770 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2771 if (chkrng->upper > nItem)
2772 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2774 return TRUE;
2777 static BOOL ranges_add(RANGES ranges, RANGE range)
2779 RANGE srchrgn;
2780 INT index;
2782 TRACE("(%s)\n", debugrange(&range));
2783 ranges_check(ranges, "before add");
2785 /* try find overlapping regions first */
2786 srchrgn.lower = range.lower - 1;
2787 srchrgn.upper = range.upper + 1;
2788 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2790 if (index == -1)
2792 RANGE *newrgn;
2794 TRACE("Adding new range\n");
2796 /* create the brand new range to insert */
2797 newrgn = Alloc(sizeof(RANGE));
2798 if(!newrgn) goto fail;
2799 *newrgn = range;
2801 /* figure out where to insert it */
2802 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2803 TRACE("index=%d\n", index);
2804 if (index == -1) index = 0;
2806 /* and get it over with */
2807 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2809 Free(newrgn);
2810 goto fail;
2813 else
2815 RANGE *chkrgn, *mrgrgn;
2816 INT fromindex, mergeindex;
2818 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2819 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2821 chkrgn->lower = min(range.lower, chkrgn->lower);
2822 chkrgn->upper = max(range.upper, chkrgn->upper);
2824 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2826 /* merge now common ranges */
2827 fromindex = 0;
2828 srchrgn.lower = chkrgn->lower - 1;
2829 srchrgn.upper = chkrgn->upper + 1;
2833 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2834 if (mergeindex == -1) break;
2835 if (mergeindex == index)
2837 fromindex = index + 1;
2838 continue;
2841 TRACE("Merge with index %i\n", mergeindex);
2843 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2844 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2845 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2846 Free(mrgrgn);
2847 DPA_DeletePtr(ranges->hdpa, mergeindex);
2848 if (mergeindex < index) index --;
2849 } while(1);
2852 ranges_check(ranges, "after add");
2853 return TRUE;
2855 fail:
2856 ranges_check(ranges, "failed add");
2857 return FALSE;
2860 static BOOL ranges_del(RANGES ranges, RANGE range)
2862 RANGE *chkrgn;
2863 INT index;
2865 TRACE("(%s)\n", debugrange(&range));
2866 ranges_check(ranges, "before del");
2868 /* we don't use DPAS_SORTED here, since we need *
2869 * to find the first overlapping range */
2870 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2871 while(index != -1)
2873 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2875 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2877 /* case 1: Same range */
2878 if ( (chkrgn->upper == range.upper) &&
2879 (chkrgn->lower == range.lower) )
2881 DPA_DeletePtr(ranges->hdpa, index);
2882 break;
2884 /* case 2: engulf */
2885 else if ( (chkrgn->upper <= range.upper) &&
2886 (chkrgn->lower >= range.lower) )
2888 DPA_DeletePtr(ranges->hdpa, index);
2890 /* case 3: overlap upper */
2891 else if ( (chkrgn->upper <= range.upper) &&
2892 (chkrgn->lower < range.lower) )
2894 chkrgn->upper = range.lower;
2896 /* case 4: overlap lower */
2897 else if ( (chkrgn->upper > range.upper) &&
2898 (chkrgn->lower >= range.lower) )
2900 chkrgn->lower = range.upper;
2901 break;
2903 /* case 5: fully internal */
2904 else
2906 RANGE tmprgn = *chkrgn, *newrgn;
2908 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2909 newrgn->lower = chkrgn->lower;
2910 newrgn->upper = range.lower;
2911 chkrgn->lower = range.upper;
2912 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2914 Free(newrgn);
2915 goto fail;
2917 chkrgn = &tmprgn;
2918 break;
2921 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2924 ranges_check(ranges, "after del");
2925 return TRUE;
2927 fail:
2928 ranges_check(ranges, "failed del");
2929 return FALSE;
2932 /***
2933 * DESCRIPTION:
2934 * Removes all selection ranges
2936 * Parameters(s):
2937 * [I] infoPtr : valid pointer to the listview structure
2938 * [I] toSkip : item range to skip removing the selection
2940 * RETURNS:
2941 * SUCCESS : TRUE
2942 * FAILURE : FALSE
2944 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2946 LVITEMW lvItem;
2947 ITERATOR i;
2948 RANGES clone;
2950 TRACE("()\n");
2952 lvItem.state = 0;
2953 lvItem.stateMask = LVIS_SELECTED;
2955 /* need to clone the DPA because callbacks can change it */
2956 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2957 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2958 while(iterator_next(&i))
2959 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2960 /* note that the iterator destructor will free the cloned range */
2961 iterator_destroy(&i);
2963 return TRUE;
2966 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2968 RANGES toSkip;
2970 if (!(toSkip = ranges_create(1))) return FALSE;
2971 if (nItem != -1) ranges_additem(toSkip, nItem);
2972 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2973 ranges_destroy(toSkip);
2974 return TRUE;
2977 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2979 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2982 /***
2983 * DESCRIPTION:
2984 * Retrieves the number of items that are marked as selected.
2986 * PARAMETER(S):
2987 * [I] infoPtr : valid pointer to the listview structure
2989 * RETURN:
2990 * Number of items selected.
2992 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2994 INT nSelectedCount = 0;
2996 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2998 INT i;
2999 for (i = 0; i < infoPtr->nItemCount; i++)
3001 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3002 nSelectedCount++;
3005 else
3006 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3008 TRACE("nSelectedCount=%d\n", nSelectedCount);
3009 return nSelectedCount;
3012 /***
3013 * DESCRIPTION:
3014 * Manages the item focus.
3016 * PARAMETER(S):
3017 * [I] infoPtr : valid pointer to the listview structure
3018 * [I] nItem : item index
3020 * RETURN:
3021 * TRUE : focused item changed
3022 * FALSE : focused item has NOT changed
3024 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3026 INT oldFocus = infoPtr->nFocusedItem;
3027 LVITEMW lvItem;
3029 if (nItem == infoPtr->nFocusedItem) return FALSE;
3031 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3032 lvItem.stateMask = LVIS_FOCUSED;
3033 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3035 return oldFocus != infoPtr->nFocusedItem;
3038 /* Helper function for LISTVIEW_ShiftIndices *only* */
3039 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3041 if (nShiftItem < nItem) return nShiftItem;
3043 if (nShiftItem > nItem) return nShiftItem + direction;
3045 if (direction > 0) return nShiftItem + direction;
3047 return min(nShiftItem, infoPtr->nItemCount - 1);
3051 * DESCRIPTION:
3052 * Updates the various indices after an item has been inserted or deleted.
3054 * PARAMETER(S):
3055 * [I] infoPtr : valid pointer to the listview structure
3056 * [I] nItem : item index
3057 * [I] direction : Direction of shift, +1 or -1.
3059 * RETURN:
3060 * None
3062 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3064 INT nNewFocus;
3065 BOOL bOldChange;
3067 /* temporarily disable change notification while shifting items */
3068 bOldChange = infoPtr->bDoChangeNotify;
3069 infoPtr->bDoChangeNotify = FALSE;
3071 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3073 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3075 assert(abs(direction) == 1);
3077 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3079 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3080 if (nNewFocus != infoPtr->nFocusedItem)
3081 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3083 /* But we are not supposed to modify nHotItem! */
3085 infoPtr->bDoChangeNotify = bOldChange;
3090 * DESCRIPTION:
3091 * Adds a block of selections.
3093 * PARAMETER(S):
3094 * [I] infoPtr : valid pointer to the listview structure
3095 * [I] nItem : item index
3097 * RETURN:
3098 * Whether the window is still valid.
3100 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3102 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3103 INT nLast = max(infoPtr->nSelectionMark, nItem);
3104 HWND hwndSelf = infoPtr->hwndSelf;
3105 NMLVODSTATECHANGE nmlv;
3106 LVITEMW item;
3107 BOOL bOldChange;
3108 INT i;
3110 /* Temporarily disable change notification
3111 * If the control is LVS_OWNERDATA, we need to send
3112 * only one LVN_ODSTATECHANGED notification.
3113 * See MSDN documentation for LVN_ITEMCHANGED.
3115 bOldChange = infoPtr->bDoChangeNotify;
3116 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3118 if (nFirst == -1) nFirst = nItem;
3120 item.state = LVIS_SELECTED;
3121 item.stateMask = LVIS_SELECTED;
3123 for (i = nFirst; i <= nLast; i++)
3124 LISTVIEW_SetItemState(infoPtr,i,&item);
3126 ZeroMemory(&nmlv, sizeof(nmlv));
3127 nmlv.iFrom = nFirst;
3128 nmlv.iTo = nLast;
3129 nmlv.uNewState = 0;
3130 nmlv.uOldState = item.state;
3132 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3133 if (!IsWindow(hwndSelf))
3134 return FALSE;
3135 infoPtr->bDoChangeNotify = bOldChange;
3136 return TRUE;
3140 /***
3141 * DESCRIPTION:
3142 * Sets a single group selection.
3144 * PARAMETER(S):
3145 * [I] infoPtr : valid pointer to the listview structure
3146 * [I] nItem : item index
3148 * RETURN:
3149 * None
3151 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3153 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3154 RANGES selection;
3155 LVITEMW item;
3156 ITERATOR i;
3157 BOOL bOldChange;
3159 if (!(selection = ranges_create(100))) return;
3161 item.state = LVIS_SELECTED;
3162 item.stateMask = LVIS_SELECTED;
3164 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3166 if (infoPtr->nSelectionMark == -1)
3168 infoPtr->nSelectionMark = nItem;
3169 ranges_additem(selection, nItem);
3171 else
3173 RANGE sel;
3175 sel.lower = min(infoPtr->nSelectionMark, nItem);
3176 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3177 ranges_add(selection, sel);
3180 else
3182 RECT rcItem, rcSel, rcSelMark;
3183 POINT ptItem;
3185 rcItem.left = LVIR_BOUNDS;
3186 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3187 rcSelMark.left = LVIR_BOUNDS;
3188 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3189 UnionRect(&rcSel, &rcItem, &rcSelMark);
3190 iterator_frameditems(&i, infoPtr, &rcSel);
3191 while(iterator_next(&i))
3193 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3194 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3196 iterator_destroy(&i);
3199 /* disable per item notifications on LVS_OWNERDATA style
3200 FIXME: single LVN_ODSTATECHANGED should be used */
3201 bOldChange = infoPtr->bDoChangeNotify;
3202 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3204 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3207 iterator_rangesitems(&i, selection);
3208 while(iterator_next(&i))
3209 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3210 /* this will also destroy the selection */
3211 iterator_destroy(&i);
3213 infoPtr->bDoChangeNotify = bOldChange;
3215 LISTVIEW_SetItemFocus(infoPtr, nItem);
3218 /***
3219 * DESCRIPTION:
3220 * Sets a single selection.
3222 * PARAMETER(S):
3223 * [I] infoPtr : valid pointer to the listview structure
3224 * [I] nItem : item index
3226 * RETURN:
3227 * None
3229 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3231 LVITEMW lvItem;
3233 TRACE("nItem=%d\n", nItem);
3235 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3237 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3238 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3239 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3241 infoPtr->nSelectionMark = nItem;
3244 /***
3245 * DESCRIPTION:
3246 * Set selection(s) with keyboard.
3248 * PARAMETER(S):
3249 * [I] infoPtr : valid pointer to the listview structure
3250 * [I] nItem : item index
3251 * [I] space : VK_SPACE code sent
3253 * RETURN:
3254 * SUCCESS : TRUE (needs to be repainted)
3255 * FAILURE : FALSE (nothing has changed)
3257 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3259 /* FIXME: pass in the state */
3260 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3261 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3262 BOOL bResult = FALSE;
3264 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3265 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3267 if (infoPtr->dwStyle & LVS_SINGLESEL)
3269 bResult = TRUE;
3270 LISTVIEW_SetSelection(infoPtr, nItem);
3272 else
3274 if (wShift)
3276 bResult = TRUE;
3277 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3279 else if (wCtrl)
3281 LVITEMW lvItem;
3282 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3283 lvItem.stateMask = LVIS_SELECTED;
3284 if (space)
3286 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3287 if (lvItem.state & LVIS_SELECTED)
3288 infoPtr->nSelectionMark = nItem;
3290 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3292 else
3294 bResult = TRUE;
3295 LISTVIEW_SetSelection(infoPtr, nItem);
3298 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3301 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3302 return bResult;
3305 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3307 LVHITTESTINFO lvHitTestInfo;
3309 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3310 lvHitTestInfo.pt.x = pt.x;
3311 lvHitTestInfo.pt.y = pt.y;
3313 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3315 lpLVItem->mask = LVIF_PARAM;
3316 lpLVItem->iItem = lvHitTestInfo.iItem;
3317 lpLVItem->iSubItem = 0;
3319 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3322 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3324 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3325 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3326 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3329 /***
3330 * DESCRIPTION:
3331 * Called when the mouse is being actively tracked and has hovered for a specified
3332 * amount of time
3334 * PARAMETER(S):
3335 * [I] infoPtr : valid pointer to the listview structure
3336 * [I] fwKeys : key indicator
3337 * [I] x,y : mouse position
3339 * RETURN:
3340 * 0 if the message was processed, non-zero if there was an error
3342 * INFO:
3343 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3344 * over the item for a certain period of time.
3347 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3349 if (LISTVIEW_isHotTracking(infoPtr))
3351 LVITEMW item;
3352 POINT pt;
3354 pt.x = x;
3355 pt.y = y;
3357 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3358 LISTVIEW_SetSelection(infoPtr, item.iItem);
3361 return 0;
3364 /***
3365 * DESCRIPTION:
3366 * Called whenever WM_MOUSEMOVE is received.
3368 * PARAMETER(S):
3369 * [I] infoPtr : valid pointer to the listview structure
3370 * [I] fwKeys : key indicator
3371 * [I] x,y : mouse position
3373 * RETURN:
3374 * 0 if the message is processed, non-zero if there was an error
3376 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3378 TRACKMOUSEEVENT trackinfo;
3380 if (!(fwKeys & MK_LBUTTON))
3381 infoPtr->bLButtonDown = FALSE;
3383 if (infoPtr->bLButtonDown)
3385 POINT tmp;
3386 RECT rect;
3387 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3388 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3390 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3391 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3392 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3393 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3395 tmp.x = x;
3396 tmp.y = y;
3398 if (!PtInRect(&rect, tmp))
3400 LVHITTESTINFO lvHitTestInfo;
3401 NMLISTVIEW nmlv;
3403 lvHitTestInfo.pt = infoPtr->ptClickPos;
3404 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3406 ZeroMemory(&nmlv, sizeof(nmlv));
3407 nmlv.iItem = lvHitTestInfo.iItem;
3408 nmlv.ptAction = infoPtr->ptClickPos;
3410 if (!infoPtr->bDragging)
3412 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3413 infoPtr->bDragging = TRUE;
3416 return 0;
3419 else
3420 infoPtr->bLButtonDown = FALSE;
3422 /* see if we are supposed to be tracking mouse hovering */
3423 if (LISTVIEW_isHotTracking(infoPtr)) {
3424 /* fill in the trackinfo struct */
3425 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3426 trackinfo.dwFlags = TME_QUERY;
3427 trackinfo.hwndTrack = infoPtr->hwndSelf;
3428 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3430 /* see if we are already tracking this hwnd */
3431 _TrackMouseEvent(&trackinfo);
3433 if(!(trackinfo.dwFlags & TME_HOVER)) {
3434 trackinfo.dwFlags = TME_HOVER;
3436 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3437 _TrackMouseEvent(&trackinfo);
3441 return 0;
3445 /***
3446 * Tests whether the item is assignable to a list with style lStyle
3448 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3450 if ( (lpLVItem->mask & LVIF_TEXT) &&
3451 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3452 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3454 return TRUE;
3458 /***
3459 * DESCRIPTION:
3460 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3462 * PARAMETER(S):
3463 * [I] infoPtr : valid pointer to the listview structure
3464 * [I] lpLVItem : valid pointer to new item attributes
3465 * [I] isNew : the item being set is being inserted
3466 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3467 * [O] bChanged : will be set to TRUE if the item really changed
3469 * RETURN:
3470 * SUCCESS : TRUE
3471 * FAILURE : FALSE
3473 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3475 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3476 ITEM_INFO *lpItem;
3477 NMLISTVIEW nmlv;
3478 UINT uChanged = 0;
3479 LVITEMW item;
3480 /* stateMask is ignored for LVM_INSERTITEM */
3481 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
3483 TRACE("()\n");
3485 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3487 if (lpLVItem->mask == 0) return TRUE;
3489 if (infoPtr->dwStyle & LVS_OWNERDATA)
3491 /* a virtual listview only stores selection and focus */
3492 if (lpLVItem->mask & ~LVIF_STATE)
3493 return FALSE;
3494 lpItem = NULL;
3496 else
3498 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3499 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3500 assert (lpItem);
3503 /* we need to get the lParam and state of the item */
3504 item.iItem = lpLVItem->iItem;
3505 item.iSubItem = lpLVItem->iSubItem;
3506 item.mask = LVIF_STATE | LVIF_PARAM;
3507 item.stateMask = ~0;
3508 item.state = 0;
3509 item.lParam = 0;
3510 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3512 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3513 /* determine what fields will change */
3514 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
3515 uChanged |= LVIF_STATE;
3517 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3518 uChanged |= LVIF_IMAGE;
3520 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3521 uChanged |= LVIF_PARAM;
3523 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3524 uChanged |= LVIF_INDENT;
3526 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3527 uChanged |= LVIF_TEXT;
3529 TRACE("uChanged=0x%x\n", uChanged);
3530 if (!uChanged) return TRUE;
3531 *bChanged = TRUE;
3533 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3534 nmlv.iItem = lpLVItem->iItem;
3535 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
3536 nmlv.uOldState = item.state;
3537 nmlv.uChanged = uChanged;
3538 nmlv.lParam = item.lParam;
3540 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3541 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3542 /* are enabled */
3543 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3545 HWND hwndSelf = infoPtr->hwndSelf;
3547 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3548 return FALSE;
3549 if (!IsWindow(hwndSelf))
3550 return FALSE;
3553 /* copy information */
3554 if (lpLVItem->mask & LVIF_TEXT)
3555 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3557 if (lpLVItem->mask & LVIF_IMAGE)
3558 lpItem->hdr.iImage = lpLVItem->iImage;
3560 if (lpLVItem->mask & LVIF_PARAM)
3561 lpItem->lParam = lpLVItem->lParam;
3563 if (lpLVItem->mask & LVIF_INDENT)
3564 lpItem->iIndent = lpLVItem->iIndent;
3566 if (uChanged & LVIF_STATE)
3568 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
3570 lpItem->state &= ~stateMask;
3571 lpItem->state |= (lpLVItem->state & stateMask);
3573 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3575 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3576 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3578 else if (stateMask & LVIS_SELECTED)
3580 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3582 /* if we are asked to change focus, and we manage it, do it */
3583 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3585 if (lpLVItem->state & LVIS_FOCUSED)
3587 if (infoPtr->nFocusedItem != -1)
3589 /* remove current focus */
3590 item.mask = LVIF_STATE;
3591 item.state = 0;
3592 item.stateMask = LVIS_FOCUSED;
3594 /* recurse with redrawing an item */
3595 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
3598 infoPtr->nFocusedItem = lpLVItem->iItem;
3599 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3601 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3603 infoPtr->nFocusedItem = -1;
3608 /* if we're inserting the item, we're done */
3609 if (isNew) return TRUE;
3611 /* send LVN_ITEMCHANGED notification */
3612 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3613 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3615 return TRUE;
3618 /***
3619 * DESCRIPTION:
3620 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3622 * PARAMETER(S):
3623 * [I] infoPtr : valid pointer to the listview structure
3624 * [I] lpLVItem : valid pointer to new subitem attributes
3625 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3626 * [O] bChanged : will be set to TRUE if the item really changed
3628 * RETURN:
3629 * SUCCESS : TRUE
3630 * FAILURE : FALSE
3632 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3634 HDPA hdpaSubItems;
3635 SUBITEM_INFO *lpSubItem;
3637 /* we do not support subitems for virtual listviews */
3638 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3640 /* set subitem only if column is present */
3641 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3643 /* First do some sanity checks */
3644 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3645 particularly useful. We currently do not actually do anything with
3646 the flag on subitems.
3648 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3649 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3651 /* get the subitem structure, and create it if not there */
3652 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3653 assert (hdpaSubItems);
3655 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3656 if (!lpSubItem)
3658 SUBITEM_INFO *tmpSubItem;
3659 INT i;
3661 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3662 if (!lpSubItem) return FALSE;
3663 /* we could binary search here, if need be...*/
3664 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3666 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3667 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3669 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3671 Free(lpSubItem);
3672 return FALSE;
3674 lpSubItem->iSubItem = lpLVItem->iSubItem;
3675 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3676 *bChanged = TRUE;
3679 if (lpLVItem->mask & LVIF_IMAGE)
3680 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3682 lpSubItem->hdr.iImage = lpLVItem->iImage;
3683 *bChanged = TRUE;
3686 if (lpLVItem->mask & LVIF_TEXT)
3687 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3689 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3690 *bChanged = TRUE;
3693 return TRUE;
3696 /***
3697 * DESCRIPTION:
3698 * Sets item attributes.
3700 * PARAMETER(S):
3701 * [I] infoPtr : valid pointer to the listview structure
3702 * [I] lpLVItem : new item attributes
3703 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3705 * RETURN:
3706 * SUCCESS : TRUE
3707 * FAILURE : FALSE
3709 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3711 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3712 HWND hwndSelf = infoPtr->hwndSelf;
3713 LPWSTR pszText = NULL;
3714 BOOL bResult, bChanged = FALSE;
3716 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3718 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3719 return FALSE;
3721 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3722 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3724 pszText = lpLVItem->pszText;
3725 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3728 /* actually set the fields */
3729 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3731 if (lpLVItem->iSubItem)
3732 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3733 else
3734 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3735 if (!IsWindow(hwndSelf))
3736 return FALSE;
3738 /* redraw item, if necessary */
3739 if (bChanged && !infoPtr->bIsDrawing)
3741 /* this little optimization eliminates some nasty flicker */
3742 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3743 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3744 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3745 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3746 else
3747 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3749 /* restore text */
3750 if (pszText)
3752 textfreeT(lpLVItem->pszText, isW);
3753 lpLVItem->pszText = pszText;
3756 return bResult;
3759 /***
3760 * DESCRIPTION:
3761 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3763 * PARAMETER(S):
3764 * [I] infoPtr : valid pointer to the listview structure
3766 * RETURN:
3767 * item index
3769 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3771 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3772 INT nItem = 0;
3773 SCROLLINFO scrollInfo;
3775 scrollInfo.cbSize = sizeof(SCROLLINFO);
3776 scrollInfo.fMask = SIF_POS;
3778 if (uView == LVS_LIST)
3780 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3781 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3783 else if (uView == LVS_REPORT)
3785 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3786 nItem = scrollInfo.nPos;
3788 else
3790 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3791 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3794 TRACE("nItem=%d\n", nItem);
3796 return nItem;
3800 /***
3801 * DESCRIPTION:
3802 * Erases the background of the given rectangle
3804 * PARAMETER(S):
3805 * [I] infoPtr : valid pointer to the listview structure
3806 * [I] hdc : device context handle
3807 * [I] lprcBox : clipping rectangle
3809 * RETURN:
3810 * Success: TRUE
3811 * Failure: FALSE
3813 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3815 if (!infoPtr->hBkBrush) return FALSE;
3817 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3819 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3822 /***
3823 * DESCRIPTION:
3824 * Draws an item.
3826 * PARAMETER(S):
3827 * [I] infoPtr : valid pointer to the listview structure
3828 * [I] hdc : device context handle
3829 * [I] nItem : item index
3830 * [I] nSubItem : subitem index
3831 * [I] pos : item position in client coordinates
3832 * [I] cdmode : custom draw mode
3834 * RETURN:
3835 * Success: TRUE
3836 * Failure: FALSE
3838 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3840 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3841 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3842 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3843 DWORD cdsubitemmode = CDRF_DODEFAULT;
3844 LPRECT lprcFocus;
3845 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3846 NMLVCUSTOMDRAW nmlvcd;
3847 HIMAGELIST himl;
3848 LVITEMW lvItem;
3849 HFONT hOldFont;
3851 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3853 /* get information needed for drawing the item */
3854 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3855 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3856 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3857 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3858 lvItem.iItem = nItem;
3859 lvItem.iSubItem = nSubItem;
3860 lvItem.state = 0;
3861 lvItem.lParam = 0;
3862 lvItem.cchTextMax = DISP_TEXT_SIZE;
3863 lvItem.pszText = szDispText;
3864 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3865 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3866 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3867 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3868 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3870 /* now check if we need to update the focus rectangle */
3871 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3873 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3874 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3875 OffsetRect(&rcBox, pos.x, pos.y);
3876 OffsetRect(&rcSelect, pos.x, pos.y);
3877 OffsetRect(&rcIcon, pos.x, pos.y);
3878 OffsetRect(&rcStateIcon, pos.x, pos.y);
3879 OffsetRect(&rcLabel, pos.x, pos.y);
3880 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3881 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3882 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3884 /* fill in the custom draw structure */
3885 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3887 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3888 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3889 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3890 if (cdmode & CDRF_NOTIFYITEMDRAW)
3891 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3892 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3893 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3894 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3895 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3897 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3898 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3900 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3901 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3902 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3903 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3905 /* in full row select, subitems, will just use main item's colors */
3906 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3907 nmlvcd.clrTextBk = CLR_NONE;
3909 /* state icons */
3910 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3912 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3913 if (uStateImage)
3915 TRACE("uStateImage=%d\n", uStateImage);
3916 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3917 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3921 /* small icons */
3922 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3923 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3925 TRACE("iImage=%d\n", lvItem.iImage);
3926 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3927 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3928 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3931 /* Don't bother painting item being edited */
3932 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3934 /* FIXME: temporary hack */
3935 rcSelect.left = rcLabel.left;
3937 /* draw the selection background, if we're drawing the main item */
3938 if (nSubItem == 0)
3940 /* in icon mode, the label rect is really what we want to draw the
3941 * background for */
3942 if (uView == LVS_ICON)
3943 rcSelect = rcLabel;
3945 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3946 rcSelect.right = rcBox.right;
3948 if (nmlvcd.clrTextBk != CLR_NONE)
3949 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3950 if(lprcFocus) *lprcFocus = rcSelect;
3953 /* figure out the text drawing flags */
3954 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3955 if (uView == LVS_ICON)
3956 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3957 else if (nSubItem)
3959 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3961 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3962 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3963 default: uFormat |= DT_LEFT;
3966 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3968 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3969 else rcLabel.left += LABEL_HOR_PADDING;
3971 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3973 /* for GRIDLINES reduce the bottom so the text formats correctly */
3974 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3975 rcLabel.bottom--;
3977 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3979 postpaint:
3980 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3981 notify_postpaint(infoPtr, &nmlvcd);
3982 if (cdsubitemmode & CDRF_NEWFONT)
3983 SelectObject(hdc, hOldFont);
3984 return TRUE;
3987 /***
3988 * DESCRIPTION:
3989 * Draws listview items when in owner draw mode.
3991 * PARAMETER(S):
3992 * [I] infoPtr : valid pointer to the listview structure
3993 * [I] hdc : device context handle
3995 * RETURN:
3996 * None
3998 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4000 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4001 DWORD cditemmode = CDRF_DODEFAULT;
4002 NMLVCUSTOMDRAW nmlvcd;
4003 POINT Origin, Position;
4004 DRAWITEMSTRUCT dis;
4005 LVITEMW item;
4007 TRACE("()\n");
4009 ZeroMemory(&dis, sizeof(dis));
4011 /* Get scroll info once before loop */
4012 LISTVIEW_GetOrigin(infoPtr, &Origin);
4014 /* iterate through the invalidated rows */
4015 while(iterator_next(i))
4017 item.iItem = i->nItem;
4018 item.iSubItem = 0;
4019 item.mask = LVIF_PARAM | LVIF_STATE;
4020 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4021 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4023 dis.CtlType = ODT_LISTVIEW;
4024 dis.CtlID = uID;
4025 dis.itemID = item.iItem;
4026 dis.itemAction = ODA_DRAWENTIRE;
4027 dis.itemState = 0;
4028 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4029 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4030 dis.hwndItem = infoPtr->hwndSelf;
4031 dis.hDC = hdc;
4032 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4033 dis.rcItem.left = Position.x + Origin.x;
4034 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4035 dis.rcItem.top = Position.y + Origin.y;
4036 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4037 dis.itemData = item.lParam;
4039 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4042 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4043 * structure for the rest. of the paint cycle
4045 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4046 if (cdmode & CDRF_NOTIFYITEMDRAW)
4047 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4049 if (!(cditemmode & CDRF_SKIPDEFAULT))
4051 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4052 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4055 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4056 notify_postpaint(infoPtr, &nmlvcd);
4060 /***
4061 * DESCRIPTION:
4062 * Draws listview items when in report display mode.
4064 * PARAMETER(S):
4065 * [I] infoPtr : valid pointer to the listview structure
4066 * [I] hdc : device context handle
4067 * [I] cdmode : custom draw mode
4069 * RETURN:
4070 * None
4072 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4074 INT rgntype;
4075 RECT rcClip, rcItem;
4076 POINT Origin, Position;
4077 RANGE colRange;
4078 ITERATOR j;
4080 TRACE("()\n");
4082 /* figure out what to draw */
4083 rgntype = GetClipBox(hdc, &rcClip);
4084 if (rgntype == NULLREGION) return;
4086 /* Get scroll info once before loop */
4087 LISTVIEW_GetOrigin(infoPtr, &Origin);
4089 /* narrow down the columns we need to paint */
4090 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4092 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4093 if (rcItem.right + Origin.x >= rcClip.left) break;
4095 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4097 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4098 if (rcItem.left + Origin.x < rcClip.right) break;
4100 iterator_rangeitems(&j, colRange);
4102 /* in full row select, we _have_ to draw the main item */
4103 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4104 j.nSpecial = 0;
4106 /* iterate through the invalidated rows */
4107 while(iterator_next(i))
4109 /* iterate through the invalidated columns */
4110 while(iterator_next(&j))
4112 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4113 Position.x += Origin.x;
4114 Position.y += Origin.y;
4116 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4118 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4119 rcItem.top = 0;
4120 rcItem.bottom = infoPtr->nItemHeight;
4121 OffsetRect(&rcItem, Position.x, Position.y);
4122 if (!RectVisible(hdc, &rcItem)) continue;
4125 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4128 iterator_destroy(&j);
4131 /***
4132 * DESCRIPTION:
4133 * Draws the gridlines if necessary when in report display mode.
4135 * PARAMETER(S):
4136 * [I] infoPtr : valid pointer to the listview structure
4137 * [I] hdc : device context handle
4139 * RETURN:
4140 * None
4142 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4144 INT rgntype;
4145 INT y, itemheight;
4146 HPEN hPen, hOldPen;
4147 RECT rcClip, rcItem;
4148 POINT Origin;
4149 RANGE colRange;
4150 ITERATOR j;
4152 TRACE("()\n");
4154 /* figure out what to draw */
4155 rgntype = GetClipBox(hdc, &rcClip);
4156 if (rgntype == NULLREGION) return;
4158 /* Get scroll info once before loop */
4159 LISTVIEW_GetOrigin(infoPtr, &Origin);
4161 /* narrow down the columns we need to paint */
4162 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4164 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4165 if (rcItem.right + Origin.x >= rcClip.left) break;
4167 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4169 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4170 if (rcItem.left + Origin.x < rcClip.right) break;
4172 iterator_rangeitems(&j, colRange);
4174 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4176 hOldPen = SelectObject ( hdc, hPen );
4178 /* draw the vertical lines for the columns */
4179 iterator_rangeitems(&j, colRange);
4180 while(iterator_next(&j))
4182 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4183 if (rcItem.left == 0) continue; /* skip first column */
4184 rcItem.left += Origin.x;
4185 rcItem.right += Origin.x;
4186 rcItem.top = infoPtr->rcList.top;
4187 rcItem.bottom = infoPtr->rcList.bottom;
4188 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4189 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4190 LineTo (hdc, rcItem.left, rcItem.bottom);
4192 iterator_destroy(&j);
4194 /* draw the horizontial lines for the rows */
4195 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4196 rcItem.left = infoPtr->rcList.left + Origin.x;
4197 rcItem.right = infoPtr->rcList.right + Origin.x;
4198 rcItem.bottom = rcItem.top = Origin.y - 1;
4199 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4200 LineTo(hdc, rcItem.right, rcItem.top);
4201 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4203 rcItem.bottom = rcItem.top = y;
4204 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4205 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4206 LineTo (hdc, rcItem.right, rcItem.top);
4209 SelectObject( hdc, hOldPen );
4210 DeleteObject( hPen );
4214 /***
4215 * DESCRIPTION:
4216 * Draws listview items when in list display mode.
4218 * PARAMETER(S):
4219 * [I] infoPtr : valid pointer to the listview structure
4220 * [I] hdc : device context handle
4221 * [I] cdmode : custom draw mode
4223 * RETURN:
4224 * None
4226 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4228 POINT Origin, Position;
4230 /* Get scroll info once before loop */
4231 LISTVIEW_GetOrigin(infoPtr, &Origin);
4233 while(iterator_prev(i))
4235 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4236 Position.x += Origin.x;
4237 Position.y += Origin.y;
4239 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4244 /***
4245 * DESCRIPTION:
4246 * Draws listview items.
4248 * PARAMETER(S):
4249 * [I] infoPtr : valid pointer to the listview structure
4250 * [I] hdc : device context handle
4251 * [I] prcErase : rect to be erased before refresh (may be NULL)
4253 * RETURN:
4254 * NoneX
4256 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4258 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4259 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4260 NMLVCUSTOMDRAW nmlvcd;
4261 HFONT hOldFont = 0;
4262 DWORD cdmode;
4263 INT oldBkMode = 0;
4264 RECT rcClient;
4265 ITERATOR i;
4266 HDC hdcOrig = hdc;
4267 HBITMAP hbmp = NULL;
4269 LISTVIEW_DUMP(infoPtr);
4271 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4272 TRACE("double buffering\n");
4274 hdc = CreateCompatibleDC(hdcOrig);
4275 if (!hdc) {
4276 ERR("Failed to create DC for backbuffer\n");
4277 return;
4279 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4280 infoPtr->rcList.bottom);
4281 if (!hbmp) {
4282 ERR("Failed to create bitmap for backbuffer\n");
4283 DeleteDC(hdc);
4284 return;
4287 SelectObject(hdc, hbmp);
4288 SelectObject(hdc, infoPtr->hFont);
4289 } else {
4290 /* Save dc values we're gonna trash while drawing
4291 * FIXME: Should be done in LISTVIEW_DrawItem() */
4292 hOldFont = SelectObject(hdc, infoPtr->hFont);
4293 oldBkMode = GetBkMode(hdc);
4294 oldBkColor = GetBkColor(hdc);
4295 oldTextColor = GetTextColor(hdc);
4298 infoPtr->bIsDrawing = TRUE;
4300 if (prcErase) {
4301 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4302 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4303 /* If no erasing was done (usually because RedrawWindow was called
4304 * with RDW_INVALIDATE only) we need to copy the old contents into
4305 * the backbuffer before continuing. */
4306 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4307 infoPtr->rcList.right - infoPtr->rcList.left,
4308 infoPtr->rcList.bottom - infoPtr->rcList.top,
4309 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4312 /* FIXME: Shouldn't need to do this */
4313 oldClrTextBk = infoPtr->clrTextBk;
4314 oldClrText = infoPtr->clrText;
4316 infoPtr->cditemmode = CDRF_DODEFAULT;
4318 GetClientRect(infoPtr->hwndSelf, &rcClient);
4319 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4320 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4321 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4322 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4324 /* Use these colors to draw the items */
4325 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4326 infoPtr->clrText = nmlvcd.clrText;
4328 /* nothing to draw */
4329 if(infoPtr->nItemCount == 0) goto enddraw;
4331 /* figure out what we need to draw */
4332 iterator_visibleitems(&i, infoPtr, hdc);
4334 /* send cache hint notification */
4335 if (infoPtr->dwStyle & LVS_OWNERDATA)
4337 RANGE range = iterator_range(&i);
4338 NMLVCACHEHINT nmlv;
4340 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4341 nmlv.iFrom = range.lower;
4342 nmlv.iTo = range.upper - 1;
4343 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4346 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4347 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4348 else
4350 if (uView == LVS_REPORT)
4351 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4352 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4353 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4355 /* if we have a focus rect, draw it */
4356 if (infoPtr->bFocus)
4357 DrawFocusRect(hdc, &infoPtr->rcFocus);
4359 iterator_destroy(&i);
4361 enddraw:
4362 /* For LVS_EX_GRIDLINES go and draw lines */
4363 /* This includes the case where there were *no* items */
4364 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4365 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4366 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4368 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4369 notify_postpaint(infoPtr, &nmlvcd);
4371 infoPtr->clrTextBk = oldClrTextBk;
4372 infoPtr->clrText = oldClrText;
4374 if(hbmp) {
4375 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4376 infoPtr->rcList.right - infoPtr->rcList.left,
4377 infoPtr->rcList.bottom - infoPtr->rcList.top,
4378 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4380 DeleteObject(hbmp);
4381 DeleteDC(hdc);
4382 } else {
4383 SelectObject(hdc, hOldFont);
4384 SetBkMode(hdc, oldBkMode);
4385 SetBkColor(hdc, oldBkColor);
4386 SetTextColor(hdc, oldTextColor);
4389 infoPtr->bIsDrawing = FALSE;
4393 /***
4394 * DESCRIPTION:
4395 * Calculates the approximate width and height of a given number of items.
4397 * PARAMETER(S):
4398 * [I] infoPtr : valid pointer to the listview structure
4399 * [I] nItemCount : number of items
4400 * [I] wWidth : width
4401 * [I] wHeight : height
4403 * RETURN:
4404 * Returns a DWORD. The width in the low word and the height in high word.
4406 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4407 WORD wWidth, WORD wHeight)
4409 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4410 INT nItemCountPerColumn = 1;
4411 INT nColumnCount = 0;
4412 DWORD dwViewRect = 0;
4414 if (nItemCount == -1)
4415 nItemCount = infoPtr->nItemCount;
4417 if (uView == LVS_LIST)
4419 if (wHeight == 0xFFFF)
4421 /* use current height */
4422 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4425 if (wHeight < infoPtr->nItemHeight)
4426 wHeight = infoPtr->nItemHeight;
4428 if (nItemCount > 0)
4430 if (infoPtr->nItemHeight > 0)
4432 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4433 if (nItemCountPerColumn == 0)
4434 nItemCountPerColumn = 1;
4436 if (nItemCount % nItemCountPerColumn != 0)
4437 nColumnCount = nItemCount / nItemCountPerColumn;
4438 else
4439 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4443 /* Microsoft padding magic */
4444 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4445 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4447 dwViewRect = MAKELONG(wWidth, wHeight);
4449 else if (uView == LVS_REPORT)
4451 RECT rcBox;
4453 if (infoPtr->nItemCount > 0)
4455 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4456 wWidth = rcBox.right - rcBox.left;
4457 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4459 else
4461 /* use current height and width */
4462 if (wHeight == 0xffff)
4463 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4464 if (wWidth == 0xffff)
4465 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4468 dwViewRect = MAKELONG(wWidth, wHeight);
4470 else if (uView == LVS_SMALLICON)
4471 FIXME("uView == LVS_SMALLICON: not implemented\n");
4472 else if (uView == LVS_ICON)
4473 FIXME("uView == LVS_ICON: not implemented\n");
4475 return dwViewRect;
4479 /***
4480 * DESCRIPTION:
4481 * Create a drag image list for the specified item.
4483 * PARAMETER(S):
4484 * [I] infoPtr : valid pointer to the listview structure
4485 * [I] iItem : index of item
4486 * [O] lppt : Upper-left corner of the image
4488 * RETURN:
4489 * Returns a handle to the image list if successful, NULL otherwise.
4491 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4493 RECT rcItem;
4494 SIZE size;
4495 POINT pos;
4496 HDC hdc, hdcOrig;
4497 HBITMAP hbmp, hOldbmp;
4498 HIMAGELIST dragList = 0;
4499 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4501 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4502 return 0;
4504 rcItem.left = LVIR_BOUNDS;
4505 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4506 return 0;
4508 lppt->x = rcItem.left;
4509 lppt->y = rcItem.top;
4511 size.cx = rcItem.right - rcItem.left;
4512 size.cy = rcItem.bottom - rcItem.top;
4514 hdcOrig = GetDC(infoPtr->hwndSelf);
4515 hdc = CreateCompatibleDC(hdcOrig);
4516 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4517 hOldbmp = SelectObject(hdc, hbmp);
4519 rcItem.left = rcItem.top = 0;
4520 rcItem.right = size.cx;
4521 rcItem.bottom = size.cy;
4522 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4524 pos.x = pos.y = 0;
4525 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4527 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4528 SelectObject(hdc, hOldbmp);
4529 ImageList_Add(dragList, hbmp, 0);
4531 else
4532 SelectObject(hdc, hOldbmp);
4534 DeleteObject(hbmp);
4535 DeleteDC(hdc);
4536 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4538 TRACE("ret=%p\n", dragList);
4540 return dragList;
4544 /***
4545 * DESCRIPTION:
4546 * Removes all listview items and subitems.
4548 * PARAMETER(S):
4549 * [I] infoPtr : valid pointer to the listview structure
4551 * RETURN:
4552 * SUCCESS : TRUE
4553 * FAILURE : FALSE
4555 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4557 NMLISTVIEW nmlv;
4558 HDPA hdpaSubItems = NULL;
4559 BOOL bSuppress;
4560 ITEMHDR *hdrItem;
4561 INT i, j;
4563 TRACE("()\n");
4565 /* we do it directly, to avoid notifications */
4566 ranges_clear(infoPtr->selectionRanges);
4567 infoPtr->nSelectionMark = -1;
4568 infoPtr->nFocusedItem = -1;
4569 SetRectEmpty(&infoPtr->rcFocus);
4570 /* But we are supposed to leave nHotItem as is! */
4573 /* send LVN_DELETEALLITEMS notification */
4574 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4575 nmlv.iItem = -1;
4576 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4578 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4580 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4582 /* send LVN_DELETEITEM notification, if not suppressed
4583 and if it is not a virtual listview */
4584 if (!bSuppress) notify_deleteitem(infoPtr, i);
4585 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4586 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4588 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4589 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4590 Free(hdrItem);
4592 DPA_Destroy(hdpaSubItems);
4593 DPA_DeletePtr(infoPtr->hdpaItems, i);
4595 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4596 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4597 infoPtr->nItemCount --;
4600 if (!destroy)
4602 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4603 LISTVIEW_UpdateScroll(infoPtr);
4605 LISTVIEW_InvalidateList(infoPtr);
4607 return TRUE;
4610 /***
4611 * DESCRIPTION:
4612 * Scrolls, and updates the columns, when a column is changing width.
4614 * PARAMETER(S):
4615 * [I] infoPtr : valid pointer to the listview structure
4616 * [I] nColumn : column to scroll
4617 * [I] dx : amount of scroll, in pixels
4619 * RETURN:
4620 * None.
4622 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4624 COLUMN_INFO *lpColumnInfo;
4625 RECT rcOld, rcCol;
4626 POINT ptOrigin;
4627 INT nCol;
4629 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4630 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4631 rcCol = lpColumnInfo->rcHeader;
4632 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4633 rcCol.left = rcCol.right;
4635 /* adjust the other columns */
4636 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4638 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4639 lpColumnInfo->rcHeader.left += dx;
4640 lpColumnInfo->rcHeader.right += dx;
4643 /* do not update screen if not in report mode */
4644 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4646 /* if we have a focus, we must first erase the focus rect */
4647 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4649 /* Need to reset the item width when inserting a new column */
4650 infoPtr->nItemWidth += dx;
4652 LISTVIEW_UpdateScroll(infoPtr);
4653 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4655 /* scroll to cover the deleted column, and invalidate for redraw */
4656 rcOld = infoPtr->rcList;
4657 rcOld.left = ptOrigin.x + rcCol.left + dx;
4658 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4660 /* we can restore focus now */
4661 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4664 /***
4665 * DESCRIPTION:
4666 * Removes a column from the listview control.
4668 * PARAMETER(S):
4669 * [I] infoPtr : valid pointer to the listview structure
4670 * [I] nColumn : column index
4672 * RETURN:
4673 * SUCCESS : TRUE
4674 * FAILURE : FALSE
4676 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4678 RECT rcCol;
4680 TRACE("nColumn=%d\n", nColumn);
4682 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4683 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4685 /* While the MSDN specifically says that column zero should not be deleted,
4686 what actually happens is that the column itself is deleted but no items or subitems
4687 are removed.
4690 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4692 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4693 return FALSE;
4695 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4696 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4698 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4700 SUBITEM_INFO *lpSubItem, *lpDelItem;
4701 HDPA hdpaSubItems;
4702 INT nItem, nSubItem, i;
4704 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4706 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4707 nSubItem = 0;
4708 lpDelItem = 0;
4709 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4711 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4712 if (lpSubItem->iSubItem == nColumn)
4714 nSubItem = i;
4715 lpDelItem = lpSubItem;
4717 else if (lpSubItem->iSubItem > nColumn)
4719 lpSubItem->iSubItem--;
4723 /* if we found our subitem, zapp it */
4724 if (nSubItem > 0)
4726 /* free string */
4727 if (is_textW(lpDelItem->hdr.pszText))
4728 Free(lpDelItem->hdr.pszText);
4730 /* free item */
4731 Free(lpDelItem);
4733 /* free dpa memory */
4734 DPA_DeletePtr(hdpaSubItems, nSubItem);
4739 /* update the other column info */
4740 LISTVIEW_UpdateItemSize(infoPtr);
4741 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4742 LISTVIEW_InvalidateList(infoPtr);
4743 else
4744 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4746 return TRUE;
4749 /***
4750 * DESCRIPTION:
4751 * Invalidates the listview after an item's insertion or deletion.
4753 * PARAMETER(S):
4754 * [I] infoPtr : valid pointer to the listview structure
4755 * [I] nItem : item index
4756 * [I] dir : -1 if deleting, 1 if inserting
4758 * RETURN:
4759 * None
4761 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4763 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4764 INT nPerCol, nItemCol, nItemRow;
4765 RECT rcScroll;
4766 POINT Origin;
4768 /* if we don't refresh, what's the point of scrolling? */
4769 if (!is_redrawing(infoPtr)) return;
4771 assert (abs(dir) == 1);
4773 /* arrange icons if autoarrange is on */
4774 if (is_autoarrange(infoPtr))
4776 BOOL arrange = TRUE;
4777 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4778 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4779 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4782 /* scrollbars need updating */
4783 LISTVIEW_UpdateScroll(infoPtr);
4785 /* figure out the item's position */
4786 if (uView == LVS_REPORT)
4787 nPerCol = infoPtr->nItemCount + 1;
4788 else if (uView == LVS_LIST)
4789 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4790 else /* LVS_ICON, or LVS_SMALLICON */
4791 return;
4793 nItemCol = nItem / nPerCol;
4794 nItemRow = nItem % nPerCol;
4795 LISTVIEW_GetOrigin(infoPtr, &Origin);
4797 /* move the items below up a slot */
4798 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4799 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4800 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4801 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4802 OffsetRect(&rcScroll, Origin.x, Origin.y);
4803 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4804 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4806 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4807 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4808 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4811 /* report has only that column, so we're done */
4812 if (uView == LVS_REPORT) return;
4814 /* now for LISTs, we have to deal with the columns to the right */
4815 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4816 rcScroll.top = 0;
4817 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4818 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4819 OffsetRect(&rcScroll, Origin.x, Origin.y);
4820 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4821 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4822 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4825 /***
4826 * DESCRIPTION:
4827 * Removes an item from the listview control.
4829 * PARAMETER(S):
4830 * [I] infoPtr : valid pointer to the listview structure
4831 * [I] nItem : item index
4833 * RETURN:
4834 * SUCCESS : TRUE
4835 * FAILURE : FALSE
4837 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4839 LVITEMW item;
4840 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4841 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4843 TRACE("(nItem=%d)\n", nItem);
4845 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4847 /* remove selection, and focus */
4848 item.state = 0;
4849 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4850 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4852 /* send LVN_DELETEITEM notification. */
4853 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4855 /* we need to do this here, because we'll be deleting stuff */
4856 if (is_icon)
4857 LISTVIEW_InvalidateItem(infoPtr, nItem);
4859 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4861 HDPA hdpaSubItems;
4862 ITEMHDR *hdrItem;
4863 INT i;
4865 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4866 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4868 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4869 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4870 Free(hdrItem);
4872 DPA_Destroy(hdpaSubItems);
4875 if (is_icon)
4877 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4878 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4881 infoPtr->nItemCount--;
4882 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4884 /* now is the invalidation fun */
4885 if (!is_icon)
4886 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4887 return TRUE;
4891 /***
4892 * DESCRIPTION:
4893 * Callback implementation for editlabel control
4895 * PARAMETER(S):
4896 * [I] infoPtr : valid pointer to the listview structure
4897 * [I] pszText : modified text
4898 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4900 * RETURN:
4901 * SUCCESS : TRUE
4902 * FAILURE : FALSE
4904 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4906 HWND hwndSelf = infoPtr->hwndSelf;
4907 NMLVDISPINFOW dispInfo;
4908 INT editedItem = infoPtr->nEditLabelItem;
4909 BOOL bSame;
4911 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4913 infoPtr->nEditLabelItem = -1;
4915 ZeroMemory(&dispInfo, sizeof(dispInfo));
4916 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4917 dispInfo.item.iItem = editedItem;
4918 dispInfo.item.iSubItem = 0;
4919 dispInfo.item.stateMask = ~0;
4920 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4922 if (isW)
4923 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4924 else
4926 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4927 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4928 textfreeT(tmp, FALSE);
4930 if (bSame) return TRUE;
4932 /* add the text from the edit in */
4933 dispInfo.item.mask |= LVIF_TEXT;
4934 dispInfo.item.pszText = pszText;
4935 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4937 /* Do we need to update the Item Text */
4938 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4939 if (!IsWindow(hwndSelf))
4940 return FALSE;
4941 if (!pszText) return TRUE;
4943 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4945 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4946 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4947 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4949 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4950 return TRUE;
4954 ZeroMemory(&dispInfo, sizeof(dispInfo));
4955 dispInfo.item.mask = LVIF_TEXT;
4956 dispInfo.item.iItem = editedItem;
4957 dispInfo.item.iSubItem = 0;
4958 dispInfo.item.pszText = pszText;
4959 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4960 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4963 /***
4964 * DESCRIPTION:
4965 * Begin in place editing of specified list view item
4967 * PARAMETER(S):
4968 * [I] infoPtr : valid pointer to the listview structure
4969 * [I] nItem : item index
4970 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4972 * RETURN:
4973 * SUCCESS : TRUE
4974 * FAILURE : FALSE
4976 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4978 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4979 NMLVDISPINFOW dispInfo;
4980 RECT rect;
4981 HWND hwndSelf = infoPtr->hwndSelf;
4983 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4985 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4986 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4988 infoPtr->nEditLabelItem = nItem;
4990 /* Is the EditBox still there, if so remove it */
4991 if(infoPtr->hwndEdit != 0)
4993 SetFocus(infoPtr->hwndSelf);
4994 infoPtr->hwndEdit = 0;
4997 LISTVIEW_SetSelection(infoPtr, nItem);
4998 LISTVIEW_SetItemFocus(infoPtr, nItem);
4999 LISTVIEW_InvalidateItem(infoPtr, nItem);
5001 rect.left = LVIR_LABEL;
5002 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
5004 ZeroMemory(&dispInfo, sizeof(dispInfo));
5005 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5006 dispInfo.item.iItem = nItem;
5007 dispInfo.item.iSubItem = 0;
5008 dispInfo.item.stateMask = ~0;
5009 dispInfo.item.pszText = szDispText;
5010 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5011 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
5013 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
5014 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
5015 if (!infoPtr->hwndEdit) return 0;
5017 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
5019 if (!IsWindow(hwndSelf))
5020 return 0;
5021 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
5022 infoPtr->hwndEdit = 0;
5023 return 0;
5026 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
5027 SetFocus(infoPtr->hwndEdit);
5028 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
5029 return infoPtr->hwndEdit;
5033 /***
5034 * DESCRIPTION:
5035 * Ensures the specified item is visible, scrolling into view if necessary.
5037 * PARAMETER(S):
5038 * [I] infoPtr : valid pointer to the listview structure
5039 * [I] nItem : item index
5040 * [I] bPartial : partially or entirely visible
5042 * RETURN:
5043 * SUCCESS : TRUE
5044 * FAILURE : FALSE
5046 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
5048 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5049 INT nScrollPosHeight = 0;
5050 INT nScrollPosWidth = 0;
5051 INT nHorzAdjust = 0;
5052 INT nVertAdjust = 0;
5053 INT nHorzDiff = 0;
5054 INT nVertDiff = 0;
5055 RECT rcItem, rcTemp;
5057 rcItem.left = LVIR_BOUNDS;
5058 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
5060 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
5062 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
5064 /* scroll left/right, but in LVS_REPORT mode */
5065 if (uView == LVS_LIST)
5066 nScrollPosWidth = infoPtr->nItemWidth;
5067 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5068 nScrollPosWidth = 1;
5070 if (rcItem.left < infoPtr->rcList.left)
5072 nHorzAdjust = -1;
5073 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5075 else
5077 nHorzAdjust = 1;
5078 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5082 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5084 /* scroll up/down, but not in LVS_LIST mode */
5085 if (uView == LVS_REPORT)
5086 nScrollPosHeight = infoPtr->nItemHeight;
5087 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5088 nScrollPosHeight = 1;
5090 if (rcItem.top < infoPtr->rcList.top)
5092 nVertAdjust = -1;
5093 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5095 else
5097 nVertAdjust = 1;
5098 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5102 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5104 if (nScrollPosWidth)
5106 INT diff = nHorzDiff / nScrollPosWidth;
5107 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5108 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5111 if (nScrollPosHeight)
5113 INT diff = nVertDiff / nScrollPosHeight;
5114 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5115 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5118 return TRUE;
5121 /***
5122 * DESCRIPTION:
5123 * Searches for an item with specific characteristics.
5125 * PARAMETER(S):
5126 * [I] hwnd : window handle
5127 * [I] nStart : base item index
5128 * [I] lpFindInfo : item information to look for
5130 * RETURN:
5131 * SUCCESS : index of item
5132 * FAILURE : -1
5134 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5135 const LVFINDINFOW *lpFindInfo)
5137 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5138 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5139 BOOL bWrap = FALSE, bNearest = FALSE;
5140 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5141 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5142 POINT Position, Destination;
5143 LVITEMW lvItem;
5145 /* Search in virtual listviews should be done by application, not by
5146 listview control, so we just send LVN_ODFINDITEMW and return the result */
5147 if (infoPtr->dwStyle & LVS_OWNERDATA)
5149 NMLVFINDITEMW nmlv;
5151 nmlv.iStart = nStart;
5152 nmlv.lvfi = *lpFindInfo;
5153 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
5156 if (!lpFindInfo || nItem < 0) return -1;
5158 lvItem.mask = 0;
5159 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5161 lvItem.mask |= LVIF_TEXT;
5162 lvItem.pszText = szDispText;
5163 lvItem.cchTextMax = DISP_TEXT_SIZE;
5166 if (lpFindInfo->flags & LVFI_WRAP)
5167 bWrap = TRUE;
5169 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5170 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5172 POINT Origin;
5173 RECT rcArea;
5175 LISTVIEW_GetOrigin(infoPtr, &Origin);
5176 Destination.x = lpFindInfo->pt.x - Origin.x;
5177 Destination.y = lpFindInfo->pt.y - Origin.y;
5178 switch(lpFindInfo->vkDirection)
5180 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5181 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5182 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5183 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5184 case VK_HOME: Destination.x = Destination.y = 0; break;
5185 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5186 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5187 case VK_END:
5188 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5189 Destination.x = rcArea.right;
5190 Destination.y = rcArea.bottom;
5191 break;
5192 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5194 bNearest = TRUE;
5196 else Destination.x = Destination.y = 0;
5198 /* if LVFI_PARAM is specified, all other flags are ignored */
5199 if (lpFindInfo->flags & LVFI_PARAM)
5201 lvItem.mask |= LVIF_PARAM;
5202 bNearest = FALSE;
5203 lvItem.mask &= ~LVIF_TEXT;
5206 again:
5207 for (; nItem < nLast; nItem++)
5209 lvItem.iItem = nItem;
5210 lvItem.iSubItem = 0;
5211 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5213 if (lvItem.mask & LVIF_PARAM)
5215 if (lpFindInfo->lParam == lvItem.lParam)
5216 return nItem;
5217 else
5218 continue;
5221 if (lvItem.mask & LVIF_TEXT)
5223 if (lpFindInfo->flags & LVFI_PARTIAL)
5225 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5227 else
5229 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5233 if (!bNearest) return nItem;
5235 /* This is very inefficient. To do a good job here,
5236 * we need a sorted array of (x,y) item positions */
5237 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5239 /* compute the distance^2 to the destination */
5240 xdist = Destination.x - Position.x;
5241 ydist = Destination.y - Position.y;
5242 dist = xdist * xdist + ydist * ydist;
5244 /* remember the distance, and item if it's closer */
5245 if (dist < mindist)
5247 mindist = dist;
5248 nNearestItem = nItem;
5252 if (bWrap)
5254 nItem = 0;
5255 nLast = min(nStart + 1, infoPtr->nItemCount);
5256 bWrap = FALSE;
5257 goto again;
5260 return nNearestItem;
5263 /***
5264 * DESCRIPTION:
5265 * Searches for an item with specific characteristics.
5267 * PARAMETER(S):
5268 * [I] hwnd : window handle
5269 * [I] nStart : base item index
5270 * [I] lpFindInfo : item information to look for
5272 * RETURN:
5273 * SUCCESS : index of item
5274 * FAILURE : -1
5276 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5277 const LVFINDINFOA *lpFindInfo)
5279 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5280 LVFINDINFOW fiw;
5281 INT res;
5282 LPWSTR strW = NULL;
5284 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5285 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5286 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5287 textfreeT(strW, FALSE);
5288 return res;
5291 /***
5292 * DESCRIPTION:
5293 * Retrieves the background image of the listview control.
5295 * PARAMETER(S):
5296 * [I] infoPtr : valid pointer to the listview structure
5297 * [O] lpBkImage : background image attributes
5299 * RETURN:
5300 * SUCCESS : TRUE
5301 * FAILURE : FALSE
5303 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5304 /* { */
5305 /* FIXME (listview, "empty stub!\n"); */
5306 /* return FALSE; */
5307 /* } */
5309 /***
5310 * DESCRIPTION:
5311 * Retrieves column attributes.
5313 * PARAMETER(S):
5314 * [I] infoPtr : valid pointer to the listview structure
5315 * [I] nColumn : column index
5316 * [IO] lpColumn : column information
5317 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5318 * otherwise it is in fact a LPLVCOLUMNA
5320 * RETURN:
5321 * SUCCESS : TRUE
5322 * FAILURE : FALSE
5324 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5326 COLUMN_INFO *lpColumnInfo;
5327 HDITEMW hdi;
5329 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5330 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5332 /* initialize memory */
5333 ZeroMemory(&hdi, sizeof(hdi));
5335 if (lpColumn->mask & LVCF_TEXT)
5337 hdi.mask |= HDI_TEXT;
5338 hdi.pszText = lpColumn->pszText;
5339 hdi.cchTextMax = lpColumn->cchTextMax;
5342 if (lpColumn->mask & LVCF_IMAGE)
5343 hdi.mask |= HDI_IMAGE;
5345 if (lpColumn->mask & LVCF_ORDER)
5346 hdi.mask |= HDI_ORDER;
5348 if (lpColumn->mask & LVCF_SUBITEM)
5349 hdi.mask |= HDI_LPARAM;
5351 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5353 if (lpColumn->mask & LVCF_FMT)
5354 lpColumn->fmt = lpColumnInfo->fmt;
5356 if (lpColumn->mask & LVCF_WIDTH)
5357 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5359 if (lpColumn->mask & LVCF_IMAGE)
5360 lpColumn->iImage = hdi.iImage;
5362 if (lpColumn->mask & LVCF_ORDER)
5363 lpColumn->iOrder = hdi.iOrder;
5365 if (lpColumn->mask & LVCF_SUBITEM)
5366 lpColumn->iSubItem = hdi.lParam;
5368 return TRUE;
5372 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5374 INT i;
5376 if (!lpiArray)
5377 return FALSE;
5379 /* FIXME: little hack */
5380 for (i = 0; i < iCount; i++)
5381 lpiArray[i] = i;
5383 return TRUE;
5386 /***
5387 * DESCRIPTION:
5388 * Retrieves the column width.
5390 * PARAMETER(S):
5391 * [I] infoPtr : valid pointer to the listview structure
5392 * [I] int : column index
5394 * RETURN:
5395 * SUCCESS : column width
5396 * FAILURE : zero
5398 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5400 INT nColumnWidth = 0;
5401 HDITEMW hdItem;
5403 TRACE("nColumn=%d\n", nColumn);
5405 /* we have a 'column' in LIST and REPORT mode only */
5406 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5408 case LVS_LIST:
5409 nColumnWidth = infoPtr->nItemWidth;
5410 break;
5411 case LVS_REPORT:
5412 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5413 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5414 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5416 * TODO: should we do the same in LVM_GETCOLUMN?
5418 hdItem.mask = HDI_WIDTH;
5419 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5421 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5422 return 0;
5424 nColumnWidth = hdItem.cxy;
5425 break;
5428 TRACE("nColumnWidth=%d\n", nColumnWidth);
5429 return nColumnWidth;
5432 /***
5433 * DESCRIPTION:
5434 * In list or report display mode, retrieves the number of items that can fit
5435 * vertically in the visible area. In icon or small icon display mode,
5436 * retrieves the total number of visible items.
5438 * PARAMETER(S):
5439 * [I] infoPtr : valid pointer to the listview structure
5441 * RETURN:
5442 * Number of fully visible items.
5444 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5446 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5448 case LVS_ICON:
5449 case LVS_SMALLICON:
5450 return infoPtr->nItemCount;
5451 case LVS_REPORT:
5452 return LISTVIEW_GetCountPerColumn(infoPtr);
5453 case LVS_LIST:
5454 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5456 assert(FALSE);
5457 return 0;
5460 /***
5461 * DESCRIPTION:
5462 * Retrieves an image list handle.
5464 * PARAMETER(S):
5465 * [I] infoPtr : valid pointer to the listview structure
5466 * [I] nImageList : image list identifier
5468 * RETURN:
5469 * SUCCESS : image list handle
5470 * FAILURE : NULL
5472 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5474 switch (nImageList)
5476 case LVSIL_NORMAL: return infoPtr->himlNormal;
5477 case LVSIL_SMALL: return infoPtr->himlSmall;
5478 case LVSIL_STATE: return infoPtr->himlState;
5480 return NULL;
5483 /* LISTVIEW_GetISearchString */
5485 /***
5486 * DESCRIPTION:
5487 * Retrieves item attributes.
5489 * PARAMETER(S):
5490 * [I] hwnd : window handle
5491 * [IO] lpLVItem : item info
5492 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5493 * if FALSE, then lpLVItem is a LPLVITEMA.
5495 * NOTE:
5496 * This is the internal 'GetItem' interface -- it tries to
5497 * be smart and avoid text copies, if possible, by modifying
5498 * lpLVItem->pszText to point to the text string. Please note
5499 * that this is not always possible (e.g. OWNERDATA), so on
5500 * entry you *must* supply valid values for pszText, and cchTextMax.
5501 * The only difference to the documented interface is that upon
5502 * return, you should use *only* the lpLVItem->pszText, rather than
5503 * the buffer pointer you provided on input. Most code already does
5504 * that, so it's not a problem.
5505 * For the two cases when the text must be copied (that is,
5506 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5508 * RETURN:
5509 * SUCCESS : TRUE
5510 * FAILURE : FALSE
5512 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5514 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5515 NMLVDISPINFOW dispInfo;
5516 ITEM_INFO *lpItem;
5517 ITEMHDR* pItemHdr;
5518 HDPA hdpaSubItems;
5519 INT isubitem;
5521 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5523 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5524 return FALSE;
5526 if (lpLVItem->mask == 0) return TRUE;
5528 /* make a local copy */
5529 isubitem = lpLVItem->iSubItem;
5531 /* a quick optimization if all we're asked is the focus state
5532 * these queries are worth optimising since they are common,
5533 * and can be answered in constant time, without the heavy accesses */
5534 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5535 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5537 lpLVItem->state = 0;
5538 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5539 lpLVItem->state |= LVIS_FOCUSED;
5540 return TRUE;
5543 ZeroMemory(&dispInfo, sizeof(dispInfo));
5545 /* if the app stores all the data, handle it separately */
5546 if (infoPtr->dwStyle & LVS_OWNERDATA)
5548 dispInfo.item.state = 0;
5550 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5551 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5553 /* NOTE: copy only fields which we _know_ are initialized, some apps
5554 * depend on the uninitialized fields being 0 */
5555 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5556 dispInfo.item.iItem = lpLVItem->iItem;
5557 dispInfo.item.iSubItem = isubitem;
5558 if (lpLVItem->mask & LVIF_TEXT)
5560 dispInfo.item.pszText = lpLVItem->pszText;
5561 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5563 if (lpLVItem->mask & LVIF_STATE)
5564 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5565 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5566 dispInfo.item.stateMask = lpLVItem->stateMask;
5567 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5569 /* full size structure expected - _WIN32IE >= 0x560 */
5570 *lpLVItem = dispInfo.item;
5572 else if (lpLVItem->mask & LVIF_INDENT)
5574 /* indent member expected - _WIN32IE >= 0x300 */
5575 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5577 else
5579 /* minimal structure expected */
5580 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5582 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5585 /* make sure lParam is zeroed out */
5586 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5588 /* we store only a little state, so if we're not asked, we're done */
5589 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5591 /* if focus is handled by us, report it */
5592 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5594 lpLVItem->state &= ~LVIS_FOCUSED;
5595 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5596 lpLVItem->state |= LVIS_FOCUSED;
5599 /* and do the same for selection, if we handle it */
5600 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5602 lpLVItem->state &= ~LVIS_SELECTED;
5603 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5604 lpLVItem->state |= LVIS_SELECTED;
5607 return TRUE;
5610 /* find the item and subitem structures before we proceed */
5611 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5612 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5613 assert (lpItem);
5615 if (isubitem)
5617 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5618 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5619 if (!lpSubItem)
5621 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5622 isubitem = 0;
5625 else
5626 pItemHdr = &lpItem->hdr;
5628 /* Do we need to query the state from the app? */
5629 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5631 dispInfo.item.mask |= LVIF_STATE;
5632 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5635 /* Do we need to enquire about the image? */
5636 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5637 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5639 dispInfo.item.mask |= LVIF_IMAGE;
5640 dispInfo.item.iImage = I_IMAGECALLBACK;
5643 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5644 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5646 dispInfo.item.mask |= LVIF_TEXT;
5647 dispInfo.item.pszText = lpLVItem->pszText;
5648 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5649 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5650 *dispInfo.item.pszText = '\0';
5653 /* If we don't have all the requested info, query the application */
5654 if (dispInfo.item.mask != 0)
5656 dispInfo.item.iItem = lpLVItem->iItem;
5657 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5658 dispInfo.item.lParam = lpItem->lParam;
5659 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5660 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5663 /* we should not store values for subitems */
5664 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5666 /* Now, handle the iImage field */
5667 if (dispInfo.item.mask & LVIF_IMAGE)
5669 lpLVItem->iImage = dispInfo.item.iImage;
5670 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5671 pItemHdr->iImage = dispInfo.item.iImage;
5673 else if (lpLVItem->mask & LVIF_IMAGE)
5675 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5676 lpLVItem->iImage = pItemHdr->iImage;
5677 else
5678 lpLVItem->iImage = 0;
5681 /* The pszText field */
5682 if (dispInfo.item.mask & LVIF_TEXT)
5684 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5685 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5687 lpLVItem->pszText = dispInfo.item.pszText;
5689 else if (lpLVItem->mask & LVIF_TEXT)
5691 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5692 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5695 /* Next is the lParam field */
5696 if (dispInfo.item.mask & LVIF_PARAM)
5698 lpLVItem->lParam = dispInfo.item.lParam;
5699 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5700 lpItem->lParam = dispInfo.item.lParam;
5702 else if (lpLVItem->mask & LVIF_PARAM)
5703 lpLVItem->lParam = lpItem->lParam;
5705 /* if this is a subitem, we're done */
5706 if (isubitem) return TRUE;
5708 /* ... the state field (this one is different due to uCallbackmask) */
5709 if (lpLVItem->mask & LVIF_STATE)
5711 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5712 if (dispInfo.item.mask & LVIF_STATE)
5714 lpLVItem->state &= ~dispInfo.item.stateMask;
5715 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5717 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5719 lpLVItem->state &= ~LVIS_FOCUSED;
5720 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5721 lpLVItem->state |= LVIS_FOCUSED;
5723 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5725 lpLVItem->state &= ~LVIS_SELECTED;
5726 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5727 lpLVItem->state |= LVIS_SELECTED;
5731 /* and last, but not least, the indent field */
5732 if (lpLVItem->mask & LVIF_INDENT)
5733 lpLVItem->iIndent = lpItem->iIndent;
5735 return TRUE;
5738 /***
5739 * DESCRIPTION:
5740 * Retrieves item attributes.
5742 * PARAMETER(S):
5743 * [I] hwnd : window handle
5744 * [IO] lpLVItem : item info
5745 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5746 * if FALSE, then lpLVItem is a LPLVITEMA.
5748 * NOTE:
5749 * This is the external 'GetItem' interface -- it properly copies
5750 * the text in the provided buffer.
5752 * RETURN:
5753 * SUCCESS : TRUE
5754 * FAILURE : FALSE
5756 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5758 LPWSTR pszText;
5759 BOOL bResult;
5761 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5762 return FALSE;
5764 pszText = lpLVItem->pszText;
5765 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5766 if (bResult && lpLVItem->pszText != pszText)
5767 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5768 lpLVItem->pszText = pszText;
5770 return bResult;
5774 /***
5775 * DESCRIPTION:
5776 * Retrieves the position (upper-left) of the listview control item.
5777 * Note that for LVS_ICON style, the upper-left is that of the icon
5778 * and not the bounding box.
5780 * PARAMETER(S):
5781 * [I] infoPtr : valid pointer to the listview structure
5782 * [I] nItem : item index
5783 * [O] lpptPosition : coordinate information
5785 * RETURN:
5786 * SUCCESS : TRUE
5787 * FAILURE : FALSE
5789 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5791 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5792 POINT Origin;
5794 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5796 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5798 LISTVIEW_GetOrigin(infoPtr, &Origin);
5799 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5801 if (uView == LVS_ICON)
5803 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5804 lpptPosition->y += ICON_TOP_PADDING;
5806 lpptPosition->x += Origin.x;
5807 lpptPosition->y += Origin.y;
5809 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5810 return TRUE;
5814 /***
5815 * DESCRIPTION:
5816 * Retrieves the bounding rectangle for a listview control item.
5818 * PARAMETER(S):
5819 * [I] infoPtr : valid pointer to the listview structure
5820 * [I] nItem : item index
5821 * [IO] lprc : bounding rectangle coordinates
5822 * lprc->left specifies the portion of the item for which the bounding
5823 * rectangle will be retrieved.
5825 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5826 * including the icon and label.
5828 * * For LVS_ICON
5829 * * Experiment shows that native control returns:
5830 * * width = min (48, length of text line)
5831 * * .left = position.x - (width - iconsize.cx)/2
5832 * * .right = .left + width
5833 * * height = #lines of text * ntmHeight + icon height + 8
5834 * * .top = position.y - 2
5835 * * .bottom = .top + height
5836 * * separation between items .y = itemSpacing.cy - height
5837 * * .x = itemSpacing.cx - width
5838 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5840 * * For LVS_ICON
5841 * * Experiment shows that native control returns:
5842 * * width = iconSize.cx + 16
5843 * * .left = position.x - (width - iconsize.cx)/2
5844 * * .right = .left + width
5845 * * height = iconSize.cy + 4
5846 * * .top = position.y - 2
5847 * * .bottom = .top + height
5848 * * separation between items .y = itemSpacing.cy - height
5849 * * .x = itemSpacing.cx - width
5850 * LVIR_LABEL Returns the bounding rectangle of the item text.
5852 * * For LVS_ICON
5853 * * Experiment shows that native control returns:
5854 * * width = text length
5855 * * .left = position.x - width/2
5856 * * .right = .left + width
5857 * * height = ntmH * linecount + 2
5858 * * .top = position.y + iconSize.cy + 6
5859 * * .bottom = .top + height
5860 * * separation between items .y = itemSpacing.cy - height
5861 * * .x = itemSpacing.cx - width
5862 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5863 * rectangles, but excludes columns in report view.
5865 * RETURN:
5866 * SUCCESS : TRUE
5867 * FAILURE : FALSE
5869 * NOTES
5870 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5871 * upon whether the window has the focus currently and on whether the item
5872 * is the one with the focus. Ensure that the control's record of which
5873 * item has the focus agrees with the items' records.
5875 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5877 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5878 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5879 BOOL doLabel = TRUE, oversizedBox = FALSE;
5880 POINT Position, Origin;
5881 LVITEMW lvItem;
5883 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5885 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5887 LISTVIEW_GetOrigin(infoPtr, &Origin);
5888 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5890 /* Be smart and try to figure out the minimum we have to do */
5891 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5892 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5893 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5894 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5895 oversizedBox = TRUE;
5897 /* get what we need from the item before hand, so we make
5898 * only one request. This can speed up things, if data
5899 * is stored on the app side */
5900 lvItem.mask = 0;
5901 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5902 if (doLabel) lvItem.mask |= LVIF_TEXT;
5903 lvItem.iItem = nItem;
5904 lvItem.iSubItem = 0;
5905 lvItem.pszText = szDispText;
5906 lvItem.cchTextMax = DISP_TEXT_SIZE;
5907 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5908 /* we got the state already up, simulate it here, to avoid a reget */
5909 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5911 lvItem.mask |= LVIF_STATE;
5912 lvItem.stateMask = LVIS_FOCUSED;
5913 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5916 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5917 lprc->left = LVIR_BOUNDS;
5918 switch(lprc->left)
5920 case LVIR_ICON:
5921 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5922 break;
5924 case LVIR_LABEL:
5925 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5926 break;
5928 case LVIR_BOUNDS:
5929 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5930 break;
5932 case LVIR_SELECTBOUNDS:
5933 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5934 break;
5936 default:
5937 WARN("Unknown value: %d\n", lprc->left);
5938 return FALSE;
5941 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5943 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5945 return TRUE;
5948 /***
5949 * DESCRIPTION:
5950 * Retrieves the spacing between listview control items.
5952 * PARAMETER(S):
5953 * [I] infoPtr : valid pointer to the listview structure
5954 * [IO] lprc : rectangle to receive the output
5955 * on input, lprc->top = nSubItem
5956 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5958 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5959 * not only those of the first column.
5960 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5962 * RETURN:
5963 * TRUE: success
5964 * FALSE: failure
5966 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5968 POINT Position;
5969 LVITEMW lvItem;
5970 INT nColumn;
5972 if (!lprc) return FALSE;
5974 nColumn = lprc->top;
5976 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5977 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5978 if (lprc->top == 0)
5979 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5981 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5983 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5985 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5987 lvItem.mask = 0;
5988 lvItem.iItem = nItem;
5989 lvItem.iSubItem = nColumn;
5991 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5992 switch(lprc->left)
5994 case LVIR_ICON:
5995 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5996 break;
5998 case LVIR_LABEL:
5999 case LVIR_BOUNDS:
6000 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
6001 break;
6003 default:
6004 ERR("Unknown bounds=%d\n", lprc->left);
6005 return FALSE;
6008 OffsetRect(lprc, Position.x, Position.y);
6009 return TRUE;
6013 /***
6014 * DESCRIPTION:
6015 * Retrieves the width of a label.
6017 * PARAMETER(S):
6018 * [I] infoPtr : valid pointer to the listview structure
6020 * RETURN:
6021 * SUCCESS : string width (in pixels)
6022 * FAILURE : zero
6024 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
6026 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6027 LVITEMW lvItem;
6029 TRACE("(nItem=%d)\n", nItem);
6031 lvItem.mask = LVIF_TEXT;
6032 lvItem.iItem = nItem;
6033 lvItem.iSubItem = 0;
6034 lvItem.pszText = szDispText;
6035 lvItem.cchTextMax = DISP_TEXT_SIZE;
6036 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6038 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6041 /***
6042 * DESCRIPTION:
6043 * Retrieves the spacing between listview control items.
6045 * PARAMETER(S):
6046 * [I] infoPtr : valid pointer to the listview structure
6047 * [I] bSmall : flag for small or large icon
6049 * RETURN:
6050 * Horizontal + vertical spacing
6052 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
6054 LONG lResult;
6056 if (!bSmall)
6058 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6060 else
6062 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
6063 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
6064 else
6065 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
6067 return lResult;
6070 /***
6071 * DESCRIPTION:
6072 * Retrieves the state of a listview control item.
6074 * PARAMETER(S):
6075 * [I] infoPtr : valid pointer to the listview structure
6076 * [I] nItem : item index
6077 * [I] uMask : state mask
6079 * RETURN:
6080 * State specified by the mask.
6082 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6084 LVITEMW lvItem;
6086 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6088 lvItem.iItem = nItem;
6089 lvItem.iSubItem = 0;
6090 lvItem.mask = LVIF_STATE;
6091 lvItem.stateMask = uMask;
6092 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6094 return lvItem.state & uMask;
6097 /***
6098 * DESCRIPTION:
6099 * Retrieves the text of a listview control item or subitem.
6101 * PARAMETER(S):
6102 * [I] hwnd : window handle
6103 * [I] nItem : item index
6104 * [IO] lpLVItem : item information
6105 * [I] isW : TRUE if lpLVItem is Unicode
6107 * RETURN:
6108 * SUCCESS : string length
6109 * FAILURE : 0
6111 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6113 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6115 lpLVItem->mask = LVIF_TEXT;
6116 lpLVItem->iItem = nItem;
6117 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6119 return textlenT(lpLVItem->pszText, isW);
6122 /***
6123 * DESCRIPTION:
6124 * Searches for an item based on properties + relationships.
6126 * PARAMETER(S):
6127 * [I] infoPtr : valid pointer to the listview structure
6128 * [I] nItem : item index
6129 * [I] uFlags : relationship flag
6131 * RETURN:
6132 * SUCCESS : item index
6133 * FAILURE : -1
6135 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6137 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6138 UINT uMask = 0;
6139 LVFINDINFOW lvFindInfo;
6140 INT nCountPerColumn;
6141 INT nCountPerRow;
6142 INT i;
6144 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6145 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6147 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6149 if (uFlags & LVNI_CUT)
6150 uMask |= LVIS_CUT;
6152 if (uFlags & LVNI_DROPHILITED)
6153 uMask |= LVIS_DROPHILITED;
6155 if (uFlags & LVNI_FOCUSED)
6156 uMask |= LVIS_FOCUSED;
6158 if (uFlags & LVNI_SELECTED)
6159 uMask |= LVIS_SELECTED;
6161 /* if we're asked for the focused item, that's only one,
6162 * so it's worth optimizing */
6163 if (uFlags & LVNI_FOCUSED)
6165 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6166 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6169 if (uFlags & LVNI_ABOVE)
6171 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6173 while (nItem >= 0)
6175 nItem--;
6176 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6177 return nItem;
6180 else
6182 /* Special case for autoarrange - move 'til the top of a list */
6183 if (is_autoarrange(infoPtr))
6185 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6186 while (nItem - nCountPerRow >= 0)
6188 nItem -= nCountPerRow;
6189 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6190 return nItem;
6192 return -1;
6194 lvFindInfo.flags = LVFI_NEARESTXY;
6195 lvFindInfo.vkDirection = VK_UP;
6196 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6197 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6199 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6200 return nItem;
6204 else if (uFlags & LVNI_BELOW)
6206 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6208 while (nItem < infoPtr->nItemCount)
6210 nItem++;
6211 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6212 return nItem;
6215 else
6217 /* Special case for autoarrange - move 'til the bottom of a list */
6218 if (is_autoarrange(infoPtr))
6220 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6221 while (nItem + nCountPerRow < infoPtr->nItemCount )
6223 nItem += nCountPerRow;
6224 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6225 return nItem;
6227 return -1;
6229 lvFindInfo.flags = LVFI_NEARESTXY;
6230 lvFindInfo.vkDirection = VK_DOWN;
6231 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6232 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6234 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6235 return nItem;
6239 else if (uFlags & LVNI_TOLEFT)
6241 if (uView == LVS_LIST)
6243 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6244 while (nItem - nCountPerColumn >= 0)
6246 nItem -= nCountPerColumn;
6247 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6248 return nItem;
6251 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6253 /* Special case for autoarrange - move 'til the beginning of a row */
6254 if (is_autoarrange(infoPtr))
6256 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6257 while (nItem % nCountPerRow > 0)
6259 nItem --;
6260 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6261 return nItem;
6263 return -1;
6265 lvFindInfo.flags = LVFI_NEARESTXY;
6266 lvFindInfo.vkDirection = VK_LEFT;
6267 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6268 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6270 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6271 return nItem;
6275 else if (uFlags & LVNI_TORIGHT)
6277 if (uView == LVS_LIST)
6279 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6280 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6282 nItem += nCountPerColumn;
6283 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6284 return nItem;
6287 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6289 /* Special case for autoarrange - move 'til the end of a row */
6290 if (is_autoarrange(infoPtr))
6292 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6293 while (nItem % nCountPerRow < nCountPerRow - 1 )
6295 nItem ++;
6296 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6297 return nItem;
6299 return -1;
6301 lvFindInfo.flags = LVFI_NEARESTXY;
6302 lvFindInfo.vkDirection = VK_RIGHT;
6303 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6304 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6306 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6307 return nItem;
6311 else
6313 nItem++;
6315 /* search by index */
6316 for (i = nItem; i < infoPtr->nItemCount; i++)
6318 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6319 return i;
6323 return -1;
6326 /* LISTVIEW_GetNumberOfWorkAreas */
6328 /***
6329 * DESCRIPTION:
6330 * Retrieves the origin coordinates when in icon or small icon display mode.
6332 * PARAMETER(S):
6333 * [I] infoPtr : valid pointer to the listview structure
6334 * [O] lpptOrigin : coordinate information
6336 * RETURN:
6337 * None.
6339 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6341 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6342 INT nHorzPos = 0, nVertPos = 0;
6343 SCROLLINFO scrollInfo;
6345 scrollInfo.cbSize = sizeof(SCROLLINFO);
6346 scrollInfo.fMask = SIF_POS;
6348 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6349 nHorzPos = scrollInfo.nPos;
6350 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6351 nVertPos = scrollInfo.nPos;
6353 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6355 lpptOrigin->x = infoPtr->rcList.left;
6356 lpptOrigin->y = infoPtr->rcList.top;
6357 if (uView == LVS_LIST)
6358 nHorzPos *= infoPtr->nItemWidth;
6359 else if (uView == LVS_REPORT)
6360 nVertPos *= infoPtr->nItemHeight;
6362 lpptOrigin->x -= nHorzPos;
6363 lpptOrigin->y -= nVertPos;
6365 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6368 /***
6369 * DESCRIPTION:
6370 * Retrieves the width of a string.
6372 * PARAMETER(S):
6373 * [I] hwnd : window handle
6374 * [I] lpszText : text string to process
6375 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6377 * RETURN:
6378 * SUCCESS : string width (in pixels)
6379 * FAILURE : zero
6381 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6383 SIZE stringSize;
6385 stringSize.cx = 0;
6386 if (is_textT(lpszText, isW))
6388 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6389 HDC hdc = GetDC(infoPtr->hwndSelf);
6390 HFONT hOldFont = SelectObject(hdc, hFont);
6392 if (isW)
6393 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6394 else
6395 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6396 SelectObject(hdc, hOldFont);
6397 ReleaseDC(infoPtr->hwndSelf, hdc);
6399 return stringSize.cx;
6402 /***
6403 * DESCRIPTION:
6404 * Determines which listview item is located at the specified position.
6406 * PARAMETER(S):
6407 * [I] infoPtr : valid pointer to the listview structure
6408 * [IO] lpht : hit test information
6409 * [I] subitem : fill out iSubItem.
6410 * [I] select : return the index only if the hit selects the item
6412 * NOTE:
6413 * (mm 20001022): We must not allow iSubItem to be touched, for
6414 * an app might pass only a structure with space up to iItem!
6415 * (MS Office 97 does that for instance in the file open dialog)
6417 * RETURN:
6418 * SUCCESS : item index
6419 * FAILURE : -1
6421 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6423 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6424 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6425 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6426 POINT Origin, Position, opt;
6427 LVITEMW lvItem;
6428 ITERATOR i;
6429 INT iItem;
6431 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6433 lpht->flags = 0;
6434 lpht->iItem = -1;
6435 if (subitem) lpht->iSubItem = 0;
6437 if (infoPtr->rcList.left > lpht->pt.x)
6438 lpht->flags |= LVHT_TOLEFT;
6439 else if (infoPtr->rcList.right < lpht->pt.x)
6440 lpht->flags |= LVHT_TORIGHT;
6442 if (infoPtr->rcList.top > lpht->pt.y)
6443 lpht->flags |= LVHT_ABOVE;
6444 else if (infoPtr->rcList.bottom < lpht->pt.y)
6445 lpht->flags |= LVHT_BELOW;
6447 TRACE("lpht->flags=0x%x\n", lpht->flags);
6448 if (lpht->flags) return -1;
6450 lpht->flags |= LVHT_NOWHERE;
6452 LISTVIEW_GetOrigin(infoPtr, &Origin);
6454 /* first deal with the large items */
6455 rcSearch.left = lpht->pt.x;
6456 rcSearch.top = lpht->pt.y;
6457 rcSearch.right = rcSearch.left + 1;
6458 rcSearch.bottom = rcSearch.top + 1;
6460 iterator_frameditems(&i, infoPtr, &rcSearch);
6461 iterator_next(&i); /* go to first item in the sequence */
6462 iItem = i.nItem;
6463 iterator_destroy(&i);
6465 TRACE("lpht->iItem=%d\n", iItem);
6466 if (iItem == -1) return -1;
6468 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6469 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6470 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6471 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6472 lvItem.iItem = iItem;
6473 lvItem.iSubItem = 0;
6474 lvItem.pszText = szDispText;
6475 lvItem.cchTextMax = DISP_TEXT_SIZE;
6476 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6477 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6479 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6480 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6481 opt.x = lpht->pt.x - Position.x - Origin.x;
6482 opt.y = lpht->pt.y - Position.y - Origin.y;
6484 if (uView == LVS_REPORT)
6485 rcBounds = rcBox;
6486 else
6488 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6489 UnionRect(&rcBounds, &rcBounds, &rcState);
6491 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6492 if (!PtInRect(&rcBounds, opt)) return -1;
6494 if (PtInRect(&rcIcon, opt))
6495 lpht->flags |= LVHT_ONITEMICON;
6496 else if (PtInRect(&rcLabel, opt))
6497 lpht->flags |= LVHT_ONITEMLABEL;
6498 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6499 lpht->flags |= LVHT_ONITEMSTATEICON;
6500 if (lpht->flags & LVHT_ONITEM)
6501 lpht->flags &= ~LVHT_NOWHERE;
6503 TRACE("lpht->flags=0x%x\n", lpht->flags);
6504 if (uView == LVS_REPORT && subitem)
6506 INT j;
6508 rcBounds.right = rcBounds.left;
6509 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6511 rcBounds.left = rcBounds.right;
6512 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6513 if (PtInRect(&rcBounds, opt))
6515 lpht->iSubItem = j;
6516 break;
6521 if (select && !(uView == LVS_REPORT &&
6522 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6523 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6525 if (uView == LVS_REPORT)
6527 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6528 UnionRect(&rcBounds, &rcBounds, &rcState);
6530 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6532 return lpht->iItem = iItem;
6536 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6537 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6538 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6539 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6540 their own sort proc. when sending LVM_SORTITEMS.
6542 /* Platform SDK:
6543 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6545 LVS_SORTXXX must be specified,
6546 LVS_OWNERDRAW is not set,
6547 <item>.pszText is not LPSTR_TEXTCALLBACK.
6549 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6550 are sorted based on item text..."
6552 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6554 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
6555 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
6556 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6558 /* if we're sorting descending, negate the return value */
6559 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6562 /***
6563 * DESCRIPTION:
6564 * Inserts a new item in the listview control.
6566 * PARAMETER(S):
6567 * [I] infoPtr : valid pointer to the listview structure
6568 * [I] lpLVItem : item information
6569 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6571 * RETURN:
6572 * SUCCESS : new item index
6573 * FAILURE : -1
6575 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6577 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6578 INT nItem;
6579 HDPA hdpaSubItems;
6580 NMLISTVIEW nmlv;
6581 ITEM_INFO *lpItem;
6582 BOOL is_sorted, has_changed;
6583 LVITEMW item;
6584 HWND hwndSelf = infoPtr->hwndSelf;
6586 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6588 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6590 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6591 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6593 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6595 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6597 /* insert item in listview control data structure */
6598 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6599 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6601 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6602 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6604 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6606 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6607 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6608 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6609 if (nItem == -1) goto fail;
6610 infoPtr->nItemCount++;
6612 /* shift indices first so they don't get tangled */
6613 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6615 /* set the item attributes */
6616 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6618 /* full size structure expected - _WIN32IE >= 0x560 */
6619 item = *lpLVItem;
6621 else if (lpLVItem->mask & LVIF_INDENT)
6623 /* indent member expected - _WIN32IE >= 0x300 */
6624 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6626 else
6628 /* minimal structure expected */
6629 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6631 item.iItem = nItem;
6632 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6634 item.mask |= LVIF_STATE;
6635 item.stateMask |= LVIS_STATEIMAGEMASK;
6636 item.state &= ~LVIS_STATEIMAGEMASK;
6637 item.state |= INDEXTOSTATEIMAGEMASK(1);
6639 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6641 /* if we're sorted, sort the list, and update the index */
6642 if (is_sorted)
6644 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6645 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6646 assert(nItem != -1);
6649 /* make room for the position, if we are in the right mode */
6650 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6652 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6653 goto undo;
6654 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6656 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6657 goto undo;
6661 /* send LVN_INSERTITEM notification */
6662 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6663 nmlv.iItem = nItem;
6664 nmlv.lParam = lpItem->lParam;
6665 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6666 if (!IsWindow(hwndSelf))
6667 return -1;
6669 /* align items (set position of each item) */
6670 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6672 POINT pt;
6674 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6675 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6676 else
6677 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6679 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6682 /* now is the invalidation fun */
6683 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6684 return nItem;
6686 undo:
6687 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6688 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6689 infoPtr->nItemCount--;
6690 fail:
6691 DPA_DeletePtr(hdpaSubItems, 0);
6692 DPA_Destroy (hdpaSubItems);
6693 Free (lpItem);
6694 return -1;
6697 /***
6698 * DESCRIPTION:
6699 * Redraws a range of items.
6701 * PARAMETER(S):
6702 * [I] infoPtr : valid pointer to the listview structure
6703 * [I] nFirst : first item
6704 * [I] nLast : last item
6706 * RETURN:
6707 * SUCCESS : TRUE
6708 * FAILURE : FALSE
6710 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6712 INT i;
6714 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6715 max(nFirst, nLast) >= infoPtr->nItemCount)
6716 return FALSE;
6718 for (i = nFirst; i <= nLast; i++)
6719 LISTVIEW_InvalidateItem(infoPtr, i);
6721 return TRUE;
6724 /***
6725 * DESCRIPTION:
6726 * Scroll the content of a listview.
6728 * PARAMETER(S):
6729 * [I] infoPtr : valid pointer to the listview structure
6730 * [I] dx : horizontal scroll amount in pixels
6731 * [I] dy : vertical scroll amount in pixels
6733 * RETURN:
6734 * SUCCESS : TRUE
6735 * FAILURE : FALSE
6737 * COMMENTS:
6738 * If the control is in report mode (LVS_REPORT) the control can
6739 * be scrolled only in line increments. "dy" will be rounded to the
6740 * nearest number of pixels that are a whole line. Ex: if line height
6741 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6742 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6744 * For: (per experimentation with native control and CSpy ListView)
6745 * LVS_ICON dy=1 = 1 pixel (vertical only)
6746 * dx ignored
6747 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6748 * dx ignored
6749 * LVS_LIST dx=1 = 1 column (horizontal only)
6750 * but will only scroll 1 column per message
6751 * no matter what the value.
6752 * dy must be 0 or FALSE returned.
6753 * LVS_REPORT dx=1 = 1 pixel
6754 * dy= see above
6757 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6759 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6760 case LVS_REPORT:
6761 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6762 dy /= infoPtr->nItemHeight;
6763 break;
6764 case LVS_LIST:
6765 if (dy != 0) return FALSE;
6766 break;
6767 default: /* icon */
6768 dx = 0;
6769 break;
6772 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6773 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6775 return TRUE;
6778 /***
6779 * DESCRIPTION:
6780 * Sets the background color.
6782 * PARAMETER(S):
6783 * [I] infoPtr : valid pointer to the listview structure
6784 * [I] clrBk : background color
6786 * RETURN:
6787 * SUCCESS : TRUE
6788 * FAILURE : FALSE
6790 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6792 TRACE("(clrBk=%x)\n", clrBk);
6794 if(infoPtr->clrBk != clrBk) {
6795 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6796 infoPtr->clrBk = clrBk;
6797 if (clrBk == CLR_NONE)
6798 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6799 else
6800 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6801 LISTVIEW_InvalidateList(infoPtr);
6804 return TRUE;
6807 /* LISTVIEW_SetBkImage */
6809 /*** Helper for {Insert,Set}ColumnT *only* */
6810 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6811 const LVCOLUMNW *lpColumn, BOOL isW)
6813 if (lpColumn->mask & LVCF_FMT)
6815 /* format member is valid */
6816 lphdi->mask |= HDI_FORMAT;
6818 /* set text alignment (leftmost column must be left-aligned) */
6819 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6820 lphdi->fmt |= HDF_LEFT;
6821 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6822 lphdi->fmt |= HDF_RIGHT;
6823 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6824 lphdi->fmt |= HDF_CENTER;
6826 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6827 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6829 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6831 lphdi->fmt |= HDF_IMAGE;
6832 lphdi->iImage = I_IMAGECALLBACK;
6836 if (lpColumn->mask & LVCF_WIDTH)
6838 lphdi->mask |= HDI_WIDTH;
6839 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6841 /* make it fill the remainder of the controls width */
6842 RECT rcHeader;
6843 INT item_index;
6845 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6847 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6848 lphdi->cxy += rcHeader.right - rcHeader.left;
6851 /* retrieve the layout of the header */
6852 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6853 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6855 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6857 else
6858 lphdi->cxy = lpColumn->cx;
6861 if (lpColumn->mask & LVCF_TEXT)
6863 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6864 lphdi->fmt |= HDF_STRING;
6865 lphdi->pszText = lpColumn->pszText;
6866 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6869 if (lpColumn->mask & LVCF_IMAGE)
6871 lphdi->mask |= HDI_IMAGE;
6872 lphdi->iImage = lpColumn->iImage;
6875 if (lpColumn->mask & LVCF_ORDER)
6877 lphdi->mask |= HDI_ORDER;
6878 lphdi->iOrder = lpColumn->iOrder;
6883 /***
6884 * DESCRIPTION:
6885 * Inserts a new column.
6887 * PARAMETER(S):
6888 * [I] infoPtr : valid pointer to the listview structure
6889 * [I] nColumn : column index
6890 * [I] lpColumn : column information
6891 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6893 * RETURN:
6894 * SUCCESS : new column index
6895 * FAILURE : -1
6897 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6898 const LVCOLUMNW *lpColumn, BOOL isW)
6900 COLUMN_INFO *lpColumnInfo;
6901 INT nNewColumn;
6902 HDITEMW hdi;
6903 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6905 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6907 if (!lpColumn || nColumn < 0) return -1;
6908 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6910 ZeroMemory(&hdi, sizeof(HDITEMW));
6911 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6914 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6915 * (can be seen in SPY) otherwise column never gets added.
6917 if (!(lpColumn->mask & LVCF_WIDTH)) {
6918 hdi.mask |= HDI_WIDTH;
6919 hdi.cxy = 10;
6923 * when the iSubItem is available Windows copies it to the header lParam. It seems
6924 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6926 if (lpColumn->mask & LVCF_SUBITEM)
6928 hdi.mask |= HDI_LPARAM;
6929 hdi.lParam = lpColumn->iSubItem;
6932 /* create header if not present */
6933 LISTVIEW_CreateHeader(infoPtr);
6934 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
6935 (LVS_REPORT == uView) && (WS_VISIBLE & infoPtr->dwStyle))
6937 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
6940 /* insert item in header control */
6941 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6942 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6943 (WPARAM)nColumn, (LPARAM)&hdi);
6944 if (nNewColumn == -1) return -1;
6945 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6947 /* create our own column info */
6948 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6949 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6951 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6952 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6954 /* now we have to actually adjust the data */
6955 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6957 SUBITEM_INFO *lpSubItem;
6958 HDPA hdpaSubItems;
6959 INT nItem, i;
6961 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6963 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
6964 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6966 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
6967 if (lpSubItem->iSubItem >= nNewColumn)
6968 lpSubItem->iSubItem++;
6973 /* make space for the new column */
6974 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6975 LISTVIEW_UpdateItemSize(infoPtr);
6977 return nNewColumn;
6979 fail:
6980 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6981 if (lpColumnInfo)
6983 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6984 Free(lpColumnInfo);
6986 return -1;
6989 /***
6990 * DESCRIPTION:
6991 * Sets the attributes of a header item.
6993 * PARAMETER(S):
6994 * [I] infoPtr : valid pointer to the listview structure
6995 * [I] nColumn : column index
6996 * [I] lpColumn : column attributes
6997 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6999 * RETURN:
7000 * SUCCESS : TRUE
7001 * FAILURE : FALSE
7003 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
7004 const LVCOLUMNW *lpColumn, BOOL isW)
7006 HDITEMW hdi, hdiget;
7007 BOOL bResult;
7009 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
7011 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7013 ZeroMemory(&hdi, sizeof(HDITEMW));
7014 if (lpColumn->mask & LVCF_FMT)
7016 hdi.mask |= HDI_FORMAT;
7017 hdiget.mask = HDI_FORMAT;
7018 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
7019 hdi.fmt = hdiget.fmt & HDF_STRING;
7021 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
7023 /* set header item attributes */
7024 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
7025 if (!bResult) return FALSE;
7027 if (lpColumn->mask & LVCF_FMT)
7029 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
7030 int oldFmt = lpColumnInfo->fmt;
7032 lpColumnInfo->fmt = lpColumn->fmt;
7033 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
7035 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7036 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
7040 return TRUE;
7043 /***
7044 * DESCRIPTION:
7045 * Sets the column order array
7047 * PARAMETERS:
7048 * [I] infoPtr : valid pointer to the listview structure
7049 * [I] iCount : number of elements in column order array
7050 * [I] lpiArray : pointer to column order array
7052 * RETURN:
7053 * SUCCESS : TRUE
7054 * FAILURE : FALSE
7056 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
7058 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
7060 if (!lpiArray)
7061 return FALSE;
7063 return TRUE;
7067 /***
7068 * DESCRIPTION:
7069 * Sets the width of a column
7071 * PARAMETERS:
7072 * [I] infoPtr : valid pointer to the listview structure
7073 * [I] nColumn : column index
7074 * [I] cx : column width
7076 * RETURN:
7077 * SUCCESS : TRUE
7078 * FAILURE : FALSE
7080 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
7082 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7083 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
7084 INT max_cx = 0;
7085 HDITEMW hdi;
7087 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7089 /* set column width only if in report or list mode */
7090 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7092 /* take care of invalid cx values */
7093 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7094 else if (uView == LVS_LIST && cx < 1) return FALSE;
7096 /* resize all columns if in LVS_LIST mode */
7097 if(uView == LVS_LIST)
7099 infoPtr->nItemWidth = cx;
7100 LISTVIEW_InvalidateList(infoPtr);
7101 return TRUE;
7104 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7106 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7108 INT nLabelWidth;
7109 LVITEMW lvItem;
7111 lvItem.mask = LVIF_TEXT;
7112 lvItem.iItem = 0;
7113 lvItem.iSubItem = nColumn;
7114 lvItem.pszText = szDispText;
7115 lvItem.cchTextMax = DISP_TEXT_SIZE;
7116 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7118 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7119 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7120 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7122 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7123 max_cx += infoPtr->iconSize.cx;
7124 max_cx += TRAILING_LABEL_PADDING;
7127 /* autosize based on listview items width */
7128 if(cx == LVSCW_AUTOSIZE)
7129 cx = max_cx;
7130 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7132 /* if iCol is the last column make it fill the remainder of the controls width */
7133 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7135 RECT rcHeader;
7136 POINT Origin;
7138 LISTVIEW_GetOrigin(infoPtr, &Origin);
7139 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7141 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7143 else
7145 /* Despite what the MS docs say, if this is not the last
7146 column, then MS resizes the column to the width of the
7147 largest text string in the column, including headers
7148 and items. This is different from LVSCW_AUTOSIZE in that
7149 LVSCW_AUTOSIZE ignores the header string length. */
7150 cx = 0;
7152 /* retrieve header text */
7153 hdi.mask = HDI_TEXT;
7154 hdi.cchTextMax = DISP_TEXT_SIZE;
7155 hdi.pszText = szDispText;
7156 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7158 HDC hdc = GetDC(infoPtr->hwndSelf);
7159 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7160 SIZE size;
7162 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7163 cx = size.cx + TRAILING_HEADER_PADDING;
7164 /* FIXME: Take into account the header image, if one is present */
7165 SelectObject(hdc, old_font);
7166 ReleaseDC(infoPtr->hwndSelf, hdc);
7168 cx = max (cx, max_cx);
7172 if (cx < 0) return FALSE;
7174 /* call header to update the column change */
7175 hdi.mask = HDI_WIDTH;
7176 hdi.cxy = cx;
7177 TRACE("hdi.cxy=%d\n", hdi.cxy);
7178 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7181 /***
7182 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7185 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7187 HDC hdc_wnd, hdc;
7188 HBITMAP hbm_im, hbm_mask, hbm_orig;
7189 RECT rc;
7190 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7191 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7192 HIMAGELIST himl;
7194 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7195 ILC_COLOR | ILC_MASK, 2, 2);
7196 hdc_wnd = GetDC(infoPtr->hwndSelf);
7197 hdc = CreateCompatibleDC(hdc_wnd);
7198 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7199 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7200 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7202 rc.left = rc.top = 0;
7203 rc.right = GetSystemMetrics(SM_CXSMICON);
7204 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7206 hbm_orig = SelectObject(hdc, hbm_mask);
7207 FillRect(hdc, &rc, hbr_white);
7208 InflateRect(&rc, -2, -2);
7209 FillRect(hdc, &rc, hbr_black);
7211 SelectObject(hdc, hbm_im);
7212 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7213 SelectObject(hdc, hbm_orig);
7214 ImageList_Add(himl, hbm_im, hbm_mask);
7216 SelectObject(hdc, hbm_im);
7217 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7218 SelectObject(hdc, hbm_orig);
7219 ImageList_Add(himl, hbm_im, hbm_mask);
7221 DeleteObject(hbm_mask);
7222 DeleteObject(hbm_im);
7223 DeleteDC(hdc);
7225 return himl;
7228 /***
7229 * DESCRIPTION:
7230 * Sets the extended listview style.
7232 * PARAMETERS:
7233 * [I] infoPtr : valid pointer to the listview structure
7234 * [I] dwMask : mask
7235 * [I] dwStyle : style
7237 * RETURN:
7238 * SUCCESS : previous style
7239 * FAILURE : 0
7241 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7243 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7245 /* set new style */
7246 if (dwMask)
7247 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7248 else
7249 infoPtr->dwLvExStyle = dwExStyle;
7251 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7253 HIMAGELIST himl = 0;
7254 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7256 LVITEMW item;
7257 item.mask = LVIF_STATE;
7258 item.stateMask = LVIS_STATEIMAGEMASK;
7259 item.state = INDEXTOSTATEIMAGEMASK(1);
7260 LISTVIEW_SetItemState(infoPtr, -1, &item);
7262 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7264 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7267 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7269 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7270 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7271 dwStyle |= HDS_DRAGDROP;
7272 else
7273 dwStyle &= ~HDS_DRAGDROP;
7274 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7277 /* GRIDLINES adds decoration at top so changes sizes */
7278 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7280 LISTVIEW_UpdateSize(infoPtr);
7284 LISTVIEW_InvalidateList(infoPtr);
7285 return dwOldExStyle;
7288 /***
7289 * DESCRIPTION:
7290 * Sets the new hot cursor used during hot tracking and hover selection.
7292 * PARAMETER(S):
7293 * [I] infoPtr : valid pointer to the listview structure
7294 * [I] hCursor : the new hot cursor handle
7296 * RETURN:
7297 * Returns the previous hot cursor
7299 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7301 HCURSOR oldCursor = infoPtr->hHotCursor;
7303 infoPtr->hHotCursor = hCursor;
7305 return oldCursor;
7309 /***
7310 * DESCRIPTION:
7311 * Sets the hot item index.
7313 * PARAMETERS:
7314 * [I] infoPtr : valid pointer to the listview structure
7315 * [I] iIndex : index
7317 * RETURN:
7318 * SUCCESS : previous hot item index
7319 * FAILURE : -1 (no hot item)
7321 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7323 INT iOldIndex = infoPtr->nHotItem;
7325 infoPtr->nHotItem = iIndex;
7327 return iOldIndex;
7331 /***
7332 * DESCRIPTION:
7333 * Sets the amount of time the cursor must hover over an item before it is selected.
7335 * PARAMETER(S):
7336 * [I] infoPtr : valid pointer to the listview structure
7337 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7339 * RETURN:
7340 * Returns the previous hover time
7342 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7344 DWORD oldHoverTime = infoPtr->dwHoverTime;
7346 infoPtr->dwHoverTime = dwHoverTime;
7348 return oldHoverTime;
7351 /***
7352 * DESCRIPTION:
7353 * Sets spacing for icons of LVS_ICON style.
7355 * PARAMETER(S):
7356 * [I] infoPtr : valid pointer to the listview structure
7357 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7358 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7360 * RETURN:
7361 * MAKELONG(oldcx, oldcy)
7363 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7365 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7366 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7368 TRACE("requested=(%d,%d)\n", cx, cy);
7370 /* this is supported only for LVS_ICON style */
7371 if (uView != LVS_ICON) return oldspacing;
7373 /* set to defaults, if instructed to */
7374 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7375 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7377 /* if 0 then compute width
7378 * FIXME: Should scan each item and determine max width of
7379 * icon or label, then make that the width */
7380 if (cx == 0)
7381 cx = infoPtr->iconSpacing.cx;
7383 /* if 0 then compute height */
7384 if (cy == 0)
7385 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7386 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7389 infoPtr->iconSpacing.cx = cx;
7390 infoPtr->iconSpacing.cy = cy;
7392 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7393 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7394 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7395 infoPtr->ntmHeight);
7397 /* these depend on the iconSpacing */
7398 LISTVIEW_UpdateItemSize(infoPtr);
7400 return oldspacing;
7403 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7405 INT cx, cy;
7407 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7409 size->cx = cx;
7410 size->cy = cy;
7412 else
7414 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7415 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7419 /***
7420 * DESCRIPTION:
7421 * Sets image lists.
7423 * PARAMETER(S):
7424 * [I] infoPtr : valid pointer to the listview structure
7425 * [I] nType : image list type
7426 * [I] himl : image list handle
7428 * RETURN:
7429 * SUCCESS : old image list
7430 * FAILURE : NULL
7432 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7434 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7435 INT oldHeight = infoPtr->nItemHeight;
7436 HIMAGELIST himlOld = 0;
7438 TRACE("(nType=%d, himl=%p\n", nType, himl);
7440 switch (nType)
7442 case LVSIL_NORMAL:
7443 himlOld = infoPtr->himlNormal;
7444 infoPtr->himlNormal = himl;
7445 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7446 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7447 break;
7449 case LVSIL_SMALL:
7450 himlOld = infoPtr->himlSmall;
7451 infoPtr->himlSmall = himl;
7452 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7453 break;
7455 case LVSIL_STATE:
7456 himlOld = infoPtr->himlState;
7457 infoPtr->himlState = himl;
7458 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7459 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7460 break;
7462 default:
7463 ERR("Unknown icon type=%d\n", nType);
7464 return NULL;
7467 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7468 if (infoPtr->nItemHeight != oldHeight)
7469 LISTVIEW_UpdateScroll(infoPtr);
7471 return himlOld;
7474 /***
7475 * DESCRIPTION:
7476 * Preallocates memory (does *not* set the actual count of items !)
7478 * PARAMETER(S):
7479 * [I] infoPtr : valid pointer to the listview structure
7480 * [I] nItems : item count (projected number of items to allocate)
7481 * [I] dwFlags : update flags
7483 * RETURN:
7484 * SUCCESS : TRUE
7485 * FAILURE : FALSE
7487 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7489 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7491 if (infoPtr->dwStyle & LVS_OWNERDATA)
7493 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7494 INT nOldCount = infoPtr->nItemCount;
7496 if (nItems < nOldCount)
7498 RANGE range = { nItems, nOldCount };
7499 ranges_del(infoPtr->selectionRanges, range);
7500 if (infoPtr->nFocusedItem >= nItems)
7502 LISTVIEW_SetItemFocus(infoPtr, -1);
7503 SetRectEmpty(&infoPtr->rcFocus);
7507 infoPtr->nItemCount = nItems;
7508 LISTVIEW_UpdateScroll(infoPtr);
7510 /* the flags are valid only in ownerdata report and list modes */
7511 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7513 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7514 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7516 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7517 LISTVIEW_InvalidateList(infoPtr);
7518 else
7520 INT nFrom, nTo;
7521 POINT Origin;
7522 RECT rcErase;
7524 LISTVIEW_GetOrigin(infoPtr, &Origin);
7525 nFrom = min(nOldCount, nItems);
7526 nTo = max(nOldCount, nItems);
7528 if (uView == LVS_REPORT)
7530 rcErase.left = 0;
7531 rcErase.top = nFrom * infoPtr->nItemHeight;
7532 rcErase.right = infoPtr->nItemWidth;
7533 rcErase.bottom = nTo * infoPtr->nItemHeight;
7534 OffsetRect(&rcErase, Origin.x, Origin.y);
7535 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7536 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7538 else /* LVS_LIST */
7540 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7542 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7543 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7544 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7545 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7546 OffsetRect(&rcErase, Origin.x, Origin.y);
7547 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7548 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7550 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7551 rcErase.top = 0;
7552 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7553 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7554 OffsetRect(&rcErase, Origin.x, Origin.y);
7555 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7556 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7560 else
7562 /* According to MSDN for non-LVS_OWNERDATA this is just
7563 * a performance issue. The control allocates its internal
7564 * data structures for the number of items specified. It
7565 * cuts down on the number of memory allocations. Therefore
7566 * we will just issue a WARN here
7568 WARN("for non-ownerdata performance option not implemented.\n");
7571 return TRUE;
7574 /***
7575 * DESCRIPTION:
7576 * Sets the position of an item.
7578 * PARAMETER(S):
7579 * [I] infoPtr : valid pointer to the listview structure
7580 * [I] nItem : item index
7581 * [I] pt : coordinate
7583 * RETURN:
7584 * SUCCESS : TRUE
7585 * FAILURE : FALSE
7587 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7589 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7590 POINT Origin;
7592 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7594 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7595 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7597 LISTVIEW_GetOrigin(infoPtr, &Origin);
7599 /* This point value seems to be an undocumented feature.
7600 * The best guess is that it means either at the origin,
7601 * or at true beginning of the list. I will assume the origin. */
7602 if ((pt.x == -1) && (pt.y == -1))
7603 pt = Origin;
7605 if (uView == LVS_ICON)
7607 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7608 pt.y -= ICON_TOP_PADDING;
7610 pt.x -= Origin.x;
7611 pt.y -= Origin.y;
7613 infoPtr->bAutoarrange = FALSE;
7615 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7618 /***
7619 * DESCRIPTION:
7620 * Sets the state of one or many items.
7622 * PARAMETER(S):
7623 * [I] infoPtr : valid pointer to the listview structure
7624 * [I] nItem : item index
7625 * [I] lpLVItem : item or subitem info
7627 * RETURN:
7628 * SUCCESS : TRUE
7629 * FAILURE : FALSE
7631 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7633 BOOL bResult = TRUE;
7634 LVITEMW lvItem;
7636 lvItem.iItem = nItem;
7637 lvItem.iSubItem = 0;
7638 lvItem.mask = LVIF_STATE;
7639 lvItem.state = lpLVItem->state;
7640 lvItem.stateMask = lpLVItem->stateMask;
7641 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7643 if (nItem == -1)
7645 /* apply to all items */
7646 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7647 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7649 else
7650 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7653 * Update selection mark
7655 * Investigation on windows 2k showed that selection mark was updated
7656 * whenever a new selection was made, but if the selected item was
7657 * unselected it was not updated.
7659 * we are probably still not 100% accurate, but this at least sets the
7660 * proper selection mark when it is needed
7663 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7664 (infoPtr->nSelectionMark == -1))
7666 int i;
7667 for (i = 0; i < infoPtr->nItemCount; i++)
7669 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7671 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7673 infoPtr->nSelectionMark = i;
7674 break;
7677 else if (ranges_contain(infoPtr->selectionRanges, i))
7679 infoPtr->nSelectionMark = i;
7680 break;
7685 return bResult;
7688 /***
7689 * DESCRIPTION:
7690 * Sets the text of an item or subitem.
7692 * PARAMETER(S):
7693 * [I] hwnd : window handle
7694 * [I] nItem : item index
7695 * [I] lpLVItem : item or subitem info
7696 * [I] isW : TRUE if input is Unicode
7698 * RETURN:
7699 * SUCCESS : TRUE
7700 * FAILURE : FALSE
7702 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7704 LVITEMW lvItem;
7706 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7708 lvItem.iItem = nItem;
7709 lvItem.iSubItem = lpLVItem->iSubItem;
7710 lvItem.mask = LVIF_TEXT;
7711 lvItem.pszText = lpLVItem->pszText;
7712 lvItem.cchTextMax = lpLVItem->cchTextMax;
7714 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7716 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7719 /***
7720 * DESCRIPTION:
7721 * Set item index that marks the start of a multiple selection.
7723 * PARAMETER(S):
7724 * [I] infoPtr : valid pointer to the listview structure
7725 * [I] nIndex : index
7727 * RETURN:
7728 * Index number or -1 if there is no selection mark.
7730 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7732 INT nOldIndex = infoPtr->nSelectionMark;
7734 TRACE("(nIndex=%d)\n", nIndex);
7736 infoPtr->nSelectionMark = nIndex;
7738 return nOldIndex;
7741 /***
7742 * DESCRIPTION:
7743 * Sets the text background color.
7745 * PARAMETER(S):
7746 * [I] infoPtr : valid pointer to the listview structure
7747 * [I] clrTextBk : text background color
7749 * RETURN:
7750 * SUCCESS : TRUE
7751 * FAILURE : FALSE
7753 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7755 TRACE("(clrTextBk=%x)\n", clrTextBk);
7757 if (infoPtr->clrTextBk != clrTextBk)
7759 infoPtr->clrTextBk = clrTextBk;
7760 LISTVIEW_InvalidateList(infoPtr);
7763 return TRUE;
7766 /***
7767 * DESCRIPTION:
7768 * Sets the text foreground color.
7770 * PARAMETER(S):
7771 * [I] infoPtr : valid pointer to the listview structure
7772 * [I] clrText : text color
7774 * RETURN:
7775 * SUCCESS : TRUE
7776 * FAILURE : FALSE
7778 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7780 TRACE("(clrText=%x)\n", clrText);
7782 if (infoPtr->clrText != clrText)
7784 infoPtr->clrText = clrText;
7785 LISTVIEW_InvalidateList(infoPtr);
7788 return TRUE;
7791 /***
7792 * DESCRIPTION:
7793 * Determines which listview item is located at the specified position.
7795 * PARAMETER(S):
7796 * [I] infoPtr : valid pointer to the listview structure
7797 * [I] hwndNewToolTip : handle to new ToolTip
7799 * RETURN:
7800 * old tool tip
7802 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7804 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7805 infoPtr->hwndToolTip = hwndNewToolTip;
7806 return hwndOldToolTip;
7810 * DESCRIPTION:
7811 * sets the Unicode character format flag for the control
7812 * PARAMETER(S):
7813 * [I] infoPtr :valid pointer to the listview structure
7814 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7816 * RETURN:
7817 * Old Unicode Format
7819 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7821 BOOL rc = infoPtr->notifyFormat;
7822 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7823 return rc;
7826 /* LISTVIEW_SetWorkAreas */
7828 /***
7829 * DESCRIPTION:
7830 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
7832 * PARAMETER(S):
7833 * [I] first : pointer to first ITEM_INFO to compare
7834 * [I] second : pointer to second ITEM_INFO to compare
7835 * [I] lParam : HWND of control
7837 * RETURN:
7838 * if first comes before second : negative
7839 * if first comes after second : positive
7840 * if first and second are equivalent : zero
7842 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7844 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7845 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
7846 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
7848 /* Forward the call to the client defined callback */
7849 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7852 /***
7853 * DESCRIPTION:
7854 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
7856 * PARAMETER(S):
7857 * [I] first : pointer to first ITEM_INFO to compare
7858 * [I] second : pointer to second ITEM_INFO to compare
7859 * [I] lParam : HWND of control
7861 * RETURN:
7862 * if first comes before second : negative
7863 * if first comes after second : positive
7864 * if first and second are equivalent : zero
7866 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
7868 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7869 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
7870 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
7872 /* Forward the call to the client defined callback */
7873 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
7876 /***
7877 * DESCRIPTION:
7878 * Sorts the listview items.
7880 * PARAMETER(S):
7881 * [I] infoPtr : valid pointer to the listview structure
7882 * [I] pfnCompare : application-defined value
7883 * [I] lParamSort : pointer to comparison callback
7884 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
7886 * RETURN:
7887 * SUCCESS : TRUE
7888 * FAILURE : FALSE
7890 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
7891 LPARAM lParamSort, BOOL IsEx)
7893 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7894 HDPA hdpaSubItems;
7895 ITEM_INFO *lpItem;
7896 LPVOID selectionMarkItem = NULL;
7897 LPVOID focusedItem = NULL;
7898 int i;
7900 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7902 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7904 if (!pfnCompare) return FALSE;
7905 if (!infoPtr->hdpaItems) return FALSE;
7907 /* if there are 0 or 1 items, there is no need to sort */
7908 if (infoPtr->nItemCount < 2) return TRUE;
7910 /* clear selection */
7911 ranges_clear(infoPtr->selectionRanges);
7913 /* save selection mark and focused item */
7914 if (infoPtr->nSelectionMark >= 0)
7915 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
7916 if (infoPtr->nFocusedItem >= 0)
7917 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7919 infoPtr->pfnCompare = pfnCompare;
7920 infoPtr->lParamSort = lParamSort;
7921 if (IsEx)
7922 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
7923 else
7924 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7926 /* restore selection ranges */
7927 for (i=0; i < infoPtr->nItemCount; i++)
7929 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7930 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7932 if (lpItem->state & LVIS_SELECTED)
7933 ranges_additem(infoPtr->selectionRanges, i);
7935 /* restore selection mark and focused item */
7936 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7937 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
7939 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7941 /* refresh the display */
7942 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7943 LISTVIEW_InvalidateList(infoPtr);
7945 return TRUE;
7948 /***
7949 * DESCRIPTION:
7950 * Update theme handle after a theme change.
7952 * PARAMETER(S):
7953 * [I] infoPtr : valid pointer to the listview structure
7955 * RETURN:
7956 * SUCCESS : 0
7957 * FAILURE : something else
7959 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7961 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7962 CloseThemeData(theme);
7963 OpenThemeData(infoPtr->hwndSelf, themeClass);
7964 return 0;
7967 /***
7968 * DESCRIPTION:
7969 * Updates an items or rearranges the listview control.
7971 * PARAMETER(S):
7972 * [I] infoPtr : valid pointer to the listview structure
7973 * [I] nItem : item index
7975 * RETURN:
7976 * SUCCESS : TRUE
7977 * FAILURE : FALSE
7979 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7981 TRACE("(nItem=%d)\n", nItem);
7983 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7985 /* rearrange with default alignment style */
7986 if (is_autoarrange(infoPtr))
7987 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7988 else
7989 LISTVIEW_InvalidateItem(infoPtr, nItem);
7991 return TRUE;
7994 /***
7995 * DESCRIPTION:
7996 * Draw the track line at the place defined in the infoPtr structure.
7997 * The line is drawn with a XOR pen so drawing the line for the second time
7998 * in the same place erases the line.
8000 * PARAMETER(S):
8001 * [I] infoPtr : valid pointer to the listview structure
8003 * RETURN:
8004 * SUCCESS : TRUE
8005 * FAILURE : FALSE
8007 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
8009 HPEN hOldPen;
8010 HDC hdc;
8011 INT oldROP;
8013 if (infoPtr->xTrackLine == -1)
8014 return FALSE;
8016 if (!(hdc = GetDC(infoPtr->hwndSelf)))
8017 return FALSE;
8018 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
8019 oldROP = SetROP2(hdc, R2_XORPEN);
8020 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
8021 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
8022 SetROP2(hdc, oldROP);
8023 SelectObject(hdc, hOldPen);
8024 ReleaseDC(infoPtr->hwndSelf, hdc);
8025 return TRUE;
8028 /***
8029 * DESCRIPTION:
8030 * Called when an edit control should be displayed. This function is called after
8031 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
8033 * PARAMETER(S):
8034 * [I] hwnd : Handle to the listview
8035 * [I] uMsg : WM_TIMER (ignored)
8036 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
8037 * [I] dwTimer : The elapsed time (ignored)
8039 * RETURN:
8040 * None.
8042 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
8044 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
8045 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8047 KillTimer(hwnd, idEvent);
8048 editItem->fEnabled = FALSE;
8049 /* check if the item is still selected */
8050 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
8051 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
8054 /***
8055 * DESCRIPTION:
8056 * Creates the listview control - the WM_NCCREATE phase.
8058 * PARAMETER(S):
8059 * [I] hwnd : window handle
8060 * [I] lpcs : the create parameters
8062 * RETURN:
8063 * Success: TRUE
8064 * Failure: FALSE
8066 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
8068 LISTVIEW_INFO *infoPtr;
8069 LOGFONTW logFont;
8071 TRACE("(lpcs=%p)\n", lpcs);
8073 /* initialize info pointer */
8074 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
8075 if (!infoPtr) return FALSE;
8077 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
8079 infoPtr->hwndSelf = hwnd;
8080 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
8081 /* determine the type of structures to use */
8082 infoPtr->hwndNotify = lpcs->hwndParent;
8083 /* infoPtr->notifyFormat will be filled in WM_CREATE */
8085 /* initialize color information */
8086 infoPtr->clrBk = CLR_NONE;
8087 infoPtr->clrText = CLR_DEFAULT;
8088 infoPtr->clrTextBk = CLR_DEFAULT;
8089 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
8091 /* set default values */
8092 infoPtr->nFocusedItem = -1;
8093 infoPtr->nSelectionMark = -1;
8094 infoPtr->nHotItem = -1;
8095 infoPtr->bRedraw = TRUE;
8096 infoPtr->bNoItemMetrics = TRUE;
8097 infoPtr->bDoChangeNotify = TRUE;
8098 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
8099 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
8100 infoPtr->nEditLabelItem = -1;
8101 infoPtr->dwHoverTime = -1; /* default system hover time */
8102 infoPtr->nMeasureItemHeight = 0;
8103 infoPtr->xTrackLine = -1; /* no track line */
8104 infoPtr->itemEdit.fEnabled = FALSE;
8106 /* get default font (icon title) */
8107 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8108 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8109 infoPtr->hFont = infoPtr->hDefaultFont;
8110 LISTVIEW_SaveTextMetrics(infoPtr);
8112 /* allocate memory for the data structure */
8113 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8114 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8115 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8116 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8117 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8118 return TRUE;
8120 fail:
8121 DestroyWindow(infoPtr->hwndHeader);
8122 ranges_destroy(infoPtr->selectionRanges);
8123 DPA_Destroy(infoPtr->hdpaItems);
8124 DPA_Destroy(infoPtr->hdpaPosX);
8125 DPA_Destroy(infoPtr->hdpaPosY);
8126 DPA_Destroy(infoPtr->hdpaColumns);
8127 Free(infoPtr);
8128 return FALSE;
8131 /***
8132 * DESCRIPTION:
8133 * Creates the listview control - the WM_CREATE phase. Most of the data is
8134 * already set up in LISTVIEW_NCCreate
8136 * PARAMETER(S):
8137 * [I] hwnd : window handle
8138 * [I] lpcs : the create parameters
8140 * RETURN:
8141 * Success: 0
8142 * Failure: -1
8144 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8146 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8147 UINT uView = lpcs->style & LVS_TYPEMASK;
8149 TRACE("(lpcs=%p)\n", lpcs);
8151 infoPtr->dwStyle = lpcs->style;
8152 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8153 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8155 if ((uView == LVS_REPORT) && (lpcs->style & WS_VISIBLE))
8157 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
8159 else
8160 infoPtr->hwndHeader = 0;
8162 /* init item size to avoid division by 0 */
8163 LISTVIEW_UpdateItemSize (infoPtr);
8165 if (uView == LVS_REPORT)
8167 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
8169 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8171 LISTVIEW_UpdateScroll(infoPtr);
8174 OpenThemeData(hwnd, themeClass);
8176 /* initialize the icon sizes */
8177 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8178 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8179 return 0;
8182 /***
8183 * DESCRIPTION:
8184 * Destroys the listview control.
8186 * PARAMETER(S):
8187 * [I] infoPtr : valid pointer to the listview structure
8189 * RETURN:
8190 * Success: 0
8191 * Failure: -1
8193 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8195 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8196 CloseThemeData(theme);
8197 return 0;
8200 /***
8201 * DESCRIPTION:
8202 * Enables the listview control.
8204 * PARAMETER(S):
8205 * [I] infoPtr : valid pointer to the listview structure
8206 * [I] bEnable : specifies whether to enable or disable the window
8208 * RETURN:
8209 * SUCCESS : TRUE
8210 * FAILURE : FALSE
8212 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8214 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8215 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8216 return TRUE;
8219 /***
8220 * DESCRIPTION:
8221 * Erases the background of the listview control.
8223 * PARAMETER(S):
8224 * [I] infoPtr : valid pointer to the listview structure
8225 * [I] hdc : device context handle
8227 * RETURN:
8228 * SUCCESS : TRUE
8229 * FAILURE : FALSE
8231 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8233 RECT rc;
8235 TRACE("(hdc=%p)\n", hdc);
8237 if (!GetClipBox(hdc, &rc)) return FALSE;
8239 /* for double buffered controls we need to do this during refresh */
8240 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8242 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8246 /***
8247 * DESCRIPTION:
8248 * Helper function for LISTVIEW_[HV]Scroll *only*.
8249 * Performs vertical/horizontal scrolling by a give amount.
8251 * PARAMETER(S):
8252 * [I] infoPtr : valid pointer to the listview structure
8253 * [I] dx : amount of horizontal scroll
8254 * [I] dy : amount of vertical scroll
8256 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8258 /* now we can scroll the list */
8259 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8260 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8261 /* if we have focus, adjust rect */
8262 OffsetRect(&infoPtr->rcFocus, dx, dy);
8263 UpdateWindow(infoPtr->hwndSelf);
8266 /***
8267 * DESCRIPTION:
8268 * Performs vertical scrolling.
8270 * PARAMETER(S):
8271 * [I] infoPtr : valid pointer to the listview structure
8272 * [I] nScrollCode : scroll code
8273 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8274 * [I] hScrollWnd : scrollbar control window handle
8276 * RETURN:
8277 * Zero
8279 * NOTES:
8280 * SB_LINEUP/SB_LINEDOWN:
8281 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8282 * for LVS_REPORT is 1 line
8283 * for LVS_LIST cannot occur
8286 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8287 INT nScrollDiff, HWND hScrollWnd)
8289 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8290 INT nOldScrollPos, nNewScrollPos;
8291 SCROLLINFO scrollInfo;
8292 BOOL is_an_icon;
8294 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8295 debugscrollcode(nScrollCode), nScrollDiff);
8297 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8299 scrollInfo.cbSize = sizeof(SCROLLINFO);
8300 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8302 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8304 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8306 nOldScrollPos = scrollInfo.nPos;
8307 switch (nScrollCode)
8309 case SB_INTERNAL:
8310 break;
8312 case SB_LINEUP:
8313 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8314 break;
8316 case SB_LINEDOWN:
8317 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8318 break;
8320 case SB_PAGEUP:
8321 nScrollDiff = -scrollInfo.nPage;
8322 break;
8324 case SB_PAGEDOWN:
8325 nScrollDiff = scrollInfo.nPage;
8326 break;
8328 case SB_THUMBPOSITION:
8329 case SB_THUMBTRACK:
8330 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8331 break;
8333 default:
8334 nScrollDiff = 0;
8337 /* quit right away if pos isn't changing */
8338 if (nScrollDiff == 0) return 0;
8340 /* calculate new position, and handle overflows */
8341 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8342 if (nScrollDiff > 0) {
8343 if (nNewScrollPos < nOldScrollPos ||
8344 nNewScrollPos > scrollInfo.nMax)
8345 nNewScrollPos = scrollInfo.nMax;
8346 } else {
8347 if (nNewScrollPos > nOldScrollPos ||
8348 nNewScrollPos < scrollInfo.nMin)
8349 nNewScrollPos = scrollInfo.nMin;
8352 /* set the new position, and reread in case it changed */
8353 scrollInfo.fMask = SIF_POS;
8354 scrollInfo.nPos = nNewScrollPos;
8355 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8357 /* carry on only if it really changed */
8358 if (nNewScrollPos == nOldScrollPos) return 0;
8360 /* now adjust to client coordinates */
8361 nScrollDiff = nOldScrollPos - nNewScrollPos;
8362 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8364 /* and scroll the window */
8365 scroll_list(infoPtr, 0, nScrollDiff);
8367 return 0;
8370 /***
8371 * DESCRIPTION:
8372 * Performs horizontal scrolling.
8374 * PARAMETER(S):
8375 * [I] infoPtr : valid pointer to the listview structure
8376 * [I] nScrollCode : scroll code
8377 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8378 * [I] hScrollWnd : scrollbar control window handle
8380 * RETURN:
8381 * Zero
8383 * NOTES:
8384 * SB_LINELEFT/SB_LINERIGHT:
8385 * for LVS_ICON, LVS_SMALLICON 1 pixel
8386 * for LVS_REPORT is 1 pixel
8387 * for LVS_LIST is 1 column --> which is a 1 because the
8388 * scroll is based on columns not pixels
8391 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8392 INT nScrollDiff, HWND hScrollWnd)
8394 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8395 INT nOldScrollPos, nNewScrollPos;
8396 SCROLLINFO scrollInfo;
8398 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8399 debugscrollcode(nScrollCode), nScrollDiff);
8401 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8403 scrollInfo.cbSize = sizeof(SCROLLINFO);
8404 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8406 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8408 nOldScrollPos = scrollInfo.nPos;
8410 switch (nScrollCode)
8412 case SB_INTERNAL:
8413 break;
8415 case SB_LINELEFT:
8416 nScrollDiff = -1;
8417 break;
8419 case SB_LINERIGHT:
8420 nScrollDiff = 1;
8421 break;
8423 case SB_PAGELEFT:
8424 nScrollDiff = -scrollInfo.nPage;
8425 break;
8427 case SB_PAGERIGHT:
8428 nScrollDiff = scrollInfo.nPage;
8429 break;
8431 case SB_THUMBPOSITION:
8432 case SB_THUMBTRACK:
8433 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8434 break;
8436 default:
8437 nScrollDiff = 0;
8440 /* quit right away if pos isn't changing */
8441 if (nScrollDiff == 0) return 0;
8443 /* calculate new position, and handle overflows */
8444 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8445 if (nScrollDiff > 0) {
8446 if (nNewScrollPos < nOldScrollPos ||
8447 nNewScrollPos > scrollInfo.nMax)
8448 nNewScrollPos = scrollInfo.nMax;
8449 } else {
8450 if (nNewScrollPos > nOldScrollPos ||
8451 nNewScrollPos < scrollInfo.nMin)
8452 nNewScrollPos = scrollInfo.nMin;
8455 /* set the new position, and reread in case it changed */
8456 scrollInfo.fMask = SIF_POS;
8457 scrollInfo.nPos = nNewScrollPos;
8458 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8460 /* carry on only if it really changed */
8461 if (nNewScrollPos == nOldScrollPos) return 0;
8463 if(uView == LVS_REPORT)
8464 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8466 /* now adjust to client coordinates */
8467 nScrollDiff = nOldScrollPos - nNewScrollPos;
8468 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8470 /* and scroll the window */
8471 scroll_list(infoPtr, nScrollDiff, 0);
8473 return 0;
8476 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8478 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8479 INT gcWheelDelta = 0;
8480 INT pulScrollLines = 3;
8481 SCROLLINFO scrollInfo;
8483 TRACE("(wheelDelta=%d)\n", wheelDelta);
8485 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8486 gcWheelDelta -= wheelDelta;
8488 scrollInfo.cbSize = sizeof(SCROLLINFO);
8489 scrollInfo.fMask = SIF_POS;
8491 switch(uView)
8493 case LVS_ICON:
8494 case LVS_SMALLICON:
8496 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8497 * should be fixed in the future.
8499 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8500 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8501 break;
8503 case LVS_REPORT:
8504 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8506 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8507 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8508 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8510 break;
8512 case LVS_LIST:
8513 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8514 break;
8516 return 0;
8519 /***
8520 * DESCRIPTION:
8521 * ???
8523 * PARAMETER(S):
8524 * [I] infoPtr : valid pointer to the listview structure
8525 * [I] nVirtualKey : virtual key
8526 * [I] lKeyData : key data
8528 * RETURN:
8529 * Zero
8531 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8533 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8534 HWND hwndSelf = infoPtr->hwndSelf;
8535 INT nItem = -1;
8536 NMLVKEYDOWN nmKeyDown;
8538 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8540 /* send LVN_KEYDOWN notification */
8541 nmKeyDown.wVKey = nVirtualKey;
8542 nmKeyDown.flags = 0;
8543 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8544 if (!IsWindow(hwndSelf))
8545 return 0;
8547 switch (nVirtualKey)
8549 case VK_SPACE:
8550 nItem = infoPtr->nFocusedItem;
8551 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8552 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8553 break;
8555 case VK_RETURN:
8556 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8558 if (!notify(infoPtr, NM_RETURN)) return 0;
8559 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8561 break;
8563 case VK_HOME:
8564 if (infoPtr->nItemCount > 0)
8565 nItem = 0;
8566 break;
8568 case VK_END:
8569 if (infoPtr->nItemCount > 0)
8570 nItem = infoPtr->nItemCount - 1;
8571 break;
8573 case VK_LEFT:
8574 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8575 break;
8577 case VK_UP:
8578 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8579 break;
8581 case VK_RIGHT:
8582 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8583 break;
8585 case VK_DOWN:
8586 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8587 break;
8589 case VK_PRIOR:
8590 if (uView == LVS_REPORT)
8592 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8593 if (infoPtr->nFocusedItem == topidx)
8594 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8595 else
8596 nItem = topidx;
8598 else
8599 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8600 * LISTVIEW_GetCountPerRow(infoPtr);
8601 if(nItem < 0) nItem = 0;
8602 break;
8604 case VK_NEXT:
8605 if (uView == LVS_REPORT)
8607 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8608 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8609 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8610 nItem = infoPtr->nFocusedItem + cnt - 1;
8611 else
8612 nItem = topidx + cnt - 1;
8614 else
8615 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8616 * LISTVIEW_GetCountPerRow(infoPtr);
8617 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8618 break;
8621 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8622 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
8624 return 0;
8627 /***
8628 * DESCRIPTION:
8629 * Kills the focus.
8631 * PARAMETER(S):
8632 * [I] infoPtr : valid pointer to the listview structure
8634 * RETURN:
8635 * Zero
8637 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8639 TRACE("()\n");
8641 /* if we did not have the focus, there's nothing to do */
8642 if (!infoPtr->bFocus) return 0;
8644 /* send NM_KILLFOCUS notification */
8645 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8647 /* if we have a focus rectangle, get rid of it */
8648 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8650 /* set window focus flag */
8651 infoPtr->bFocus = FALSE;
8653 /* invalidate the selected items before resetting focus flag */
8654 LISTVIEW_InvalidateSelectedItems(infoPtr);
8656 return 0;
8659 /***
8660 * DESCRIPTION:
8661 * Processes double click messages (left mouse button).
8663 * PARAMETER(S):
8664 * [I] infoPtr : valid pointer to the listview structure
8665 * [I] wKey : key flag
8666 * [I] x,y : mouse coordinate
8668 * RETURN:
8669 * Zero
8671 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8673 LVHITTESTINFO htInfo;
8675 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8677 /* Cancel the item edition if any */
8678 if (infoPtr->itemEdit.fEnabled)
8680 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8681 infoPtr->itemEdit.fEnabled = FALSE;
8684 /* send NM_RELEASEDCAPTURE notification */
8685 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8687 htInfo.pt.x = x;
8688 htInfo.pt.y = y;
8690 /* send NM_DBLCLK notification */
8691 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8692 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8694 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8695 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8697 return 0;
8700 /***
8701 * DESCRIPTION:
8702 * Processes mouse down messages (left mouse button).
8704 * PARAMETERS:
8705 * infoPtr [I ] valid pointer to the listview structure
8706 * wKey [I ] key flag
8707 * x,y [I ] mouse coordinate
8709 * RETURN:
8710 * Zero
8712 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8714 LVHITTESTINFO lvHitTestInfo;
8715 static BOOL bGroupSelect = TRUE;
8716 POINT pt = { x, y };
8717 INT nItem;
8719 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8721 /* send NM_RELEASEDCAPTURE notification */
8722 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8724 /* set left button down flag and record the click position */
8725 infoPtr->bLButtonDown = TRUE;
8726 infoPtr->ptClickPos = pt;
8727 infoPtr->bDragging = FALSE;
8729 lvHitTestInfo.pt.x = x;
8730 lvHitTestInfo.pt.y = y;
8732 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8733 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8734 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8736 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8738 toggle_checkbox_state(infoPtr, nItem);
8739 return 0;
8742 if (infoPtr->dwStyle & LVS_SINGLESEL)
8744 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8745 infoPtr->nEditLabelItem = nItem;
8746 else
8747 LISTVIEW_SetSelection(infoPtr, nItem);
8749 else
8751 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8753 if (bGroupSelect)
8755 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8756 LISTVIEW_SetItemFocus(infoPtr, nItem);
8757 infoPtr->nSelectionMark = nItem;
8759 else
8761 LVITEMW item;
8763 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8764 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8766 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8767 infoPtr->nSelectionMark = nItem;
8770 else if (wKey & MK_CONTROL)
8772 LVITEMW item;
8774 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8776 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8777 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8778 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8779 infoPtr->nSelectionMark = nItem;
8781 else if (wKey & MK_SHIFT)
8783 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8785 else
8787 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8788 infoPtr->nEditLabelItem = nItem;
8790 /* set selection (clears other pre-existing selections) */
8791 LISTVIEW_SetSelection(infoPtr, nItem);
8795 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8796 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8798 else
8800 /* remove all selections */
8801 LISTVIEW_DeselectAll(infoPtr);
8802 ReleaseCapture();
8805 return 0;
8808 /***
8809 * DESCRIPTION:
8810 * Processes mouse up messages (left mouse button).
8812 * PARAMETERS:
8813 * infoPtr [I ] valid pointer to the listview structure
8814 * wKey [I ] key flag
8815 * x,y [I ] mouse coordinate
8817 * RETURN:
8818 * Zero
8820 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8822 LVHITTESTINFO lvHitTestInfo;
8824 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8826 if (!infoPtr->bLButtonDown) return 0;
8828 lvHitTestInfo.pt.x = x;
8829 lvHitTestInfo.pt.y = y;
8831 /* send NM_CLICK notification */
8832 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8833 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8835 /* set left button flag */
8836 infoPtr->bLButtonDown = FALSE;
8838 if (infoPtr->bDragging)
8840 infoPtr->bDragging = FALSE;
8841 return 0;
8844 /* if we clicked on a selected item, edit the label */
8845 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8847 /* we want to make sure the user doesn't want to do a double click. So we will
8848 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8850 infoPtr->itemEdit.fEnabled = TRUE;
8851 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8852 SetTimer(infoPtr->hwndSelf,
8853 (UINT_PTR)&infoPtr->itemEdit,
8854 GetDoubleClickTime(),
8855 LISTVIEW_DelayedEditItem);
8858 if (!infoPtr->bFocus)
8859 SetFocus(infoPtr->hwndSelf);
8861 return 0;
8864 /***
8865 * DESCRIPTION:
8866 * Destroys the listview control (called after WM_DESTROY).
8868 * PARAMETER(S):
8869 * [I] infoPtr : valid pointer to the listview structure
8871 * RETURN:
8872 * Zero
8874 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8876 TRACE("()\n");
8878 /* delete all items */
8879 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8881 /* destroy data structure */
8882 DPA_Destroy(infoPtr->hdpaItems);
8883 DPA_Destroy(infoPtr->hdpaPosX);
8884 DPA_Destroy(infoPtr->hdpaPosY);
8885 DPA_Destroy(infoPtr->hdpaColumns);
8886 ranges_destroy(infoPtr->selectionRanges);
8888 /* destroy image lists */
8889 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8891 if (infoPtr->himlNormal)
8892 ImageList_Destroy(infoPtr->himlNormal);
8893 if (infoPtr->himlSmall)
8894 ImageList_Destroy(infoPtr->himlSmall);
8895 if (infoPtr->himlState)
8896 ImageList_Destroy(infoPtr->himlState);
8899 /* destroy font, bkgnd brush */
8900 infoPtr->hFont = 0;
8901 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8902 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8904 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8906 /* free listview info pointer*/
8907 Free(infoPtr);
8909 return 0;
8912 /***
8913 * DESCRIPTION:
8914 * Handles notifications from header.
8916 * PARAMETER(S):
8917 * [I] infoPtr : valid pointer to the listview structure
8918 * [I] nCtrlId : control identifier
8919 * [I] lpnmh : notification information
8921 * RETURN:
8922 * Zero
8924 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8926 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8927 HWND hwndSelf = infoPtr->hwndSelf;
8929 TRACE("(lpnmh=%p)\n", lpnmh);
8931 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8933 switch (lpnmh->hdr.code)
8935 case HDN_TRACKW:
8936 case HDN_TRACKA:
8938 COLUMN_INFO *lpColumnInfo;
8939 POINT ptOrigin;
8940 INT x;
8942 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8943 break;
8945 /* remove the old line (if any) */
8946 LISTVIEW_DrawTrackLine(infoPtr);
8948 /* compute & draw the new line */
8949 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8950 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8951 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8952 infoPtr->xTrackLine = x + ptOrigin.x;
8953 LISTVIEW_DrawTrackLine(infoPtr);
8954 break;
8957 case HDN_ENDTRACKA:
8958 case HDN_ENDTRACKW:
8959 /* remove the track line (if any) */
8960 LISTVIEW_DrawTrackLine(infoPtr);
8961 infoPtr->xTrackLine = -1;
8962 break;
8964 case HDN_ENDDRAG:
8965 FIXME("Changing column order not implemented\n");
8966 return TRUE;
8968 case HDN_ITEMCHANGINGW:
8969 case HDN_ITEMCHANGINGA:
8970 return notify_forward_header(infoPtr, lpnmh);
8972 case HDN_ITEMCHANGEDW:
8973 case HDN_ITEMCHANGEDA:
8975 COLUMN_INFO *lpColumnInfo;
8976 INT dx, cxy;
8978 notify_forward_header(infoPtr, lpnmh);
8979 if (!IsWindow(hwndSelf))
8980 break;
8982 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8984 HDITEMW hdi;
8986 hdi.mask = HDI_WIDTH;
8987 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8988 cxy = hdi.cxy;
8990 else
8991 cxy = lpnmh->pitem->cxy;
8993 /* determine how much we change since the last know position */
8994 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8995 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8996 if (dx != 0)
8998 lpColumnInfo->rcHeader.right += dx;
8999 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
9000 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
9001 else
9003 /* only needs to update the scrolls */
9004 infoPtr->nItemWidth += dx;
9005 LISTVIEW_UpdateScroll(infoPtr);
9007 LISTVIEW_UpdateItemSize(infoPtr);
9008 if (uView == LVS_REPORT && is_redrawing(infoPtr))
9010 POINT ptOrigin;
9011 RECT rcCol = lpColumnInfo->rcHeader;
9013 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
9014 OffsetRect(&rcCol, ptOrigin.x, 0);
9016 rcCol.top = infoPtr->rcList.top;
9017 rcCol.bottom = infoPtr->rcList.bottom;
9019 /* resizing left-aligned columns leaves most of the left side untouched */
9020 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
9022 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
9023 if (dx > 0)
9024 nMaxDirty += dx;
9025 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
9028 /* when shrinking the last column clear the now unused field */
9029 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
9030 rcCol.right -= dx;
9032 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
9036 break;
9038 case HDN_ITEMCLICKW:
9039 case HDN_ITEMCLICKA:
9041 /* Handle sorting by Header Column */
9042 NMLISTVIEW nmlv;
9044 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
9045 nmlv.iItem = -1;
9046 nmlv.iSubItem = lpnmh->iItem;
9047 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
9048 notify_forward_header(infoPtr, lpnmh);
9050 break;
9052 case HDN_DIVIDERDBLCLICKW:
9053 case HDN_DIVIDERDBLCLICKA:
9054 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
9055 break;
9058 return 0;
9061 /***
9062 * DESCRIPTION:
9063 * Paint non-client area of control.
9065 * PARAMETER(S):
9066 * [I] infoPtr : valid pointer to the listview structureof the sender
9067 * [I] region : update region
9069 * RETURN:
9070 * TRUE - frame was painted
9071 * FALSE - call default window proc
9073 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
9075 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
9076 HDC dc;
9077 RECT r;
9078 HRGN cliprgn;
9079 int cxEdge = GetSystemMetrics (SM_CXEDGE),
9080 cyEdge = GetSystemMetrics (SM_CYEDGE);
9082 if (!theme) return FALSE;
9084 GetWindowRect(infoPtr->hwndSelf, &r);
9086 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
9087 r.right - cxEdge, r.bottom - cyEdge);
9088 if (region != (HRGN)1)
9089 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
9090 OffsetRect(&r, -r.left, -r.top);
9092 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9093 OffsetRect(&r, -r.left, -r.top);
9095 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9096 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9097 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9098 ReleaseDC(infoPtr->hwndSelf, dc);
9100 /* Call default proc to get the scrollbars etc. painted */
9101 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9103 return TRUE;
9106 /***
9107 * DESCRIPTION:
9108 * Determines the type of structure to use.
9110 * PARAMETER(S):
9111 * [I] infoPtr : valid pointer to the listview structureof the sender
9112 * [I] hwndFrom : listview window handle
9113 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9115 * RETURN:
9116 * Zero
9118 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9120 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9122 if (nCommand == NF_REQUERY)
9123 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9125 return infoPtr->notifyFormat;
9128 /***
9129 * DESCRIPTION:
9130 * Paints/Repaints the listview control.
9132 * PARAMETER(S):
9133 * [I] infoPtr : valid pointer to the listview structure
9134 * [I] hdc : device context handle
9136 * RETURN:
9137 * Zero
9139 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9141 TRACE("(hdc=%p)\n", hdc);
9143 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9145 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9147 infoPtr->bNoItemMetrics = FALSE;
9148 LISTVIEW_UpdateItemSize(infoPtr);
9149 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9150 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9151 LISTVIEW_UpdateScroll(infoPtr);
9154 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
9156 if (hdc)
9157 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9158 else
9160 PAINTSTRUCT ps;
9162 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9163 if (!hdc) return 1;
9164 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9165 EndPaint(infoPtr->hwndSelf, &ps);
9168 return 0;
9172 /***
9173 * DESCRIPTION:
9174 * Paints/Repaints the listview control.
9176 * PARAMETER(S):
9177 * [I] infoPtr : valid pointer to the listview structure
9178 * [I] hdc : device context handle
9179 * [I] options : drawing options
9181 * RETURN:
9182 * Zero
9184 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9186 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9188 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9189 return 0;
9191 if (options & PRF_ERASEBKGND)
9192 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9194 if (options & PRF_CLIENT)
9195 LISTVIEW_Paint(infoPtr, hdc);
9197 return 0;
9201 /***
9202 * DESCRIPTION:
9203 * Processes double click messages (right mouse button).
9205 * PARAMETER(S):
9206 * [I] infoPtr : valid pointer to the listview structure
9207 * [I] wKey : key flag
9208 * [I] x,y : mouse coordinate
9210 * RETURN:
9211 * Zero
9213 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9215 LVHITTESTINFO lvHitTestInfo;
9217 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9219 /* send NM_RELEASEDCAPTURE notification */
9220 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9222 /* send NM_RDBLCLK notification */
9223 lvHitTestInfo.pt.x = x;
9224 lvHitTestInfo.pt.y = y;
9225 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9226 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9228 return 0;
9231 /***
9232 * DESCRIPTION:
9233 * Processes mouse down messages (right mouse button).
9235 * PARAMETER(S):
9236 * [I] infoPtr : valid pointer to the listview structure
9237 * [I] wKey : key flag
9238 * [I] x,y : mouse coordinate
9240 * RETURN:
9241 * Zero
9243 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9245 LVHITTESTINFO lvHitTestInfo;
9246 INT nItem;
9248 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9250 /* send NM_RELEASEDCAPTURE notification */
9251 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9253 /* make sure the listview control window has the focus */
9254 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9256 /* set right button down flag */
9257 infoPtr->bRButtonDown = TRUE;
9259 /* determine the index of the selected item */
9260 lvHitTestInfo.pt.x = x;
9261 lvHitTestInfo.pt.y = y;
9262 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9264 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9266 LISTVIEW_SetItemFocus(infoPtr, nItem);
9267 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9268 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9269 LISTVIEW_SetSelection(infoPtr, nItem);
9271 else
9273 LISTVIEW_DeselectAll(infoPtr);
9276 return 0;
9279 /***
9280 * DESCRIPTION:
9281 * Processes mouse up messages (right mouse button).
9283 * PARAMETER(S):
9284 * [I] infoPtr : valid pointer to the listview structure
9285 * [I] wKey : key flag
9286 * [I] x,y : mouse coordinate
9288 * RETURN:
9289 * Zero
9291 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9293 LVHITTESTINFO lvHitTestInfo;
9294 POINT pt;
9296 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9298 if (!infoPtr->bRButtonDown) return 0;
9300 /* set button flag */
9301 infoPtr->bRButtonDown = FALSE;
9303 /* Send NM_RCLICK notification */
9304 lvHitTestInfo.pt.x = x;
9305 lvHitTestInfo.pt.y = y;
9306 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9307 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9309 /* Change to screen coordinate for WM_CONTEXTMENU */
9310 pt = lvHitTestInfo.pt;
9311 ClientToScreen(infoPtr->hwndSelf, &pt);
9313 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9314 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9315 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9317 return 0;
9321 /***
9322 * DESCRIPTION:
9323 * Sets the cursor.
9325 * PARAMETER(S):
9326 * [I] infoPtr : valid pointer to the listview structure
9327 * [I] hwnd : window handle of window containing the cursor
9328 * [I] nHittest : hit-test code
9329 * [I] wMouseMsg : ideintifier of the mouse message
9331 * RETURN:
9332 * TRUE if cursor is set
9333 * FALSE otherwise
9335 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9337 LVHITTESTINFO lvHitTestInfo;
9339 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9341 if(!infoPtr->hHotCursor) return FALSE;
9343 GetCursorPos(&lvHitTestInfo.pt);
9344 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9346 SetCursor(infoPtr->hHotCursor);
9348 return TRUE;
9351 /***
9352 * DESCRIPTION:
9353 * Sets the focus.
9355 * PARAMETER(S):
9356 * [I] infoPtr : valid pointer to the listview structure
9357 * [I] hwndLoseFocus : handle of previously focused window
9359 * RETURN:
9360 * Zero
9362 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9364 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9366 /* if we have the focus already, there's nothing to do */
9367 if (infoPtr->bFocus) return 0;
9369 /* send NM_SETFOCUS notification */
9370 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9372 /* set window focus flag */
9373 infoPtr->bFocus = TRUE;
9375 /* put the focus rect back on */
9376 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9378 /* redraw all visible selected items */
9379 LISTVIEW_InvalidateSelectedItems(infoPtr);
9381 return 0;
9384 /***
9385 * DESCRIPTION:
9386 * Sets the font.
9388 * PARAMETER(S):
9389 * [I] infoPtr : valid pointer to the listview structure
9390 * [I] fRedraw : font handle
9391 * [I] fRedraw : redraw flag
9393 * RETURN:
9394 * Zero
9396 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9398 HFONT oldFont = infoPtr->hFont;
9400 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9402 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9403 if (infoPtr->hFont == oldFont) return 0;
9405 LISTVIEW_SaveTextMetrics(infoPtr);
9407 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9409 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9410 LISTVIEW_UpdateSize(infoPtr);
9411 LISTVIEW_UpdateScroll(infoPtr);
9414 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9416 return 0;
9419 /***
9420 * DESCRIPTION:
9421 * Message handling for WM_SETREDRAW.
9422 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9424 * PARAMETER(S):
9425 * [I] infoPtr : valid pointer to the listview structure
9426 * [I] bRedraw: state of redraw flag
9428 * RETURN:
9429 * DefWinProc return value
9431 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9433 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9435 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9436 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9438 infoPtr->bRedraw = bRedraw;
9440 if(!bRedraw) return 0;
9442 if (is_autoarrange(infoPtr))
9443 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9444 LISTVIEW_UpdateScroll(infoPtr);
9446 /* despite what the WM_SETREDRAW docs says, apps expect us
9447 * to invalidate the listview here... stupid! */
9448 LISTVIEW_InvalidateList(infoPtr);
9450 return 0;
9453 /***
9454 * DESCRIPTION:
9455 * Resizes the listview control. This function processes WM_SIZE
9456 * messages. At this time, the width and height are not used.
9458 * PARAMETER(S):
9459 * [I] infoPtr : valid pointer to the listview structure
9460 * [I] Width : new width
9461 * [I] Height : new height
9463 * RETURN:
9464 * Zero
9466 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9468 RECT rcOld = infoPtr->rcList;
9470 TRACE("(width=%d, height=%d)\n", Width, Height);
9472 LISTVIEW_UpdateSize(infoPtr);
9473 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9475 /* do not bother with display related stuff if we're not redrawing */
9476 if (!is_redrawing(infoPtr)) return 0;
9478 if (is_autoarrange(infoPtr))
9479 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9481 LISTVIEW_UpdateScroll(infoPtr);
9483 /* refresh all only for lists whose height changed significantly */
9484 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9485 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9486 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9487 LISTVIEW_InvalidateList(infoPtr);
9489 return 0;
9492 /***
9493 * DESCRIPTION:
9494 * Sets the size information.
9496 * PARAMETER(S):
9497 * [I] infoPtr : valid pointer to the listview structure
9499 * RETURN:
9500 * None
9502 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9504 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9506 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9508 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9510 if (uView == LVS_LIST)
9512 /* Apparently the "LIST" style is supposed to have the same
9513 * number of items in a column even if there is no scroll bar.
9514 * Since if a scroll bar already exists then the bottom is already
9515 * reduced, only reduce if the scroll bar does not currently exist.
9516 * The "2" is there to mimic the native control. I think it may be
9517 * related to either padding or edges. (GLA 7/2002)
9519 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9520 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9521 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9523 else if (uView == LVS_REPORT)
9525 HDLAYOUT hl;
9526 WINDOWPOS wp;
9528 hl.prc = &infoPtr->rcList;
9529 hl.pwpos = &wp;
9530 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9531 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9532 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9533 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9534 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9535 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9537 infoPtr->rcList.top = max(wp.cy, 0);
9538 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9541 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9544 /***
9545 * DESCRIPTION:
9546 * Processes WM_STYLECHANGED messages.
9548 * PARAMETER(S):
9549 * [I] infoPtr : valid pointer to the listview structure
9550 * [I] wStyleType : window style type (normal or extended)
9551 * [I] lpss : window style information
9553 * RETURN:
9554 * Zero
9556 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9557 const STYLESTRUCT *lpss)
9559 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9560 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9561 UINT style;
9563 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9564 wStyleType, lpss->styleOld, lpss->styleNew);
9566 if (wStyleType != GWL_STYLE) return 0;
9568 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9569 /* or LVS_SINGLESEL */
9570 /* or LVS_SORT{AS,DES}CENDING */
9572 infoPtr->dwStyle = lpss->styleNew;
9574 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9575 ((lpss->styleNew & WS_HSCROLL) == 0))
9576 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9578 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9579 ((lpss->styleNew & WS_VSCROLL) == 0))
9580 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9582 if (uNewView != uOldView)
9584 SIZE oldIconSize = infoPtr->iconSize;
9585 HIMAGELIST himl;
9587 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9588 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9590 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9591 SetRectEmpty(&infoPtr->rcFocus);
9593 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9594 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9596 if (uNewView == LVS_ICON)
9598 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9600 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9601 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9602 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9605 else if (uNewView == LVS_REPORT)
9607 HDLAYOUT hl;
9608 WINDOWPOS wp;
9610 LISTVIEW_CreateHeader( infoPtr );
9612 hl.prc = &infoPtr->rcList;
9613 hl.pwpos = &wp;
9614 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9615 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9616 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9617 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9620 LISTVIEW_UpdateItemSize(infoPtr);
9623 if (uNewView == LVS_REPORT)
9625 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9627 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9629 /* Turn off the header control */
9630 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9631 TRACE("Hide header control, was 0x%08x\n", style);
9632 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9633 } else {
9634 /* Turn on the header control */
9635 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9637 TRACE("Show header control, was 0x%08x\n", style);
9638 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9644 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9645 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9646 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9648 /* update the size of the client area */
9649 LISTVIEW_UpdateSize(infoPtr);
9651 /* add scrollbars if needed */
9652 LISTVIEW_UpdateScroll(infoPtr);
9654 /* invalidate client area + erase background */
9655 LISTVIEW_InvalidateList(infoPtr);
9657 return 0;
9660 /***
9661 * DESCRIPTION:
9662 * Processes WM_STYLECHANGING messages.
9664 * PARAMETER(S):
9665 * [I] infoPtr : valid pointer to the listview structure
9666 * [I] wStyleType : window style type (normal or extended)
9667 * [I0] lpss : window style information
9669 * RETURN:
9670 * Zero
9672 static INT LISTVIEW_StyleChanging(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9673 STYLESTRUCT *lpss)
9675 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9676 wStyleType, lpss->styleOld, lpss->styleNew);
9678 /* don't forward LVS_OWNERDATA only if not already set to */
9679 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
9681 if (lpss->styleOld & LVS_OWNERDATA)
9682 lpss->styleNew |= LVS_OWNERDATA;
9683 else
9684 lpss->styleNew &= ~LVS_OWNERDATA;
9687 return 0;
9690 /***
9691 * DESCRIPTION:
9692 * Processes WM_SHOWWINDOW messages.
9694 * PARAMETER(S):
9695 * [I] infoPtr : valid pointer to the listview structure
9696 * [I] bShown : window is being shown (FALSE when hidden)
9697 * [I] iStatus : window show status
9699 * RETURN:
9700 * Zero
9702 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, BOOL bShown, INT iStatus)
9704 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9706 /* header delayed creation */
9707 if ((uView == LVS_REPORT) && bShown)
9709 LISTVIEW_CreateHeader(infoPtr);
9711 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
9712 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9715 return 0;
9718 /***
9719 * DESCRIPTION:
9720 * Window procedure of the listview control.
9723 static LRESULT WINAPI
9724 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9726 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9728 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9730 if (!infoPtr && (uMsg != WM_NCCREATE))
9731 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9733 switch (uMsg)
9735 case LVM_APPROXIMATEVIEWRECT:
9736 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9737 LOWORD(lParam), HIWORD(lParam));
9738 case LVM_ARRANGE:
9739 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9741 /* case LVM_CANCELEDITLABEL: */
9743 case LVM_CREATEDRAGIMAGE:
9744 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9746 case LVM_DELETEALLITEMS:
9747 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9749 case LVM_DELETECOLUMN:
9750 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9752 case LVM_DELETEITEM:
9753 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9755 case LVM_EDITLABELW:
9756 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9758 case LVM_EDITLABELA:
9759 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9761 /* case LVM_ENABLEGROUPVIEW: */
9763 case LVM_ENSUREVISIBLE:
9764 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9766 case LVM_FINDITEMW:
9767 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9769 case LVM_FINDITEMA:
9770 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9772 case LVM_GETBKCOLOR:
9773 return infoPtr->clrBk;
9775 /* case LVM_GETBKIMAGE: */
9777 case LVM_GETCALLBACKMASK:
9778 return infoPtr->uCallbackMask;
9780 case LVM_GETCOLUMNA:
9781 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9783 case LVM_GETCOLUMNW:
9784 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9786 case LVM_GETCOLUMNORDERARRAY:
9787 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9789 case LVM_GETCOLUMNWIDTH:
9790 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9792 case LVM_GETCOUNTPERPAGE:
9793 return LISTVIEW_GetCountPerPage(infoPtr);
9795 case LVM_GETEDITCONTROL:
9796 return (LRESULT)infoPtr->hwndEdit;
9798 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9799 return infoPtr->dwLvExStyle;
9801 /* case LVM_GETGROUPINFO: */
9803 /* case LVM_GETGROUPMETRICS: */
9805 case LVM_GETHEADER:
9806 return (LRESULT)infoPtr->hwndHeader;
9808 case LVM_GETHOTCURSOR:
9809 return (LRESULT)infoPtr->hHotCursor;
9811 case LVM_GETHOTITEM:
9812 return infoPtr->nHotItem;
9814 case LVM_GETHOVERTIME:
9815 return infoPtr->dwHoverTime;
9817 case LVM_GETIMAGELIST:
9818 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9820 /* case LVM_GETINSERTMARK: */
9822 /* case LVM_GETINSERTMARKCOLOR: */
9824 /* case LVM_GETINSERTMARKRECT: */
9826 case LVM_GETISEARCHSTRINGA:
9827 case LVM_GETISEARCHSTRINGW:
9828 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9829 return FALSE;
9831 case LVM_GETITEMA:
9832 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9834 case LVM_GETITEMW:
9835 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9837 case LVM_GETITEMCOUNT:
9838 return infoPtr->nItemCount;
9840 case LVM_GETITEMPOSITION:
9841 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9843 case LVM_GETITEMRECT:
9844 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9846 case LVM_GETITEMSPACING:
9847 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9849 case LVM_GETITEMSTATE:
9850 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9852 case LVM_GETITEMTEXTA:
9853 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9855 case LVM_GETITEMTEXTW:
9856 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9858 case LVM_GETNEXTITEM:
9859 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9861 case LVM_GETNUMBEROFWORKAREAS:
9862 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9863 return 1;
9865 case LVM_GETORIGIN:
9866 if (!lParam) return FALSE;
9867 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9868 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9869 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9870 return TRUE;
9872 /* case LVM_GETOUTLINECOLOR: */
9874 /* case LVM_GETSELECTEDCOLUMN: */
9876 case LVM_GETSELECTEDCOUNT:
9877 return LISTVIEW_GetSelectedCount(infoPtr);
9879 case LVM_GETSELECTIONMARK:
9880 return infoPtr->nSelectionMark;
9882 case LVM_GETSTRINGWIDTHA:
9883 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9885 case LVM_GETSTRINGWIDTHW:
9886 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9888 case LVM_GETSUBITEMRECT:
9889 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9891 case LVM_GETTEXTBKCOLOR:
9892 return infoPtr->clrTextBk;
9894 case LVM_GETTEXTCOLOR:
9895 return infoPtr->clrText;
9897 /* case LVM_GETTILEINFO: */
9899 /* case LVM_GETTILEVIEWINFO: */
9901 case LVM_GETTOOLTIPS:
9902 if( !infoPtr->hwndToolTip )
9903 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9904 return (LRESULT)infoPtr->hwndToolTip;
9906 case LVM_GETTOPINDEX:
9907 return LISTVIEW_GetTopIndex(infoPtr);
9909 case LVM_GETUNICODEFORMAT:
9910 return (infoPtr->notifyFormat == NFR_UNICODE);
9912 /* case LVM_GETVIEW: */
9914 case LVM_GETVIEWRECT:
9915 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9917 case LVM_GETWORKAREAS:
9918 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9919 return FALSE;
9921 /* case LVM_HASGROUP: */
9923 case LVM_HITTEST:
9924 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9926 case LVM_INSERTCOLUMNA:
9927 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9929 case LVM_INSERTCOLUMNW:
9930 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9932 /* case LVM_INSERTGROUP: */
9934 /* case LVM_INSERTGROUPSORTED: */
9936 case LVM_INSERTITEMA:
9937 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9939 case LVM_INSERTITEMW:
9940 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9942 /* case LVM_INSERTMARKHITTEST: */
9944 /* case LVM_ISGROUPVIEWENABLED: */
9946 /* case LVM_MAPIDTOINDEX: */
9948 /* case LVM_MAPINDEXTOID: */
9950 /* case LVM_MOVEGROUP: */
9952 /* case LVM_MOVEITEMTOGROUP: */
9954 case LVM_REDRAWITEMS:
9955 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9957 /* case LVM_REMOVEALLGROUPS: */
9959 /* case LVM_REMOVEGROUP: */
9961 case LVM_SCROLL:
9962 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9964 case LVM_SETBKCOLOR:
9965 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9967 /* case LVM_SETBKIMAGE: */
9969 case LVM_SETCALLBACKMASK:
9970 infoPtr->uCallbackMask = (UINT)wParam;
9971 return TRUE;
9973 case LVM_SETCOLUMNA:
9974 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9976 case LVM_SETCOLUMNW:
9977 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9979 case LVM_SETCOLUMNORDERARRAY:
9980 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9982 case LVM_SETCOLUMNWIDTH:
9983 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9985 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9986 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9988 /* case LVM_SETGROUPINFO: */
9990 /* case LVM_SETGROUPMETRICS: */
9992 case LVM_SETHOTCURSOR:
9993 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9995 case LVM_SETHOTITEM:
9996 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9998 case LVM_SETHOVERTIME:
9999 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
10001 case LVM_SETICONSPACING:
10002 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10004 case LVM_SETIMAGELIST:
10005 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
10007 /* case LVM_SETINFOTIP: */
10009 /* case LVM_SETINSERTMARK: */
10011 /* case LVM_SETINSERTMARKCOLOR: */
10013 case LVM_SETITEMA:
10014 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
10016 case LVM_SETITEMW:
10017 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
10019 case LVM_SETITEMCOUNT:
10020 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
10022 case LVM_SETITEMPOSITION:
10024 POINT pt;
10025 pt.x = (short)LOWORD(lParam);
10026 pt.y = (short)HIWORD(lParam);
10027 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
10030 case LVM_SETITEMPOSITION32:
10031 if (lParam == 0) return FALSE;
10032 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
10034 case LVM_SETITEMSTATE:
10035 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
10037 case LVM_SETITEMTEXTA:
10038 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
10040 case LVM_SETITEMTEXTW:
10041 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
10043 /* case LVM_SETOUTLINECOLOR: */
10045 /* case LVM_SETSELECTEDCOLUMN: */
10047 case LVM_SETSELECTIONMARK:
10048 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
10050 case LVM_SETTEXTBKCOLOR:
10051 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
10053 case LVM_SETTEXTCOLOR:
10054 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
10056 /* case LVM_SETTILEINFO: */
10058 /* case LVM_SETTILEVIEWINFO: */
10060 /* case LVM_SETTILEWIDTH: */
10062 case LVM_SETTOOLTIPS:
10063 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
10065 case LVM_SETUNICODEFORMAT:
10066 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
10068 /* case LVM_SETVIEW: */
10070 /* case LVM_SETWORKAREAS: */
10072 /* case LVM_SORTGROUPS: */
10074 case LVM_SORTITEMS:
10075 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, FALSE);
10077 case LVM_SORTITEMSEX:
10078 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam, TRUE);
10080 case LVM_SUBITEMHITTEST:
10081 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
10083 case LVM_UPDATE:
10084 return LISTVIEW_Update(infoPtr, (INT)wParam);
10086 case WM_CHAR:
10087 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
10089 case WM_COMMAND:
10090 return LISTVIEW_Command(infoPtr, wParam, lParam);
10092 case WM_NCCREATE:
10093 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
10095 case WM_CREATE:
10096 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
10098 case WM_DESTROY:
10099 return LISTVIEW_Destroy(infoPtr);
10101 case WM_ENABLE:
10102 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
10104 case WM_ERASEBKGND:
10105 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
10107 case WM_GETDLGCODE:
10108 return DLGC_WANTCHARS | DLGC_WANTARROWS;
10110 case WM_GETFONT:
10111 return (LRESULT)infoPtr->hFont;
10113 case WM_HSCROLL:
10114 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10116 case WM_KEYDOWN:
10117 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
10119 case WM_KILLFOCUS:
10120 return LISTVIEW_KillFocus(infoPtr);
10122 case WM_LBUTTONDBLCLK:
10123 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10125 case WM_LBUTTONDOWN:
10126 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10128 case WM_LBUTTONUP:
10129 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10131 case WM_MOUSEMOVE:
10132 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10134 case WM_MOUSEHOVER:
10135 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10137 case WM_NCDESTROY:
10138 return LISTVIEW_NCDestroy(infoPtr);
10140 case WM_NCPAINT:
10141 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
10142 return 0;
10143 goto fwd_msg;
10145 case WM_NOTIFY:
10146 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
10147 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
10148 else return 0;
10150 case WM_NOTIFYFORMAT:
10151 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10153 case WM_PRINTCLIENT:
10154 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10156 case WM_PAINT:
10157 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10159 case WM_RBUTTONDBLCLK:
10160 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10162 case WM_RBUTTONDOWN:
10163 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10165 case WM_RBUTTONUP:
10166 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10168 case WM_SETCURSOR:
10169 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10170 return TRUE;
10171 goto fwd_msg;
10173 case WM_SETFOCUS:
10174 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10176 case WM_SETFONT:
10177 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10179 case WM_SETREDRAW:
10180 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10182 case WM_SHOWWINDOW:
10183 LISTVIEW_ShowWindow(infoPtr, (BOOL)wParam, (INT)lParam);
10184 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10186 case WM_SIZE:
10187 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10189 case WM_STYLECHANGED:
10190 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10192 case WM_STYLECHANGING:
10193 return LISTVIEW_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10195 case WM_SYSCOLORCHANGE:
10196 COMCTL32_RefreshSysColors();
10197 return 0;
10199 /* case WM_TIMER: */
10200 case WM_THEMECHANGED:
10201 return LISTVIEW_ThemeChanged(infoPtr);
10203 case WM_VSCROLL:
10204 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10206 case WM_MOUSEWHEEL:
10207 if (wParam & (MK_SHIFT | MK_CONTROL))
10208 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10209 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10211 case WM_WINDOWPOSCHANGED:
10212 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10214 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10215 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10216 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10218 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10220 MEASUREITEMSTRUCT mis;
10221 mis.CtlType = ODT_LISTVIEW;
10222 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10223 mis.itemID = -1;
10224 mis.itemWidth = 0;
10225 mis.itemData = 0;
10226 mis.itemHeight= infoPtr->nItemHeight;
10227 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10228 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10229 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10232 LISTVIEW_UpdateSize(infoPtr);
10233 LISTVIEW_UpdateScroll(infoPtr);
10235 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10237 /* case WM_WININICHANGE: */
10239 default:
10240 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10241 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10243 fwd_msg:
10244 /* call default window procedure */
10245 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10250 /***
10251 * DESCRIPTION:
10252 * Registers the window class.
10254 * PARAMETER(S):
10255 * None
10257 * RETURN:
10258 * None
10260 void LISTVIEW_Register(void)
10262 WNDCLASSW wndClass;
10264 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10265 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10266 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10267 wndClass.cbClsExtra = 0;
10268 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10269 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10270 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10271 wndClass.lpszClassName = WC_LISTVIEWW;
10272 RegisterClassW(&wndClass);
10275 /***
10276 * DESCRIPTION:
10277 * Unregisters the window class.
10279 * PARAMETER(S):
10280 * None
10282 * RETURN:
10283 * None
10285 void LISTVIEW_Unregister(void)
10287 UnregisterClassW(WC_LISTVIEWW, NULL);
10290 /***
10291 * DESCRIPTION:
10292 * Handle any WM_COMMAND messages
10294 * PARAMETER(S):
10295 * [I] infoPtr : valid pointer to the listview structure
10296 * [I] wParam : the first message parameter
10297 * [I] lParam : the second message parameter
10299 * RETURN:
10300 * Zero.
10302 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10304 switch (HIWORD(wParam))
10306 case EN_UPDATE:
10309 * Adjust the edit window size
10311 WCHAR buffer[1024];
10312 HDC hdc = GetDC(infoPtr->hwndEdit);
10313 HFONT hFont, hOldFont = 0;
10314 RECT rect;
10315 SIZE sz;
10317 if (!infoPtr->hwndEdit || !hdc) return 0;
10318 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10319 GetWindowRect(infoPtr->hwndEdit, &rect);
10321 /* Select font to get the right dimension of the string */
10322 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10323 if(hFont != 0)
10325 hOldFont = SelectObject(hdc, hFont);
10328 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10330 TEXTMETRICW textMetric;
10332 /* Add Extra spacing for the next character */
10333 GetTextMetricsW(hdc, &textMetric);
10334 sz.cx += (textMetric.tmMaxCharWidth * 2);
10336 SetWindowPos (
10337 infoPtr->hwndEdit,
10338 HWND_TOP,
10341 sz.cx,
10342 rect.bottom - rect.top,
10343 SWP_DRAWFRAME|SWP_NOMOVE);
10345 if(hFont != 0)
10346 SelectObject(hdc, hOldFont);
10348 ReleaseDC(infoPtr->hwndEdit, hdc);
10350 break;
10353 default:
10354 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10357 return 0;
10361 /***
10362 * DESCRIPTION:
10363 * Subclassed edit control windproc function
10365 * PARAMETER(S):
10366 * [I] hwnd : the edit window handle
10367 * [I] uMsg : the message that is to be processed
10368 * [I] wParam : first message parameter
10369 * [I] lParam : second message parameter
10370 * [I] isW : TRUE if input is Unicode
10372 * RETURN:
10373 * Zero.
10375 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10377 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10378 BOOL cancel = FALSE;
10380 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10381 hwnd, uMsg, wParam, lParam, isW);
10383 switch (uMsg)
10385 case WM_GETDLGCODE:
10386 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10388 case WM_KILLFOCUS:
10389 break;
10391 case WM_DESTROY:
10393 WNDPROC editProc = infoPtr->EditWndProc;
10394 infoPtr->EditWndProc = 0;
10395 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10396 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10399 case WM_KEYDOWN:
10400 if (VK_ESCAPE == (INT)wParam)
10402 cancel = TRUE;
10403 break;
10405 else if (VK_RETURN == (INT)wParam)
10406 break;
10408 default:
10409 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10412 /* kill the edit */
10413 if (infoPtr->hwndEdit)
10415 LPWSTR buffer = NULL;
10417 infoPtr->hwndEdit = 0;
10418 if (!cancel)
10420 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10422 if (len)
10424 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10426 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10427 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10431 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10433 Free(buffer);
10436 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10437 return 0;
10440 /***
10441 * DESCRIPTION:
10442 * Subclassed edit control Unicode windproc function
10444 * PARAMETER(S):
10445 * [I] hwnd : the edit window handle
10446 * [I] uMsg : the message that is to be processed
10447 * [I] wParam : first message parameter
10448 * [I] lParam : second message parameter
10450 * RETURN:
10452 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10454 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10457 /***
10458 * DESCRIPTION:
10459 * Subclassed edit control ANSI windproc function
10461 * PARAMETER(S):
10462 * [I] hwnd : the edit window handle
10463 * [I] uMsg : the message that is to be processed
10464 * [I] wParam : first message parameter
10465 * [I] lParam : second message parameter
10467 * RETURN:
10469 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10471 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10474 /***
10475 * DESCRIPTION:
10476 * Creates a subclassed edit control
10478 * PARAMETER(S):
10479 * [I] infoPtr : valid pointer to the listview structure
10480 * [I] text : initial text for the edit
10481 * [I] style : the window style
10482 * [I] isW : TRUE if input is Unicode
10484 * RETURN:
10486 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10487 INT x, INT y, INT width, INT height, BOOL isW)
10489 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10490 HWND hedit;
10491 SIZE sz;
10492 HDC hdc;
10493 HDC hOldFont=0;
10494 TEXTMETRICW textMetric;
10495 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10497 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10499 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10500 hdc = GetDC(infoPtr->hwndSelf);
10502 /* Select the font to get appropriate metric dimensions */
10503 if(infoPtr->hFont != 0)
10504 hOldFont = SelectObject(hdc, infoPtr->hFont);
10506 /*Get String Length in pixels */
10507 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10509 /*Add Extra spacing for the next character */
10510 GetTextMetricsW(hdc, &textMetric);
10511 sz.cx += (textMetric.tmMaxCharWidth * 2);
10513 if(infoPtr->hFont != 0)
10514 SelectObject(hdc, hOldFont);
10516 ReleaseDC(infoPtr->hwndSelf, hdc);
10517 if (isW)
10518 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10519 else
10520 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10522 if (!hedit) return 0;
10524 infoPtr->EditWndProc = (WNDPROC)
10525 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10526 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10528 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10530 return hedit;