push 9eb9af089d68d39110a91889d3a673043db63c4b
[wine/hacks.git] / dlls / comctl32 / listview.c
blob172925f642a965e8b8b6be35120a912f977ae5d8
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
29 * Unless otherwise noted, we believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Default Message Processing
36 * -- EN_KILLFOCUS should be handled in WM_COMMAND
37 * -- WM_CREATE: create the icon and small icon image lists at this point only if
38 * the LVS_SHAREIMAGELISTS style is not specified.
39 * -- WM_ERASEBKGND: forward this message to the parent window if the bkgnd
40 * color is CLR_NONE.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_[GS]etColumnOrderArray stubs
58 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
59 * -- LISTVIEW_SetIconSpacing is incomplete
60 * -- LISTVIEW_SortItems is broken
61 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
63 * Speedups
64 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
65 * linear in the number of items in the list, and this is
66 * unacceptable for large lists.
67 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
68 * instead of inserting in the right spot
69 * -- we should keep an ordered array of coordinates in iconic mode
70 * this would allow to frame items (iterator_frameditems),
71 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
73 * Flags
74 * -- LVIF_COLUMNS
75 * -- LVIF_GROUPID
76 * -- LVIF_NORECOMPUTE
78 * States
79 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
80 * -- LVIS_CUT
81 * -- LVIS_DROPHILITED
82 * -- LVIS_OVERLAYMASK
84 * Styles
85 * -- LVS_NOLABELWRAP
86 * -- LVS_NOSCROLL (see Q137520)
87 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
88 * -- LVS_ALIGNTOP
89 * -- LVS_TYPESTYLEMASK
91 * Extended Styles
92 * -- LVS_EX_BORDERSELECT
93 * -- LVS_EX_FLATSB
94 * -- LVS_EX_HEADERDRAGDROP
95 * -- LVS_EX_INFOTIP
96 * -- LVS_EX_LABELTIP
97 * -- LVS_EX_MULTIWORKAREAS
98 * -- LVS_EX_REGIONAL
99 * -- LVS_EX_SIMPLESELECT
100 * -- LVS_EX_TWOCLICKACTIVATE
101 * -- LVS_EX_UNDERLINECOLD
102 * -- LVS_EX_UNDERLINEHOT
104 * Notifications:
105 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
106 * -- LVN_GETINFOTIP
107 * -- LVN_HOTTRACK
108 * -- LVN_MARQUEEBEGIN
109 * -- LVN_ODFINDITEM
110 * -- LVN_SETDISPINFO
111 * -- NM_HOVER
112 * -- LVN_BEGINRDRAG
114 * Messages:
115 * -- LVM_CANCELEDITLABEL
116 * -- LVM_ENABLEGROUPVIEW
117 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
118 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
119 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
120 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
121 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
122 * -- LVM_GETINSERTMARKRECT
123 * -- LVM_GETNUMBEROFWORKAREAS
124 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
125 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
126 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
127 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
128 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
129 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
130 * -- LVM_GETVIEW, LVM_SETVIEW
131 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
132 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
133 * -- LVM_INSERTGROUPSORTED
134 * -- LVM_INSERTMARKHITTEST
135 * -- LVM_ISGROUPVIEWENABLED
136 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
137 * -- LVM_MOVEGROUP
138 * -- LVM_MOVEITEMTOGROUP
139 * -- LVM_SETINFOTIP
140 * -- LVM_SETTILEWIDTH
141 * -- LVM_SORTGROUPS
142 * -- LVM_SORTITEMSEX
144 * Macros:
145 * -- ListView_GetCheckSate, ListView_SetCheckState
146 * -- ListView_GetHoverTime, ListView_SetHoverTime
147 * -- ListView_GetISearchString
148 * -- ListView_GetNumberOfWorkAreas
149 * -- ListView_GetOrigin
150 * -- ListView_GetTextBkColor
151 * -- ListView_GetUnicodeFormat, ListView_SetUnicodeFormat
152 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
153 * -- ListView_SortItemsEx
155 * Functions:
156 * -- LVGroupComparE
158 * Known differences in message stream from native control (not known if
159 * these differences cause problems):
160 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
161 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
162 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
163 * processing for "USEDOUBLECLICKTIME".
166 #include "config.h"
167 #include "wine/port.h"
169 #include <assert.h>
170 #include <ctype.h>
171 #include <string.h>
172 #include <stdlib.h>
173 #include <stdarg.h>
174 #include <stdio.h>
176 #include "windef.h"
177 #include "winbase.h"
178 #include "winnt.h"
179 #include "wingdi.h"
180 #include "winuser.h"
181 #include "winnls.h"
182 #include "commctrl.h"
183 #include "comctl32.h"
184 #include "uxtheme.h"
186 #include "wine/debug.h"
187 #include "wine/unicode.h"
189 WINE_DEFAULT_DEBUG_CHANNEL(listview);
191 /* make sure you set this to 0 for production use! */
192 #define DEBUG_RANGES 1
194 typedef struct tagCOLUMN_INFO
196 RECT rcHeader; /* tracks the header's rectangle */
197 int fmt; /* same as LVCOLUMN.fmt */
198 } COLUMN_INFO;
200 typedef struct tagITEMHDR
202 LPWSTR pszText;
203 INT iImage;
204 } ITEMHDR, *LPITEMHDR;
206 typedef struct tagSUBITEM_INFO
208 ITEMHDR hdr;
209 INT iSubItem;
210 } SUBITEM_INFO;
212 typedef struct tagITEM_INFO
214 ITEMHDR hdr;
215 UINT state;
216 LPARAM lParam;
217 INT iIndent;
218 } ITEM_INFO;
220 typedef struct tagRANGE
222 INT lower;
223 INT upper;
224 } RANGE;
226 typedef struct tagRANGES
228 HDPA hdpa;
229 } *RANGES;
231 typedef struct tagITERATOR
233 INT nItem;
234 INT nSpecial;
235 RANGE range;
236 RANGES ranges;
237 INT index;
238 } ITERATOR;
240 typedef struct tagDELAYED_ITEM_EDIT
242 BOOL fEnabled;
243 INT iItem;
244 } DELAYED_ITEM_EDIT;
246 typedef struct tagLISTVIEW_INFO
248 HWND hwndSelf;
249 HBRUSH hBkBrush;
250 COLORREF clrBk;
251 COLORREF clrText;
252 COLORREF clrTextBk;
253 HIMAGELIST himlNormal;
254 HIMAGELIST himlSmall;
255 HIMAGELIST himlState;
256 BOOL bLButtonDown;
257 BOOL bRButtonDown;
258 BOOL bDragging;
259 POINT ptClickPos; /* point where the user clicked */
260 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
261 INT nItemHeight;
262 INT nItemWidth;
263 RANGES selectionRanges;
264 INT nSelectionMark;
265 INT nHotItem;
266 SHORT notifyFormat;
267 HWND hwndNotify;
268 RECT rcList; /* This rectangle is really the window
269 * client rectangle possibly reduced by the
270 * horizontal scroll bar and/or header - see
271 * LISTVIEW_UpdateSize. This rectangle offset
272 * by the LISTVIEW_GetOrigin value is in
273 * client coordinates */
274 SIZE iconSize;
275 SIZE iconSpacing;
276 SIZE iconStateSize;
277 UINT uCallbackMask;
278 HWND hwndHeader;
279 HCURSOR hHotCursor;
280 HFONT hDefaultFont;
281 HFONT hFont;
282 INT ntmHeight; /* Some cached metrics of the font used */
283 INT ntmMaxCharWidth; /* by the listview to draw items */
284 INT nEllipsisWidth;
285 BOOL bRedraw; /* Turns on/off repaints & invalidations */
286 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
287 BOOL bFocus;
288 BOOL bDoChangeNotify; /* send change notification messages? */
289 INT nFocusedItem;
290 RECT rcFocus;
291 DWORD dwStyle; /* the cached window GWL_STYLE */
292 DWORD dwLvExStyle; /* extended listview style */
293 INT nItemCount; /* the number of items in the list */
294 HDPA hdpaItems; /* array ITEM_INFO pointers */
295 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
296 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
297 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
298 POINT currIconPos; /* this is the position next icon will be placed */
299 PFNLVCOMPARE pfnCompare;
300 LPARAM lParamSort;
301 HWND hwndEdit;
302 WNDPROC EditWndProc;
303 INT nEditLabelItem;
304 DWORD dwHoverTime;
305 HWND hwndToolTip;
307 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
309 DWORD lastKeyPressTimestamp;
310 WPARAM charCode;
311 INT nSearchParamLength;
312 WCHAR szSearchParam[ MAX_PATH ];
313 BOOL bIsDrawing;
314 INT nMeasureItemHeight;
315 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
316 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
317 } LISTVIEW_INFO;
320 * constants
322 /* How many we debug buffer to allocate */
323 #define DEBUG_BUFFERS 20
324 /* The size of a single debug bbuffer */
325 #define DEBUG_BUFFER_SIZE 256
327 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
328 #define SB_INTERNAL -1
330 /* maximum size of a label */
331 #define DISP_TEXT_SIZE 512
333 /* padding for items in list and small icon display modes */
334 #define WIDTH_PADDING 12
336 /* padding for items in list, report and small icon display modes */
337 #define HEIGHT_PADDING 1
339 /* offset of items in report display mode */
340 #define REPORT_MARGINX 2
342 /* padding for icon in large icon display mode
343 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
344 * that HITTEST will see.
345 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
346 * ICON_TOP_PADDING - sum of the two above.
347 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
348 * LABEL_HOR_PADDING - between text and sides of box
349 * LABEL_VERT_PADDING - between bottom of text and end of box
351 * ICON_LR_PADDING - additional width above icon size.
352 * ICON_LR_HALF - half of the above value
354 #define ICON_TOP_PADDING_NOTHITABLE 2
355 #define ICON_TOP_PADDING_HITABLE 2
356 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
357 #define ICON_BOTTOM_PADDING 4
358 #define LABEL_HOR_PADDING 5
359 #define LABEL_VERT_PADDING 7
360 #define ICON_LR_PADDING 16
361 #define ICON_LR_HALF (ICON_LR_PADDING/2)
363 /* default label width for items in list and small icon display modes */
364 #define DEFAULT_LABEL_WIDTH 40
366 /* default column width for items in list display mode */
367 #define DEFAULT_COLUMN_WIDTH 128
369 /* Size of "line" scroll for V & H scrolls */
370 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
372 /* Padding between image and label */
373 #define IMAGE_PADDING 2
375 /* Padding behind the label */
376 #define TRAILING_LABEL_PADDING 12
377 #define TRAILING_HEADER_PADDING 11
379 /* Border for the icon caption */
380 #define CAPTION_BORDER 2
382 /* Standard DrawText flags */
383 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
384 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
385 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
387 /* Image index from state */
388 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
390 /* The time in milliseconds to reset the search in the list */
391 #define KEY_DELAY 450
393 /* Dump the LISTVIEW_INFO structure to the debug channel */
394 #define LISTVIEW_DUMP(iP) do { \
395 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
396 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
397 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
398 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
399 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
400 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
401 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
402 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
403 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
404 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
405 } while(0)
407 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
410 * forward declarations
412 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
413 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
414 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
415 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
416 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
417 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
418 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
419 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
420 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
421 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, LVITEMW *, BOOL);
422 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *);
423 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
424 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
425 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
426 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *, WPARAM, LPARAM);
427 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
428 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
429 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
430 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
431 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
432 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
433 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
434 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *);
435 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
436 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
437 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
438 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
440 /******** Text handling functions *************************************/
442 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
443 * text string. The string may be ANSI or Unicode, in which case
444 * the boolean isW tells us the type of the string.
446 * The name of the function tell what type of strings it expects:
447 * W: Unicode, T: ANSI/Unicode - function of isW
450 static inline BOOL is_textW(LPCWSTR text)
452 return text != NULL && text != LPSTR_TEXTCALLBACKW;
455 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
457 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
458 return is_textW(text);
461 static inline int textlenT(LPCWSTR text, BOOL isW)
463 return !is_textT(text, isW) ? 0 :
464 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
467 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
469 if (isDestW)
470 if (isSrcW) lstrcpynW(dest, src, max);
471 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
472 else
473 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
474 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
477 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
479 LPWSTR wstr = (LPWSTR)text;
481 if (!isW && is_textT(text, isW))
483 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
484 wstr = Alloc(len * sizeof(WCHAR));
485 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
487 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
488 return wstr;
491 static inline void textfreeT(LPWSTR wstr, BOOL isW)
493 if (!isW && is_textT(wstr, isW)) Free (wstr);
497 * dest is a pointer to a Unicode string
498 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
500 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
502 BOOL bResult = TRUE;
504 if (src == LPSTR_TEXTCALLBACKW)
506 if (is_textW(*dest)) Free(*dest);
507 *dest = LPSTR_TEXTCALLBACKW;
509 else
511 LPWSTR pszText = textdupTtoW(src, isW);
512 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
513 bResult = Str_SetPtrW(dest, pszText);
514 textfreeT(pszText, isW);
516 return bResult;
520 * compares a Unicode to a Unicode/ANSI text string
522 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
524 if (!aw) return bt ? -1 : 0;
525 if (!bt) return aw ? 1 : 0;
526 if (aw == LPSTR_TEXTCALLBACKW)
527 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
528 if (bt != LPSTR_TEXTCALLBACKW)
530 LPWSTR bw = textdupTtoW(bt, isW);
531 int r = bw ? lstrcmpW(aw, bw) : 1;
532 textfreeT(bw, isW);
533 return r;
536 return 1;
539 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
541 int res;
543 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
544 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
545 return res ? res - sizeof(WCHAR) : res;
548 /******** Debugging functions *****************************************/
550 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
552 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
553 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
556 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
558 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
559 n = min(textlenT(text, isW), n);
560 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
563 static char* debug_getbuf(void)
565 static int index = 0;
566 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
567 return buffers[index++ % DEBUG_BUFFERS];
570 static inline const char* debugrange(const RANGE *lprng)
572 if (!lprng) return "(null)";
573 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
576 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
578 char* buf = debug_getbuf(), *text = buf;
579 int len, size = DEBUG_BUFFER_SIZE;
581 if (pScrollInfo == NULL) return "(null)";
582 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
583 if (len == -1) goto end; buf += len; size -= len;
584 if (pScrollInfo->fMask & SIF_RANGE)
585 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
586 else len = 0;
587 if (len == -1) goto end; buf += len; size -= len;
588 if (pScrollInfo->fMask & SIF_PAGE)
589 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
590 else len = 0;
591 if (len == -1) goto end; buf += len; size -= len;
592 if (pScrollInfo->fMask & SIF_POS)
593 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
594 else len = 0;
595 if (len == -1) goto end; buf += len; size -= len;
596 if (pScrollInfo->fMask & SIF_TRACKPOS)
597 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
598 else len = 0;
599 if (len == -1) goto end; buf += len; size -= len;
600 goto undo;
601 end:
602 buf = text + strlen(text);
603 undo:
604 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
605 return text;
608 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
610 if (!plvnm) return "(null)";
611 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
612 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
613 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
614 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
617 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
619 char* buf = debug_getbuf(), *text = buf;
620 int len, size = DEBUG_BUFFER_SIZE;
622 if (lpLVItem == NULL) return "(null)";
623 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
624 if (len == -1) goto end; buf += len; size -= len;
625 if (lpLVItem->mask & LVIF_STATE)
626 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
627 else len = 0;
628 if (len == -1) goto end; buf += len; size -= len;
629 if (lpLVItem->mask & LVIF_TEXT)
630 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
631 else len = 0;
632 if (len == -1) goto end; buf += len; size -= len;
633 if (lpLVItem->mask & LVIF_IMAGE)
634 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
635 else len = 0;
636 if (len == -1) goto end; buf += len; size -= len;
637 if (lpLVItem->mask & LVIF_PARAM)
638 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
639 else len = 0;
640 if (len == -1) goto end; buf += len; size -= len;
641 if (lpLVItem->mask & LVIF_INDENT)
642 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
643 else len = 0;
644 if (len == -1) goto end; buf += len; size -= len;
645 goto undo;
646 end:
647 buf = text + strlen(text);
648 undo:
649 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
650 return text;
653 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
655 char* buf = debug_getbuf(), *text = buf;
656 int len, size = DEBUG_BUFFER_SIZE;
658 if (lpColumn == NULL) return "(null)";
659 len = snprintf(buf, size, "{");
660 if (len == -1) goto end; buf += len; size -= len;
661 if (lpColumn->mask & LVCF_SUBITEM)
662 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
663 else len = 0;
664 if (len == -1) goto end; buf += len; size -= len;
665 if (lpColumn->mask & LVCF_FMT)
666 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
667 else len = 0;
668 if (len == -1) goto end; buf += len; size -= len;
669 if (lpColumn->mask & LVCF_WIDTH)
670 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
671 else len = 0;
672 if (len == -1) goto end; buf += len; size -= len;
673 if (lpColumn->mask & LVCF_TEXT)
674 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
675 else len = 0;
676 if (len == -1) goto end; buf += len; size -= len;
677 if (lpColumn->mask & LVCF_IMAGE)
678 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
679 else len = 0;
680 if (len == -1) goto end; buf += len; size -= len;
681 if (lpColumn->mask & LVCF_ORDER)
682 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
683 else len = 0;
684 if (len == -1) goto end; buf += len; size -= len;
685 goto undo;
686 end:
687 buf = text + strlen(text);
688 undo:
689 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
690 return text;
693 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
695 if (!lpht) return "(null)";
697 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
698 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
701 /* Return the corresponding text for a given scroll value */
702 static inline LPCSTR debugscrollcode(int nScrollCode)
704 switch(nScrollCode)
706 case SB_LINELEFT: return "SB_LINELEFT";
707 case SB_LINERIGHT: return "SB_LINERIGHT";
708 case SB_PAGELEFT: return "SB_PAGELEFT";
709 case SB_PAGERIGHT: return "SB_PAGERIGHT";
710 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
711 case SB_THUMBTRACK: return "SB_THUMBTRACK";
712 case SB_ENDSCROLL: return "SB_ENDSCROLL";
713 case SB_INTERNAL: return "SB_INTERNAL";
714 default: return "unknown";
719 /******** Notification functions i************************************/
721 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
723 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
724 (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
727 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
729 LRESULT result;
731 TRACE("(code=%d)\n", code);
733 pnmh->hwndFrom = infoPtr->hwndSelf;
734 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
735 pnmh->code = code;
736 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
738 TRACE(" <= %ld\n", result);
740 return result;
743 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
745 NMHDR nmh;
746 HWND hwnd = infoPtr->hwndSelf;
747 notify_hdr(infoPtr, code, &nmh);
748 return IsWindow(hwnd);
751 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
753 NMITEMACTIVATE nmia;
754 LVITEMW item;
756 if (htInfo) {
757 nmia.uNewState = 0;
758 nmia.uOldState = 0;
759 nmia.uChanged = 0;
760 nmia.uKeyFlags = 0;
762 item.mask = LVIF_PARAM|LVIF_STATE;
763 item.iItem = htInfo->iItem;
764 item.iSubItem = 0;
765 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
766 nmia.lParam = item.lParam;
767 nmia.uOldState = item.state;
768 nmia.uNewState = item.state | LVIS_ACTIVATING;
769 nmia.uChanged = LVIF_STATE;
772 nmia.iItem = htInfo->iItem;
773 nmia.iSubItem = htInfo->iSubItem;
774 nmia.ptAction = htInfo->pt;
776 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
777 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
778 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
780 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
783 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
785 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
786 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
789 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
791 NMLISTVIEW nmlv;
792 LVITEMW item;
793 HWND hwnd = infoPtr->hwndSelf;
795 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
796 ZeroMemory(&nmlv, sizeof(nmlv));
797 nmlv.iItem = lvht->iItem;
798 nmlv.iSubItem = lvht->iSubItem;
799 nmlv.ptAction = lvht->pt;
800 item.mask = LVIF_PARAM;
801 item.iItem = lvht->iItem;
802 item.iSubItem = 0;
803 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
804 notify_listview(infoPtr, code, &nmlv);
805 return IsWindow(hwnd);
808 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
810 NMLISTVIEW nmlv;
811 LVITEMW item;
812 HWND hwnd = infoPtr->hwndSelf;
814 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
815 nmlv.iItem = nItem;
816 item.mask = LVIF_PARAM;
817 item.iItem = nItem;
818 item.iSubItem = 0;
819 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
820 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
821 return IsWindow(hwnd);
824 static int get_ansi_notification(UINT unicodeNotificationCode)
826 switch (unicodeNotificationCode)
828 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
829 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
830 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
831 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
832 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
833 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
835 ERR("unknown notification %x\n", unicodeNotificationCode);
836 assert(FALSE);
837 return 0;
841 Send notification. depends on dispinfoW having same
842 structure as dispinfoA.
843 infoPtr : listview struct
844 notificationCode : *Unicode* notification code
845 pdi : dispinfo structure (can be unicode or ansi)
846 isW : TRUE if dispinfo is Unicode
848 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
850 BOOL bResult = FALSE;
851 BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
852 INT cchTempBufMax = 0, savCchTextMax = 0;
853 UINT realNotifCode;
854 LPWSTR pszTempBuf = NULL, savPszText = NULL;
856 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
858 convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
859 convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
862 if (convertToAnsi || convertToUnicode)
864 if (notificationCode != LVN_GETDISPINFOW)
866 cchTempBufMax = convertToUnicode ?
867 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
868 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
870 else
872 cchTempBufMax = pdi->item.cchTextMax;
873 *pdi->item.pszText = 0; /* make sure we don't process garbage */
876 pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
877 if (!pszTempBuf) return FALSE;
879 if (convertToUnicode)
880 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
881 pszTempBuf, cchTempBufMax);
882 else
883 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
884 cchTempBufMax, NULL, NULL);
886 savCchTextMax = pdi->item.cchTextMax;
887 savPszText = pdi->item.pszText;
888 pdi->item.pszText = pszTempBuf;
889 pdi->item.cchTextMax = cchTempBufMax;
892 if (infoPtr->notifyFormat == NFR_ANSI)
893 realNotifCode = get_ansi_notification(notificationCode);
894 else
895 realNotifCode = notificationCode;
896 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
897 bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
899 if (convertToUnicode || convertToAnsi)
901 if (convertToUnicode) /* note : pointer can be changed by app ! */
902 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
903 savCchTextMax, NULL, NULL);
904 else
905 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
906 savPszText, savCchTextMax);
907 pdi->item.pszText = savPszText; /* restores our buffer */
908 pdi->item.cchTextMax = savCchTextMax;
909 Free (pszTempBuf);
911 return bResult;
914 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
915 const RECT *rcBounds, const LVITEMW *lplvItem)
917 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
918 lpnmlvcd->nmcd.hdc = hdc;
919 lpnmlvcd->nmcd.rc = *rcBounds;
920 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
921 lpnmlvcd->clrText = infoPtr->clrText;
922 if (!lplvItem) return;
923 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
924 lpnmlvcd->iSubItem = lplvItem->iSubItem;
925 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
926 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
927 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
928 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
931 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
933 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
934 DWORD result;
936 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
937 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
938 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
939 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
940 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
941 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
942 return result;
945 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
947 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
948 lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
949 if (lpnmlvcd->clrText == CLR_DEFAULT)
950 lpnmlvcd->clrText = comctl32_color.clrWindowText;
952 /* apparently, for selected items, we have to override the returned values */
953 if (!SubItem)
955 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
957 if (infoPtr->bFocus)
959 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
960 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
962 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
964 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
965 lpnmlvcd->clrText = comctl32_color.clrBtnText;
970 /* Set the text attributes */
971 if (lpnmlvcd->clrTextBk != CLR_NONE)
973 SetBkMode(hdc, OPAQUE);
974 SetBkColor(hdc,lpnmlvcd->clrTextBk);
976 else
977 SetBkMode(hdc, TRANSPARENT);
978 SetTextColor(hdc, lpnmlvcd->clrText);
981 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
983 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
986 /******** Item iterator functions **********************************/
988 static RANGES ranges_create(int count);
989 static void ranges_destroy(RANGES ranges);
990 static BOOL ranges_add(RANGES ranges, RANGE range);
991 static BOOL ranges_del(RANGES ranges, RANGE range);
992 static void ranges_dump(RANGES ranges);
994 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
996 RANGE range = { nItem, nItem + 1 };
998 return ranges_add(ranges, range);
1001 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1003 RANGE range = { nItem, nItem + 1 };
1005 return ranges_del(ranges, range);
1008 /***
1009 * ITERATOR DOCUMENTATION
1011 * The iterator functions allow for easy, and convenient iteration
1012 * over items of interest in the list. Typically, you create a
1013 * iterator, use it, and destroy it, as such:
1014 * ITERATOR i;
1016 * iterator_xxxitems(&i, ...);
1017 * while (iterator_{prev,next}(&i)
1019 * //code which uses i.nItem
1021 * iterator_destroy(&i);
1023 * where xxx is either: framed, or visible.
1024 * Note that it is important that the code destroys the iterator
1025 * after it's done with it, as the creation of the iterator may
1026 * allocate memory, which thus needs to be freed.
1028 * You can iterate both forwards, and backwards through the list,
1029 * by using iterator_next or iterator_prev respectively.
1031 * Lower numbered items are draw on top of higher number items in
1032 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1033 * items may overlap). So, to test items, you should use
1034 * iterator_next
1035 * which lists the items top to bottom (in Z-order).
1036 * For drawing items, you should use
1037 * iterator_prev
1038 * which lists the items bottom to top (in Z-order).
1039 * If you keep iterating over the items after the end-of-items
1040 * marker (-1) is returned, the iterator will start from the
1041 * beginning. Typically, you don't need to test for -1,
1042 * because iterator_{next,prev} will return TRUE if more items
1043 * are to be iterated over, or FALSE otherwise.
1045 * Note: the iterator is defined to be bidirectional. That is,
1046 * any number of prev followed by any number of next, or
1047 * five versa, should leave the iterator at the same item:
1048 * prev * n, next * n = next * n, prev * n
1050 * The iterator has a notion of an out-of-order, special item,
1051 * which sits at the start of the list. This is used in
1052 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1053 * which needs to be first, as it may overlap other items.
1055 * The code is a bit messy because we have:
1056 * - a special item to deal with
1057 * - simple range, or composite range
1058 * - empty range.
1059 * If you find bugs, or want to add features, please make sure you
1060 * always check/modify *both* iterator_prev, and iterator_next.
1063 /****
1064 * This function iterates through the items in increasing order,
1065 * but prefixed by the special item, then -1. That is:
1066 * special, 1, 2, 3, ..., n, -1.
1067 * Each item is listed only once.
1069 static inline BOOL iterator_next(ITERATOR* i)
1071 if (i->nItem == -1)
1073 i->nItem = i->nSpecial;
1074 if (i->nItem != -1) return TRUE;
1076 if (i->nItem == i->nSpecial)
1078 if (i->ranges) i->index = 0;
1079 goto pickarange;
1082 i->nItem++;
1083 testitem:
1084 if (i->nItem == i->nSpecial) i->nItem++;
1085 if (i->nItem < i->range.upper) return TRUE;
1087 pickarange:
1088 if (i->ranges)
1090 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1091 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1092 else goto end;
1094 else if (i->nItem >= i->range.upper) goto end;
1096 i->nItem = i->range.lower;
1097 if (i->nItem >= 0) goto testitem;
1098 end:
1099 i->nItem = -1;
1100 return FALSE;
1103 /****
1104 * This function iterates through the items in decreasing order,
1105 * followed by the special item, then -1. That is:
1106 * n, n-1, ..., 3, 2, 1, special, -1.
1107 * Each item is listed only once.
1109 static inline BOOL iterator_prev(ITERATOR* i)
1111 BOOL start = FALSE;
1113 if (i->nItem == -1)
1115 start = TRUE;
1116 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1117 goto pickarange;
1119 if (i->nItem == i->nSpecial)
1121 i->nItem = -1;
1122 return FALSE;
1125 testitem:
1126 i->nItem--;
1127 if (i->nItem == i->nSpecial) i->nItem--;
1128 if (i->nItem >= i->range.lower) return TRUE;
1130 pickarange:
1131 if (i->ranges)
1133 if (i->index > 0)
1134 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1135 else goto end;
1137 else if (!start && i->nItem < i->range.lower) goto end;
1139 i->nItem = i->range.upper;
1140 if (i->nItem > 0) goto testitem;
1141 end:
1142 return (i->nItem = i->nSpecial) != -1;
1145 static RANGE iterator_range(const ITERATOR *i)
1147 RANGE range;
1149 if (!i->ranges) return i->range;
1151 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1153 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1154 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1156 else range.lower = range.upper = 0;
1158 return range;
1161 /***
1162 * Releases resources associated with this ierator.
1164 static inline void iterator_destroy(const ITERATOR *i)
1166 ranges_destroy(i->ranges);
1169 /***
1170 * Create an empty iterator.
1172 static inline BOOL iterator_empty(ITERATOR* i)
1174 ZeroMemory(i, sizeof(*i));
1175 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1176 return TRUE;
1179 /***
1180 * Create an iterator over a range.
1182 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1184 iterator_empty(i);
1185 i->range = range;
1186 return TRUE;
1189 /***
1190 * Create an iterator over a bunch of ranges.
1191 * Please note that the iterator will take ownership of the ranges,
1192 * and will free them upon destruction.
1194 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1196 iterator_empty(i);
1197 i->ranges = ranges;
1198 return TRUE;
1201 /***
1202 * Creates an iterator over the items which intersect lprc.
1204 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1206 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1207 RECT frame = *lprc, rcItem, rcTemp;
1208 POINT Origin;
1210 /* in case we fail, we want to return an empty iterator */
1211 if (!iterator_empty(i)) return FALSE;
1213 LISTVIEW_GetOrigin(infoPtr, &Origin);
1215 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1216 OffsetRect(&frame, -Origin.x, -Origin.y);
1218 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1220 INT nItem;
1222 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1224 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1225 if (IntersectRect(&rcTemp, &rcItem, lprc))
1226 i->nSpecial = infoPtr->nFocusedItem;
1228 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1229 /* to do better here, we need to have PosX, and PosY sorted */
1230 TRACE("building icon ranges:\n");
1231 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1233 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1234 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1235 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1236 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1237 if (IntersectRect(&rcTemp, &rcItem, &frame))
1238 ranges_additem(i->ranges, nItem);
1240 return TRUE;
1242 else if (uView == LVS_REPORT)
1244 RANGE range;
1246 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1247 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1249 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1250 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1251 if (range.upper <= range.lower) return TRUE;
1252 if (!iterator_rangeitems(i, range)) return FALSE;
1253 TRACE(" report=%s\n", debugrange(&i->range));
1255 else
1257 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1258 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1259 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1260 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1261 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1262 INT lower = nFirstCol * nPerCol + nFirstRow;
1263 RANGE item_range;
1264 INT nCol;
1266 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1267 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1269 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1271 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1272 TRACE("building list ranges:\n");
1273 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1275 item_range.lower = nCol * nPerCol + nFirstRow;
1276 if(item_range.lower >= infoPtr->nItemCount) break;
1277 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1278 TRACE(" list=%s\n", debugrange(&item_range));
1279 ranges_add(i->ranges, item_range);
1283 return TRUE;
1286 /***
1287 * Creates an iterator over the items which intersect the visible region of hdc.
1289 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1291 POINT Origin, Position;
1292 RECT rcItem, rcClip;
1293 INT rgntype;
1295 rgntype = GetClipBox(hdc, &rcClip);
1296 if (rgntype == NULLREGION) return iterator_empty(i);
1297 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1298 if (rgntype == SIMPLEREGION) return TRUE;
1300 /* first deal with the special item */
1301 if (i->nSpecial != -1)
1303 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1304 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1307 /* if we can't deal with the region, we'll just go with the simple range */
1308 LISTVIEW_GetOrigin(infoPtr, &Origin);
1309 TRACE("building visible range:\n");
1310 if (!i->ranges && i->range.lower < i->range.upper)
1312 if (!(i->ranges = ranges_create(50))) return TRUE;
1313 if (!ranges_add(i->ranges, i->range))
1315 ranges_destroy(i->ranges);
1316 i->ranges = 0;
1317 return TRUE;
1321 /* now delete the invisible items from the list */
1322 while(iterator_next(i))
1324 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1325 rcItem.left = Position.x + Origin.x;
1326 rcItem.top = Position.y + Origin.y;
1327 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1328 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1329 if (!RectVisible(hdc, &rcItem))
1330 ranges_delitem(i->ranges, i->nItem);
1332 /* the iterator should restart on the next iterator_next */
1333 TRACE("done\n");
1335 return TRUE;
1338 /******** Misc helper functions ************************************/
1340 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1341 WPARAM wParam, LPARAM lParam, BOOL isW)
1343 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1344 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1347 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1349 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1351 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1352 (uView == LVS_ICON || uView == LVS_SMALLICON);
1355 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1357 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1358 if(state == 1 || state == 2)
1360 LVITEMW lvitem;
1361 state ^= 3;
1362 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1363 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1364 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1368 /******** Internal API functions ************************************/
1370 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1372 static COLUMN_INFO mainItem;
1374 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1375 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1376 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1379 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1381 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1384 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1386 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1389 /* Listview invalidation functions: use _only_ these functions to invalidate */
1391 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1393 return infoPtr->bRedraw;
1396 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1398 if(!is_redrawing(infoPtr)) return;
1399 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1400 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1403 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1405 RECT rcBox;
1407 if(!is_redrawing(infoPtr)) return;
1408 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1409 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1412 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1414 POINT Origin, Position;
1415 RECT rcBox;
1417 if(!is_redrawing(infoPtr)) return;
1418 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1419 LISTVIEW_GetOrigin(infoPtr, &Origin);
1420 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1421 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1422 rcBox.top = 0;
1423 rcBox.bottom = infoPtr->nItemHeight;
1424 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1425 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1428 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1430 LISTVIEW_InvalidateRect(infoPtr, NULL);
1433 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1435 RECT rcCol;
1437 if(!is_redrawing(infoPtr)) return;
1438 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1439 rcCol.top = infoPtr->rcList.top;
1440 rcCol.bottom = infoPtr->rcList.bottom;
1441 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1444 /***
1445 * DESCRIPTION:
1446 * Retrieves the number of items that can fit vertically in the client area.
1448 * PARAMETER(S):
1449 * [I] infoPtr : valid pointer to the listview structure
1451 * RETURN:
1452 * Number of items per row.
1454 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1456 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1458 return max(nListWidth/infoPtr->nItemWidth, 1);
1461 /***
1462 * DESCRIPTION:
1463 * Retrieves the number of items that can fit horizontally in the client
1464 * area.
1466 * PARAMETER(S):
1467 * [I] infoPtr : valid pointer to the listview structure
1469 * RETURN:
1470 * Number of items per column.
1472 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1474 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1476 return max(nListHeight / infoPtr->nItemHeight, 1);
1480 /*************************************************************************
1481 * LISTVIEW_ProcessLetterKeys
1483 * Processes keyboard messages generated by pressing the letter keys
1484 * on the keyboard.
1485 * What this does is perform a case insensitive search from the
1486 * current position with the following quirks:
1487 * - If two chars or more are pressed in quick succession we search
1488 * for the corresponding string (e.g. 'abc').
1489 * - If there is a delay we wipe away the current search string and
1490 * restart with just that char.
1491 * - If the user keeps pressing the same character, whether slowly or
1492 * fast, so that the search string is entirely composed of this
1493 * character ('aaaaa' for instance), then we search for first item
1494 * that starting with that character.
1495 * - If the user types the above character in quick succession, then
1496 * we must also search for the corresponding string ('aaaaa'), and
1497 * go to that string if there is a match.
1499 * PARAMETERS
1500 * [I] hwnd : handle to the window
1501 * [I] charCode : the character code, the actual character
1502 * [I] keyData : key data
1504 * RETURNS
1506 * Zero.
1508 * BUGS
1510 * - The current implementation has a list of characters it will
1511 * accept and it ignores everything else. In particular it will
1512 * ignore accentuated characters which seems to match what
1513 * Windows does. But I'm not sure it makes sense to follow
1514 * Windows there.
1515 * - We don't sound a beep when the search fails.
1517 * SEE ALSO
1519 * TREEVIEW_ProcessLetterKeys
1521 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1523 INT nItem;
1524 INT endidx,idx;
1525 LVITEMW item;
1526 WCHAR buffer[MAX_PATH];
1527 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1529 /* simple parameter checking */
1530 if (!charCode || !keyData) return 0;
1532 /* only allow the valid WM_CHARs through */
1533 if (!isalnum(charCode) &&
1534 charCode != '.' && charCode != '`' && charCode != '!' &&
1535 charCode != '@' && charCode != '#' && charCode != '$' &&
1536 charCode != '%' && charCode != '^' && charCode != '&' &&
1537 charCode != '*' && charCode != '(' && charCode != ')' &&
1538 charCode != '-' && charCode != '_' && charCode != '+' &&
1539 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1540 charCode != '}' && charCode != '[' && charCode != '{' &&
1541 charCode != '/' && charCode != '?' && charCode != '>' &&
1542 charCode != '<' && charCode != ',' && charCode != '~')
1543 return 0;
1545 /* if there's one item or less, there is no where to go */
1546 if (infoPtr->nItemCount <= 1) return 0;
1548 /* update the search parameters */
1549 infoPtr->lastKeyPressTimestamp = GetTickCount();
1550 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1551 if (infoPtr->nSearchParamLength < MAX_PATH)
1552 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1553 if (infoPtr->charCode != charCode)
1554 infoPtr->charCode = charCode = 0;
1555 } else {
1556 infoPtr->charCode=charCode;
1557 infoPtr->szSearchParam[0]=charCode;
1558 infoPtr->nSearchParamLength=1;
1559 /* Redundant with the 1 char string */
1560 charCode=0;
1563 /* and search from the current position */
1564 nItem=-1;
1565 if (infoPtr->nFocusedItem >= 0) {
1566 endidx=infoPtr->nFocusedItem;
1567 idx=endidx;
1568 /* if looking for single character match,
1569 * then we must always move forward
1571 if (infoPtr->nSearchParamLength == 1)
1572 idx++;
1573 } else {
1574 endidx=infoPtr->nItemCount;
1575 idx=0;
1577 do {
1578 if (idx == infoPtr->nItemCount) {
1579 if (endidx == infoPtr->nItemCount || endidx == 0)
1580 break;
1581 idx=0;
1584 /* get item */
1585 item.mask = LVIF_TEXT;
1586 item.iItem = idx;
1587 item.iSubItem = 0;
1588 item.pszText = buffer;
1589 item.cchTextMax = MAX_PATH;
1590 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1592 /* check for a match */
1593 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1594 nItem=idx;
1595 break;
1596 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1597 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1598 /* This would work but we must keep looking for a longer match */
1599 nItem=idx;
1601 idx++;
1602 } while (idx != endidx);
1604 if (nItem != -1)
1605 LISTVIEW_KeySelection(infoPtr, nItem);
1607 return 0;
1610 /*************************************************************************
1611 * LISTVIEW_UpdateHeaderSize [Internal]
1613 * Function to resize the header control
1615 * PARAMS
1616 * [I] hwnd : handle to a window
1617 * [I] nNewScrollPos : scroll pos to set
1619 * RETURNS
1620 * None.
1622 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1624 RECT winRect;
1625 POINT point[2];
1627 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1629 GetWindowRect(infoPtr->hwndHeader, &winRect);
1630 point[0].x = winRect.left;
1631 point[0].y = winRect.top;
1632 point[1].x = winRect.right;
1633 point[1].y = winRect.bottom;
1635 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1636 point[0].x = -nNewScrollPos;
1637 point[1].x += nNewScrollPos;
1639 SetWindowPos(infoPtr->hwndHeader,0,
1640 point[0].x,point[0].y,point[1].x,point[1].y,
1641 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1642 SWP_NOZORDER | SWP_NOACTIVATE);
1645 /***
1646 * DESCRIPTION:
1647 * Update the scrollbars. This functions should be called whenever
1648 * the content, size or view changes.
1650 * PARAMETER(S):
1651 * [I] infoPtr : valid pointer to the listview structure
1653 * RETURN:
1654 * None
1656 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1658 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1659 SCROLLINFO horzInfo, vertInfo;
1660 INT dx, dy;
1662 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1664 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1665 horzInfo.cbSize = sizeof(SCROLLINFO);
1666 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1668 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1669 if (uView == LVS_LIST)
1671 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1672 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1674 /* scroll by at least one column per page */
1675 if(horzInfo.nPage < infoPtr->nItemWidth)
1676 horzInfo.nPage = infoPtr->nItemWidth;
1678 horzInfo.nPage /= infoPtr->nItemWidth;
1680 else if (uView == LVS_REPORT)
1682 horzInfo.nMax = infoPtr->nItemWidth;
1684 else /* LVS_ICON, or LVS_SMALLICON */
1686 RECT rcView;
1688 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1691 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1692 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1693 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1694 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1695 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1697 /* Setting the horizontal scroll can change the listview size
1698 * (and potentially everything else) so we need to recompute
1699 * everything again for the vertical scroll
1702 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1703 vertInfo.cbSize = sizeof(SCROLLINFO);
1704 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1706 if (uView == LVS_REPORT)
1708 vertInfo.nMax = infoPtr->nItemCount;
1710 /* scroll by at least one page */
1711 if(vertInfo.nPage < infoPtr->nItemHeight)
1712 vertInfo.nPage = infoPtr->nItemHeight;
1714 if (infoPtr->nItemHeight > 0)
1715 vertInfo.nPage /= infoPtr->nItemHeight;
1717 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1719 RECT rcView;
1721 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1724 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1725 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1726 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1727 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1728 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1730 /* Change of the range may have changed the scroll pos. If so move the content */
1731 if (dx != 0 || dy != 0)
1733 RECT listRect;
1734 listRect = infoPtr->rcList;
1735 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1736 SW_ERASE | SW_INVALIDATE);
1739 /* Update the Header Control */
1740 if (uView == LVS_REPORT)
1742 horzInfo.fMask = SIF_POS;
1743 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1744 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1749 /***
1750 * DESCRIPTION:
1751 * Shows/hides the focus rectangle.
1753 * PARAMETER(S):
1754 * [I] infoPtr : valid pointer to the listview structure
1755 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1757 * RETURN:
1758 * None
1760 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1762 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1763 HDC hdc;
1765 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1767 if (infoPtr->nFocusedItem < 0) return;
1769 /* we need some gymnastics in ICON mode to handle large items */
1770 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1772 RECT rcBox;
1774 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1775 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1777 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1778 return;
1782 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1784 /* for some reason, owner draw should work only in report mode */
1785 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1787 DRAWITEMSTRUCT dis;
1788 LVITEMW item;
1790 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1791 HFONT hOldFont = SelectObject(hdc, hFont);
1793 item.iItem = infoPtr->nFocusedItem;
1794 item.iSubItem = 0;
1795 item.mask = LVIF_PARAM;
1796 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1798 ZeroMemory(&dis, sizeof(dis));
1799 dis.CtlType = ODT_LISTVIEW;
1800 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1801 dis.itemID = item.iItem;
1802 dis.itemAction = ODA_FOCUS;
1803 if (fShow) dis.itemState |= ODS_FOCUS;
1804 dis.hwndItem = infoPtr->hwndSelf;
1805 dis.hDC = hdc;
1806 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1807 dis.itemData = item.lParam;
1809 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1811 SelectObject(hdc, hOldFont);
1813 else
1815 DrawFocusRect(hdc, &infoPtr->rcFocus);
1817 done:
1818 ReleaseDC(infoPtr->hwndSelf, hdc);
1821 /***
1822 * Invalidates all visible selected items.
1824 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
1826 ITERATOR i;
1828 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1829 while(iterator_next(&i))
1831 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1832 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1834 iterator_destroy(&i);
1838 /***
1839 * DESCRIPTION: [INTERNAL]
1840 * Computes an item's (left,top) corner, relative to rcView.
1841 * That is, the position has NOT been made relative to the Origin.
1842 * This is deliberate, to avoid computing the Origin over, and
1843 * over again, when this function is called in a loop. Instead,
1844 * one can factor the computation of the Origin before the loop,
1845 * and offset the value returned by this function, on every iteration.
1847 * PARAMETER(S):
1848 * [I] infoPtr : valid pointer to the listview structure
1849 * [I] nItem : item number
1850 * [O] lpptOrig : item top, left corner
1852 * RETURN:
1853 * None.
1855 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1857 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1859 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1861 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1863 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1864 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1866 else if (uView == LVS_LIST)
1868 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1869 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1870 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1872 else /* LVS_REPORT */
1874 lpptPosition->x = 0;
1875 lpptPosition->y = nItem * infoPtr->nItemHeight;
1879 /***
1880 * DESCRIPTION: [INTERNAL]
1881 * Compute the rectangles of an item. This is to localize all
1882 * the computations in one place. If you are not interested in some
1883 * of these values, simply pass in a NULL -- the function is smart
1884 * enough to compute only what's necessary. The function computes
1885 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1886 * one, the BOX rectangle. This rectangle is very cheap to compute,
1887 * and is guaranteed to contain all the other rectangles. Computing
1888 * the ICON rect is also cheap, but all the others are potentially
1889 * expensive. This gives an easy and effective optimization when
1890 * searching (like point inclusion, or rectangle intersection):
1891 * first test against the BOX, and if TRUE, test against the desired
1892 * rectangle.
1893 * If the function does not have all the necessary information
1894 * to computed the requested rectangles, will crash with a
1895 * failed assertion. This is done so we catch all programming
1896 * errors, given that the function is called only from our code.
1898 * We have the following 'special' meanings for a few fields:
1899 * * If LVIS_FOCUSED is set, we assume the item has the focus
1900 * This is important in ICON mode, where it might get a larger
1901 * then usual rectangle
1903 * Please note that subitem support works only in REPORT mode.
1905 * PARAMETER(S):
1906 * [I] infoPtr : valid pointer to the listview structure
1907 * [I] lpLVItem : item to compute the measures for
1908 * [O] lprcBox : ptr to Box rectangle
1909 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
1910 * [0] lprcSelectBox : ptr to select box rectangle
1911 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
1912 * [O] lprcIcon : ptr to Icon rectangle
1913 * Same as LVM_GETITEMRECT with LVIR_ICON
1914 * [O] lprcStateIcon: ptr to State Icon rectangle
1915 * [O] lprcLabel : ptr to Label rectangle
1916 * Same as LVM_GETITEMRECT with LVIR_LABEL
1918 * RETURN:
1919 * None.
1921 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1922 LPRECT lprcBox, LPRECT lprcSelectBox,
1923 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
1925 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1926 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1927 RECT Box, SelectBox, Icon, Label;
1928 COLUMN_INFO *lpColumnInfo = NULL;
1929 SIZE labelSize = { 0, 0 };
1931 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1933 /* Be smart and try to figure out the minimum we have to do */
1934 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1935 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1937 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1938 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1940 if (lprcSelectBox) doSelectBox = TRUE;
1941 if (lprcLabel) doLabel = TRUE;
1942 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
1943 if (doSelectBox)
1945 doIcon = TRUE;
1946 doLabel = TRUE;
1949 /************************************************************/
1950 /* compute the box rectangle (it should be cheap to do) */
1951 /************************************************************/
1952 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1953 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1955 if (lpLVItem->iSubItem)
1957 Box = lpColumnInfo->rcHeader;
1959 else
1961 Box.left = 0;
1962 Box.right = infoPtr->nItemWidth;
1964 Box.top = 0;
1965 Box.bottom = infoPtr->nItemHeight;
1967 /******************************************************************/
1968 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
1969 /******************************************************************/
1970 if (doIcon)
1972 LONG state_width = 0;
1974 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1975 state_width = infoPtr->iconStateSize.cx;
1977 if (uView == LVS_ICON)
1979 Icon.left = Box.left + state_width;
1980 if (infoPtr->himlNormal)
1981 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
1982 Icon.top = Box.top + ICON_TOP_PADDING;
1983 Icon.right = Icon.left;
1984 Icon.bottom = Icon.top;
1985 if (infoPtr->himlNormal)
1987 Icon.right += infoPtr->iconSize.cx;
1988 Icon.bottom += infoPtr->iconSize.cy;
1991 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1993 Icon.left = Box.left + state_width;
1995 if (uView == LVS_REPORT)
1996 Icon.left += REPORT_MARGINX;
1998 Icon.top = Box.top;
1999 Icon.right = Icon.left;
2000 if (infoPtr->himlSmall &&
2001 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2002 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2003 Icon.right += infoPtr->iconSize.cx;
2004 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2006 if(lprcIcon) *lprcIcon = Icon;
2007 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2009 /* TODO: is this correct? */
2010 if (lprcStateIcon)
2012 lprcStateIcon->left = Icon.left - state_width;
2013 lprcStateIcon->right = Icon.left;
2014 lprcStateIcon->top = Icon.top;
2015 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2016 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2019 else Icon.right = 0;
2021 /************************************************************/
2022 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2023 /************************************************************/
2024 if (doLabel)
2026 /* calculate how far to the right can the label stretch */
2027 Label.right = Box.right;
2028 if (uView == LVS_REPORT)
2030 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
2033 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
2035 labelSize.cx = infoPtr->nItemWidth;
2036 labelSize.cy = infoPtr->nItemHeight;
2037 goto calc_label;
2040 /* we need the text in non owner draw mode */
2041 assert(lpLVItem->mask & LVIF_TEXT);
2042 if (is_textT(lpLVItem->pszText, TRUE))
2044 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2045 HDC hdc = GetDC(infoPtr->hwndSelf);
2046 HFONT hOldFont = SelectObject(hdc, hFont);
2047 UINT uFormat;
2048 RECT rcText;
2050 /* compute rough rectangle where the label will go */
2051 SetRectEmpty(&rcText);
2052 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2053 rcText.bottom = infoPtr->nItemHeight;
2054 if (uView == LVS_ICON)
2055 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2057 /* now figure out the flags */
2058 if (uView == LVS_ICON)
2059 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2060 else
2061 uFormat = LV_SL_DT_FLAGS;
2063 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2065 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2066 labelSize.cy = rcText.bottom - rcText.top;
2068 SelectObject(hdc, hOldFont);
2069 ReleaseDC(infoPtr->hwndSelf, hdc);
2072 calc_label:
2073 if (uView == LVS_ICON)
2075 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2076 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2077 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2078 Label.right = Label.left + labelSize.cx;
2079 Label.bottom = Label.top + infoPtr->nItemHeight;
2080 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2082 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2083 labelSize.cy /= infoPtr->ntmHeight;
2084 labelSize.cy = max(labelSize.cy, 1);
2085 labelSize.cy *= infoPtr->ntmHeight;
2087 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2089 else if (uView == LVS_REPORT)
2091 Label.left = Icon.right;
2092 Label.top = Box.top;
2093 Label.right = lpColumnInfo->rcHeader.right;
2094 Label.bottom = Label.top + infoPtr->nItemHeight;
2096 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2098 Label.left = Icon.right;
2099 Label.top = Box.top;
2100 Label.right = min(Label.left + labelSize.cx, Label.right);
2101 Label.bottom = Label.top + infoPtr->nItemHeight;
2104 if (lprcLabel) *lprcLabel = Label;
2105 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2108 /************************************************************/
2109 /* compute STATEICON bounding box */
2110 /************************************************************/
2111 if (doSelectBox)
2113 if (uView == LVS_REPORT)
2115 SelectBox.left = Icon.right; /* FIXME: should be Icon.left */
2116 SelectBox.top = Box.top;
2117 SelectBox.bottom = Box.bottom;
2118 if (lpLVItem->iSubItem == 0)
2120 /* we need the indent in report mode */
2121 assert(lpLVItem->mask & LVIF_INDENT);
2122 SelectBox.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
2124 SelectBox.right = min(SelectBox.left + labelSize.cx, Label.right);
2126 else
2128 UnionRect(&SelectBox, &Icon, &Label);
2130 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2131 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2134 /* Fix the Box if necessary */
2135 if (lprcBox)
2137 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2138 else *lprcBox = Box;
2140 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2143 /***
2144 * DESCRIPTION: [INTERNAL]
2146 * PARAMETER(S):
2147 * [I] infoPtr : valid pointer to the listview structure
2148 * [I] nItem : item number
2149 * [O] lprcBox : ptr to Box rectangle
2151 * RETURN:
2152 * None.
2154 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2156 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2157 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2158 POINT Position, Origin;
2159 LVITEMW lvItem;
2161 LISTVIEW_GetOrigin(infoPtr, &Origin);
2162 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2164 /* Be smart and try to figure out the minimum we have to do */
2165 lvItem.mask = 0;
2166 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2167 lvItem.mask |= LVIF_TEXT;
2168 lvItem.iItem = nItem;
2169 lvItem.iSubItem = 0;
2170 lvItem.pszText = szDispText;
2171 lvItem.cchTextMax = DISP_TEXT_SIZE;
2172 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2173 if (uView == LVS_ICON)
2175 lvItem.mask |= LVIF_STATE;
2176 lvItem.stateMask = LVIS_FOCUSED;
2177 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2179 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2181 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2185 /***
2186 * DESCRIPTION:
2187 * Returns the current icon position, and advances it along the top.
2188 * The returned position is not offset by Origin.
2190 * PARAMETER(S):
2191 * [I] infoPtr : valid pointer to the listview structure
2192 * [O] lpPos : will get the current icon position
2194 * RETURN:
2195 * None
2197 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2199 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2201 *lpPos = infoPtr->currIconPos;
2203 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2204 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2206 infoPtr->currIconPos.x = 0;
2207 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2211 /***
2212 * DESCRIPTION:
2213 * Returns the current icon position, and advances it down the left edge.
2214 * The returned position is not offset by Origin.
2216 * PARAMETER(S):
2217 * [I] infoPtr : valid pointer to the listview structure
2218 * [O] lpPos : will get the current icon position
2220 * RETURN:
2221 * None
2223 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2225 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2227 *lpPos = infoPtr->currIconPos;
2229 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2230 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2232 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2233 infoPtr->currIconPos.y = 0;
2237 /***
2238 * DESCRIPTION:
2239 * Moves an icon to the specified position.
2240 * It takes care of invalidating the item, etc.
2242 * PARAMETER(S):
2243 * [I] infoPtr : valid pointer to the listview structure
2244 * [I] nItem : the item to move
2245 * [I] lpPos : the new icon position
2246 * [I] isNew : flags the item as being new
2248 * RETURN:
2249 * Success: TRUE
2250 * Failure: FALSE
2252 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2254 POINT old;
2256 if (!isNew)
2258 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2259 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2261 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2262 LISTVIEW_InvalidateItem(infoPtr, nItem);
2265 /* Allocating a POINTER for every item is too resource intensive,
2266 * so we'll keep the (x,y) in different arrays */
2267 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2268 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2270 LISTVIEW_InvalidateItem(infoPtr, nItem);
2272 return TRUE;
2275 /***
2276 * DESCRIPTION:
2277 * Arranges listview items in icon display mode.
2279 * PARAMETER(S):
2280 * [I] infoPtr : valid pointer to the listview structure
2281 * [I] nAlignCode : alignment code
2283 * RETURN:
2284 * SUCCESS : TRUE
2285 * FAILURE : FALSE
2287 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2289 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2290 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2291 POINT pos;
2292 INT i;
2294 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2296 TRACE("nAlignCode=%d\n", nAlignCode);
2298 if (nAlignCode == LVA_DEFAULT)
2300 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2301 else nAlignCode = LVA_ALIGNTOP;
2304 switch (nAlignCode)
2306 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2307 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2308 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2309 default: return FALSE;
2312 infoPtr->bAutoarrange = TRUE;
2313 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2314 for (i = 0; i < infoPtr->nItemCount; i++)
2316 next_pos(infoPtr, &pos);
2317 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2320 return TRUE;
2323 /***
2324 * DESCRIPTION:
2325 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2327 * PARAMETER(S):
2328 * [I] infoPtr : valid pointer to the listview structure
2329 * [O] lprcView : bounding rectangle
2331 * RETURN:
2332 * SUCCESS : TRUE
2333 * FAILURE : FALSE
2335 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2337 INT i, x, y;
2339 SetRectEmpty(lprcView);
2341 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2343 case LVS_ICON:
2344 case LVS_SMALLICON:
2345 for (i = 0; i < infoPtr->nItemCount; i++)
2347 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2348 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2349 lprcView->right = max(lprcView->right, x);
2350 lprcView->bottom = max(lprcView->bottom, y);
2352 if (infoPtr->nItemCount > 0)
2354 lprcView->right += infoPtr->nItemWidth;
2355 lprcView->bottom += infoPtr->nItemHeight;
2357 break;
2359 case LVS_LIST:
2360 y = LISTVIEW_GetCountPerColumn(infoPtr);
2361 x = infoPtr->nItemCount / y;
2362 if (infoPtr->nItemCount % y) x++;
2363 lprcView->right = x * infoPtr->nItemWidth;
2364 lprcView->bottom = y * infoPtr->nItemHeight;
2365 break;
2367 case LVS_REPORT:
2368 lprcView->right = infoPtr->nItemWidth;
2369 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2370 break;
2374 /***
2375 * DESCRIPTION:
2376 * Retrieves the bounding rectangle of all the items.
2378 * PARAMETER(S):
2379 * [I] infoPtr : valid pointer to the listview structure
2380 * [O] lprcView : bounding rectangle
2382 * RETURN:
2383 * SUCCESS : TRUE
2384 * FAILURE : FALSE
2386 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2388 POINT ptOrigin;
2390 TRACE("(lprcView=%p)\n", lprcView);
2392 if (!lprcView) return FALSE;
2394 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2395 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2396 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2398 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2400 return TRUE;
2403 /***
2404 * DESCRIPTION:
2405 * Retrieves the subitem pointer associated with the subitem index.
2407 * PARAMETER(S):
2408 * [I] hdpaSubItems : DPA handle for a specific item
2409 * [I] nSubItem : index of subitem
2411 * RETURN:
2412 * SUCCESS : subitem pointer
2413 * FAILURE : NULL
2415 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2417 SUBITEM_INFO *lpSubItem;
2418 INT i;
2420 /* we should binary search here if need be */
2421 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2423 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2424 if (lpSubItem->iSubItem == nSubItem)
2425 return lpSubItem;
2428 return NULL;
2432 /***
2433 * DESCRIPTION:
2434 * Calculates the desired item width.
2436 * PARAMETER(S):
2437 * [I] infoPtr : valid pointer to the listview structure
2439 * RETURN:
2440 * The desired item width.
2442 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2444 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2445 INT nItemWidth = 0;
2447 TRACE("uView=%d\n", uView);
2449 if (uView == LVS_ICON)
2450 nItemWidth = infoPtr->iconSpacing.cx;
2451 else if (uView == LVS_REPORT)
2453 RECT rcHeader;
2455 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2457 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2458 nItemWidth = rcHeader.right;
2461 else /* LVS_SMALLICON, or LVS_LIST */
2463 INT i;
2465 for (i = 0; i < infoPtr->nItemCount; i++)
2466 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2468 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2469 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2471 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2474 return max(nItemWidth, 1);
2477 /***
2478 * DESCRIPTION:
2479 * Calculates the desired item height.
2481 * PARAMETER(S):
2482 * [I] infoPtr : valid pointer to the listview structure
2484 * RETURN:
2485 * The desired item height.
2487 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2489 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2490 INT nItemHeight;
2492 TRACE("uView=%d\n", uView);
2494 if (uView == LVS_ICON)
2495 nItemHeight = infoPtr->iconSpacing.cy;
2496 else
2498 nItemHeight = infoPtr->ntmHeight;
2499 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2500 nItemHeight++;
2501 if (infoPtr->himlState)
2502 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2503 if (infoPtr->himlSmall)
2504 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2505 if (infoPtr->himlState || infoPtr->himlSmall)
2506 nItemHeight += HEIGHT_PADDING;
2507 if (infoPtr->nMeasureItemHeight > 0)
2508 nItemHeight = infoPtr->nMeasureItemHeight;
2511 return max(nItemHeight, 1);
2514 /***
2515 * DESCRIPTION:
2516 * Updates the width, and height of an item.
2518 * PARAMETER(S):
2519 * [I] infoPtr : valid pointer to the listview structure
2521 * RETURN:
2522 * None.
2524 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2526 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2527 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2531 /***
2532 * DESCRIPTION:
2533 * Retrieves and saves important text metrics info for the current
2534 * Listview font.
2536 * PARAMETER(S):
2537 * [I] infoPtr : valid pointer to the listview structure
2540 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2542 HDC hdc = GetDC(infoPtr->hwndSelf);
2543 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2544 HFONT hOldFont = SelectObject(hdc, hFont);
2545 TEXTMETRICW tm;
2546 SIZE sz;
2548 if (GetTextMetricsW(hdc, &tm))
2550 infoPtr->ntmHeight = tm.tmHeight;
2551 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2554 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2555 infoPtr->nEllipsisWidth = sz.cx;
2557 SelectObject(hdc, hOldFont);
2558 ReleaseDC(infoPtr->hwndSelf, hdc);
2560 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2563 /***
2564 * DESCRIPTION:
2565 * A compare function for ranges
2567 * PARAMETER(S)
2568 * [I] range1 : pointer to range 1;
2569 * [I] range2 : pointer to range 2;
2570 * [I] flags : flags
2572 * RETURNS:
2573 * > 0 : if range 1 > range 2
2574 * < 0 : if range 2 > range 1
2575 * = 0 : if range intersects range 2
2577 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2579 INT cmp;
2581 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2582 cmp = -1;
2583 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2584 cmp = 1;
2585 else
2586 cmp = 0;
2588 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2590 return cmp;
2593 #if DEBUG_RANGES
2594 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2595 #else
2596 #define ranges_check(ranges, desc) do { } while(0)
2597 #endif
2599 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2601 INT i;
2602 RANGE *prev, *curr;
2604 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2605 assert (ranges);
2606 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2607 ranges_dump(ranges);
2608 prev = DPA_GetPtr(ranges->hdpa, 0);
2609 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2610 assert (prev->lower >= 0 && prev->lower < prev->upper);
2611 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2613 curr = DPA_GetPtr(ranges->hdpa, i);
2614 assert (prev->upper <= curr->lower);
2615 assert (curr->lower < curr->upper);
2616 prev = curr;
2618 TRACE("--- Done checking---\n");
2621 static RANGES ranges_create(int count)
2623 RANGES ranges = Alloc(sizeof(struct tagRANGES));
2624 if (!ranges) return NULL;
2625 ranges->hdpa = DPA_Create(count);
2626 if (ranges->hdpa) return ranges;
2627 Free(ranges);
2628 return NULL;
2631 static void ranges_clear(RANGES ranges)
2633 INT i;
2635 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2636 Free(DPA_GetPtr(ranges->hdpa, i));
2637 DPA_DeleteAllPtrs(ranges->hdpa);
2641 static void ranges_destroy(RANGES ranges)
2643 if (!ranges) return;
2644 ranges_clear(ranges);
2645 DPA_Destroy(ranges->hdpa);
2646 Free(ranges);
2649 static RANGES ranges_clone(RANGES ranges)
2651 RANGES clone;
2652 INT i;
2654 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2656 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2658 RANGE *newrng = Alloc(sizeof(RANGE));
2659 if (!newrng) goto fail;
2660 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2661 DPA_SetPtr(clone->hdpa, i, newrng);
2663 return clone;
2665 fail:
2666 TRACE ("clone failed\n");
2667 ranges_destroy(clone);
2668 return NULL;
2671 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2673 INT i;
2675 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2676 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2678 return ranges;
2681 static void ranges_dump(RANGES ranges)
2683 INT i;
2685 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2686 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2689 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2691 RANGE srchrng = { nItem, nItem + 1 };
2693 TRACE("(nItem=%d)\n", nItem);
2694 ranges_check(ranges, "before contain");
2695 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2698 static INT ranges_itemcount(RANGES ranges)
2700 INT i, count = 0;
2702 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2704 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2705 count += sel->upper - sel->lower;
2708 return count;
2711 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2713 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2714 INT index;
2716 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2717 if (index == -1) return TRUE;
2719 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2721 chkrng = DPA_GetPtr(ranges->hdpa, index);
2722 if (chkrng->lower >= nItem)
2723 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2724 if (chkrng->upper > nItem)
2725 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2727 return TRUE;
2730 static BOOL ranges_add(RANGES ranges, RANGE range)
2732 RANGE srchrgn;
2733 INT index;
2735 TRACE("(%s)\n", debugrange(&range));
2736 ranges_check(ranges, "before add");
2738 /* try find overlapping regions first */
2739 srchrgn.lower = range.lower - 1;
2740 srchrgn.upper = range.upper + 1;
2741 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2743 if (index == -1)
2745 RANGE *newrgn;
2747 TRACE("Adding new range\n");
2749 /* create the brand new range to insert */
2750 newrgn = Alloc(sizeof(RANGE));
2751 if(!newrgn) goto fail;
2752 *newrgn = range;
2754 /* figure out where to insert it */
2755 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2756 TRACE("index=%d\n", index);
2757 if (index == -1) index = 0;
2759 /* and get it over with */
2760 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2762 Free(newrgn);
2763 goto fail;
2766 else
2768 RANGE *chkrgn, *mrgrgn;
2769 INT fromindex, mergeindex;
2771 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2772 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2774 chkrgn->lower = min(range.lower, chkrgn->lower);
2775 chkrgn->upper = max(range.upper, chkrgn->upper);
2777 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2779 /* merge now common ranges */
2780 fromindex = 0;
2781 srchrgn.lower = chkrgn->lower - 1;
2782 srchrgn.upper = chkrgn->upper + 1;
2786 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2787 if (mergeindex == -1) break;
2788 if (mergeindex == index)
2790 fromindex = index + 1;
2791 continue;
2794 TRACE("Merge with index %i\n", mergeindex);
2796 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2797 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2798 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2799 Free(mrgrgn);
2800 DPA_DeletePtr(ranges->hdpa, mergeindex);
2801 if (mergeindex < index) index --;
2802 } while(1);
2805 ranges_check(ranges, "after add");
2806 return TRUE;
2808 fail:
2809 ranges_check(ranges, "failed add");
2810 return FALSE;
2813 static BOOL ranges_del(RANGES ranges, RANGE range)
2815 RANGE *chkrgn;
2816 INT index;
2818 TRACE("(%s)\n", debugrange(&range));
2819 ranges_check(ranges, "before del");
2821 /* we don't use DPAS_SORTED here, since we need *
2822 * to find the first overlapping range */
2823 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2824 while(index != -1)
2826 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2828 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2830 /* case 1: Same range */
2831 if ( (chkrgn->upper == range.upper) &&
2832 (chkrgn->lower == range.lower) )
2834 DPA_DeletePtr(ranges->hdpa, index);
2835 break;
2837 /* case 2: engulf */
2838 else if ( (chkrgn->upper <= range.upper) &&
2839 (chkrgn->lower >= range.lower) )
2841 DPA_DeletePtr(ranges->hdpa, index);
2843 /* case 3: overlap upper */
2844 else if ( (chkrgn->upper <= range.upper) &&
2845 (chkrgn->lower < range.lower) )
2847 chkrgn->upper = range.lower;
2849 /* case 4: overlap lower */
2850 else if ( (chkrgn->upper > range.upper) &&
2851 (chkrgn->lower >= range.lower) )
2853 chkrgn->lower = range.upper;
2854 break;
2856 /* case 5: fully internal */
2857 else
2859 RANGE tmprgn = *chkrgn, *newrgn;
2861 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
2862 newrgn->lower = chkrgn->lower;
2863 newrgn->upper = range.lower;
2864 chkrgn->lower = range.upper;
2865 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2867 Free(newrgn);
2868 goto fail;
2870 chkrgn = &tmprgn;
2871 break;
2874 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2877 ranges_check(ranges, "after del");
2878 return TRUE;
2880 fail:
2881 ranges_check(ranges, "failed del");
2882 return FALSE;
2885 /***
2886 * DESCRIPTION:
2887 * Removes all selection ranges
2889 * Parameters(s):
2890 * [I] infoPtr : valid pointer to the listview structure
2891 * [I] toSkip : item range to skip removing the selection
2893 * RETURNS:
2894 * SUCCESS : TRUE
2895 * FAILURE : TRUE
2897 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2899 LVITEMW lvItem;
2900 ITERATOR i;
2901 RANGES clone;
2903 TRACE("()\n");
2905 lvItem.state = 0;
2906 lvItem.stateMask = LVIS_SELECTED;
2908 /* need to clone the DPA because callbacks can change it */
2909 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2910 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2911 while(iterator_next(&i))
2912 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2913 /* note that the iterator destructor will free the cloned range */
2914 iterator_destroy(&i);
2916 return TRUE;
2919 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2921 RANGES toSkip;
2923 if (!(toSkip = ranges_create(1))) return FALSE;
2924 if (nItem != -1) ranges_additem(toSkip, nItem);
2925 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2926 ranges_destroy(toSkip);
2927 return TRUE;
2930 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2932 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2935 /***
2936 * DESCRIPTION:
2937 * Retrieves the number of items that are marked as selected.
2939 * PARAMETER(S):
2940 * [I] infoPtr : valid pointer to the listview structure
2942 * RETURN:
2943 * Number of items selected.
2945 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
2947 INT nSelectedCount = 0;
2949 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2951 INT i;
2952 for (i = 0; i < infoPtr->nItemCount; i++)
2954 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2955 nSelectedCount++;
2958 else
2959 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2961 TRACE("nSelectedCount=%d\n", nSelectedCount);
2962 return nSelectedCount;
2965 /***
2966 * DESCRIPTION:
2967 * Manages the item focus.
2969 * PARAMETER(S):
2970 * [I] infoPtr : valid pointer to the listview structure
2971 * [I] nItem : item index
2973 * RETURN:
2974 * TRUE : focused item changed
2975 * FALSE : focused item has NOT changed
2977 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2979 INT oldFocus = infoPtr->nFocusedItem;
2980 LVITEMW lvItem;
2982 if (nItem == infoPtr->nFocusedItem) return FALSE;
2984 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2985 lvItem.stateMask = LVIS_FOCUSED;
2986 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2988 return oldFocus != infoPtr->nFocusedItem;
2991 /* Helper function for LISTVIEW_ShiftIndices *only* */
2992 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2994 if (nShiftItem < nItem) return nShiftItem;
2996 if (nShiftItem > nItem) return nShiftItem + direction;
2998 if (direction > 0) return nShiftItem + direction;
3000 return min(nShiftItem, infoPtr->nItemCount - 1);
3004 * DESCRIPTION:
3005 * Updates the various indices after an item has been inserted or deleted.
3007 * PARAMETER(S):
3008 * [I] infoPtr : valid pointer to the listview structure
3009 * [I] nItem : item index
3010 * [I] direction : Direction of shift, +1 or -1.
3012 * RETURN:
3013 * None
3015 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3017 INT nNewFocus;
3018 BOOL bOldChange;
3020 /* temporarily disable change notification while shifting items */
3021 bOldChange = infoPtr->bDoChangeNotify;
3022 infoPtr->bDoChangeNotify = FALSE;
3024 TRACE("Shifting %iu, %i steps\n", nItem, direction);
3026 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3028 assert(abs(direction) == 1);
3030 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3032 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3033 if (nNewFocus != infoPtr->nFocusedItem)
3034 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3036 /* But we are not supposed to modify nHotItem! */
3038 infoPtr->bDoChangeNotify = bOldChange;
3043 * DESCRIPTION:
3044 * Adds a block of selections.
3046 * PARAMETER(S):
3047 * [I] infoPtr : valid pointer to the listview structure
3048 * [I] nItem : item index
3050 * RETURN:
3051 * Whether the window is still valid.
3053 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3055 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3056 INT nLast = max(infoPtr->nSelectionMark, nItem);
3057 HWND hwndSelf = infoPtr->hwndSelf;
3058 NMLVODSTATECHANGE nmlv;
3059 LVITEMW item;
3060 BOOL bOldChange;
3061 INT i;
3063 /* Temporarily disable change notification
3064 * If the control is LVS_OWNERDATA, we need to send
3065 * only one LVN_ODSTATECHANGED notification.
3066 * See MSDN documentation for LVN_ITEMCHANGED.
3068 bOldChange = infoPtr->bDoChangeNotify;
3069 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3071 if (nFirst == -1) nFirst = nItem;
3073 item.state = LVIS_SELECTED;
3074 item.stateMask = LVIS_SELECTED;
3076 for (i = nFirst; i <= nLast; i++)
3077 LISTVIEW_SetItemState(infoPtr,i,&item);
3079 ZeroMemory(&nmlv, sizeof(nmlv));
3080 nmlv.iFrom = nFirst;
3081 nmlv.iTo = nLast;
3082 nmlv.uNewState = 0;
3083 nmlv.uOldState = item.state;
3085 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3086 if (!IsWindow(hwndSelf))
3087 return FALSE;
3088 infoPtr->bDoChangeNotify = bOldChange;
3089 return TRUE;
3093 /***
3094 * DESCRIPTION:
3095 * Sets a single group selection.
3097 * PARAMETER(S):
3098 * [I] infoPtr : valid pointer to the listview structure
3099 * [I] nItem : item index
3101 * RETURN:
3102 * None
3104 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3106 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3107 RANGES selection;
3108 LVITEMW item;
3109 ITERATOR i;
3110 BOOL bOldChange;
3112 if (!(selection = ranges_create(100))) return;
3114 item.state = LVIS_SELECTED;
3115 item.stateMask = LVIS_SELECTED;
3117 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3119 if (infoPtr->nSelectionMark == -1)
3121 infoPtr->nSelectionMark = nItem;
3122 ranges_additem(selection, nItem);
3124 else
3126 RANGE sel;
3128 sel.lower = min(infoPtr->nSelectionMark, nItem);
3129 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3130 ranges_add(selection, sel);
3133 else
3135 RECT rcItem, rcSel, rcSelMark;
3136 POINT ptItem;
3138 rcItem.left = LVIR_BOUNDS;
3139 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3140 rcSelMark.left = LVIR_BOUNDS;
3141 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3142 UnionRect(&rcSel, &rcItem, &rcSelMark);
3143 iterator_frameditems(&i, infoPtr, &rcSel);
3144 while(iterator_next(&i))
3146 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3147 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3149 iterator_destroy(&i);
3152 bOldChange = infoPtr->bDoChangeNotify;
3153 infoPtr->bDoChangeNotify = FALSE;
3155 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3158 iterator_rangesitems(&i, selection);
3159 while(iterator_next(&i))
3160 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3161 /* this will also destroy the selection */
3162 iterator_destroy(&i);
3164 infoPtr->bDoChangeNotify = bOldChange;
3166 LISTVIEW_SetItemFocus(infoPtr, nItem);
3169 /***
3170 * DESCRIPTION:
3171 * Sets a single selection.
3173 * PARAMETER(S):
3174 * [I] infoPtr : valid pointer to the listview structure
3175 * [I] nItem : item index
3177 * RETURN:
3178 * None
3180 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3182 LVITEMW lvItem;
3184 TRACE("nItem=%d\n", nItem);
3186 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3188 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3189 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3190 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3192 infoPtr->nSelectionMark = nItem;
3195 /***
3196 * DESCRIPTION:
3197 * Set selection(s) with keyboard.
3199 * PARAMETER(S):
3200 * [I] infoPtr : valid pointer to the listview structure
3201 * [I] nItem : item index
3203 * RETURN:
3204 * SUCCESS : TRUE (needs to be repainted)
3205 * FAILURE : FALSE (nothing has changed)
3207 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3209 /* FIXME: pass in the state */
3210 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3211 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3212 BOOL bResult = FALSE;
3214 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3215 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3217 if (infoPtr->dwStyle & LVS_SINGLESEL)
3219 bResult = TRUE;
3220 LISTVIEW_SetSelection(infoPtr, nItem);
3222 else
3224 if (wShift)
3226 bResult = TRUE;
3227 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3229 else if (wCtrl)
3231 LVITEMW lvItem;
3232 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3233 lvItem.stateMask = LVIS_SELECTED;
3234 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3236 if (lvItem.state & LVIS_SELECTED)
3237 infoPtr->nSelectionMark = nItem;
3239 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3241 else
3243 bResult = TRUE;
3244 LISTVIEW_SetSelection(infoPtr, nItem);
3247 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3250 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3251 return bResult;
3254 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3256 LVHITTESTINFO lvHitTestInfo;
3258 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3259 lvHitTestInfo.pt.x = pt.x;
3260 lvHitTestInfo.pt.y = pt.y;
3262 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3264 lpLVItem->mask = LVIF_PARAM;
3265 lpLVItem->iItem = lvHitTestInfo.iItem;
3266 lpLVItem->iSubItem = 0;
3268 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3271 static inline BOOL LISTVIEW_isHotTracking(const LISTVIEW_INFO *infoPtr)
3273 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3274 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3275 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3278 /***
3279 * DESCRIPTION:
3280 * Called when the mouse is being actively tracked and has hovered for a specified
3281 * amount of time
3283 * PARAMETER(S):
3284 * [I] infoPtr : valid pointer to the listview structure
3285 * [I] fwKeys : key indicator
3286 * [I] x,y : mouse position
3288 * RETURN:
3289 * 0 if the message was processed, non-zero if there was an error
3291 * INFO:
3292 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3293 * over the item for a certain period of time.
3296 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3298 if (LISTVIEW_isHotTracking(infoPtr))
3300 LVITEMW item;
3301 POINT pt;
3303 pt.x = x;
3304 pt.y = y;
3306 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3307 LISTVIEW_SetSelection(infoPtr, item.iItem);
3310 return 0;
3313 /***
3314 * DESCRIPTION:
3315 * Called whenever WM_MOUSEMOVE is received.
3317 * PARAMETER(S):
3318 * [I] infoPtr : valid pointer to the listview structure
3319 * [I] fwKeys : key indicator
3320 * [I] x,y : mouse position
3322 * RETURN:
3323 * 0 if the message is processed, non-zero if there was an error
3325 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
3327 TRACKMOUSEEVENT trackinfo;
3329 if (!(fwKeys & MK_LBUTTON))
3330 infoPtr->bLButtonDown = FALSE;
3332 if (infoPtr->bLButtonDown)
3334 POINT tmp;
3335 RECT rect;
3336 WORD wDragWidth = GetSystemMetrics(SM_CXDRAG);
3337 WORD wDragHeight= GetSystemMetrics(SM_CYDRAG);
3339 rect.left = infoPtr->ptClickPos.x - wDragWidth;
3340 rect.right = infoPtr->ptClickPos.x + wDragWidth;
3341 rect.top = infoPtr->ptClickPos.y - wDragHeight;
3342 rect.bottom = infoPtr->ptClickPos.y + wDragHeight;
3344 tmp.x = x;
3345 tmp.y = y;
3347 if (!PtInRect(&rect, tmp))
3349 LVHITTESTINFO lvHitTestInfo;
3350 NMLISTVIEW nmlv;
3352 lvHitTestInfo.pt = infoPtr->ptClickPos;
3353 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
3355 ZeroMemory(&nmlv, sizeof(nmlv));
3356 nmlv.iItem = lvHitTestInfo.iItem;
3357 nmlv.ptAction = infoPtr->ptClickPos;
3359 if (!infoPtr->bDragging)
3361 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
3362 infoPtr->bDragging = TRUE;
3365 return 0;
3368 else
3369 infoPtr->bLButtonDown = FALSE;
3371 /* see if we are supposed to be tracking mouse hovering */
3372 if (LISTVIEW_isHotTracking(infoPtr)) {
3373 /* fill in the trackinfo struct */
3374 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3375 trackinfo.dwFlags = TME_QUERY;
3376 trackinfo.hwndTrack = infoPtr->hwndSelf;
3377 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3379 /* see if we are already tracking this hwnd */
3380 _TrackMouseEvent(&trackinfo);
3382 if(!(trackinfo.dwFlags & TME_HOVER)) {
3383 trackinfo.dwFlags = TME_HOVER;
3385 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3386 _TrackMouseEvent(&trackinfo);
3390 return 0;
3394 /***
3395 * Tests whether the item is assignable to a list with style lStyle
3397 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3399 if ( (lpLVItem->mask & LVIF_TEXT) &&
3400 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3401 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3403 return TRUE;
3407 /***
3408 * DESCRIPTION:
3409 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3411 * PARAMETER(S):
3412 * [I] infoPtr : valid pointer to the listview structure
3413 * [I] lpLVItem : valid pointer to new item attributes
3414 * [I] isNew : the item being set is being inserted
3415 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3416 * [O] bChanged : will be set to TRUE if the item really changed
3418 * RETURN:
3419 * SUCCESS : TRUE
3420 * FAILURE : FALSE
3422 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3424 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3425 ITEM_INFO *lpItem;
3426 NMLISTVIEW nmlv;
3427 UINT uChanged = 0;
3428 LVITEMW item;
3430 TRACE("()\n");
3432 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3434 if (lpLVItem->mask == 0) return TRUE;
3436 if (infoPtr->dwStyle & LVS_OWNERDATA)
3438 /* a virtual listview only stores selection and focus */
3439 if (lpLVItem->mask & ~LVIF_STATE)
3440 return FALSE;
3441 lpItem = NULL;
3443 else
3445 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3446 lpItem = DPA_GetPtr(hdpaSubItems, 0);
3447 assert (lpItem);
3450 /* we need to get the lParam and state of the item */
3451 item.iItem = lpLVItem->iItem;
3452 item.iSubItem = lpLVItem->iSubItem;
3453 item.mask = LVIF_STATE | LVIF_PARAM;
3454 item.stateMask = ~0;
3455 item.state = 0;
3456 item.lParam = 0;
3457 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3459 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3460 /* determine what fields will change */
3461 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3462 uChanged |= LVIF_STATE;
3464 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3465 uChanged |= LVIF_IMAGE;
3467 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3468 uChanged |= LVIF_PARAM;
3470 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3471 uChanged |= LVIF_INDENT;
3473 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3474 uChanged |= LVIF_TEXT;
3476 TRACE("uChanged=0x%x\n", uChanged);
3477 if (!uChanged) return TRUE;
3478 *bChanged = TRUE;
3480 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3481 nmlv.iItem = lpLVItem->iItem;
3482 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3483 nmlv.uOldState = item.state;
3484 nmlv.uChanged = uChanged;
3485 nmlv.lParam = item.lParam;
3487 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3488 /* and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications */
3489 /* are enabled */
3490 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
3492 HWND hwndSelf = infoPtr->hwndSelf;
3494 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3495 return FALSE;
3496 if (!IsWindow(hwndSelf))
3497 return FALSE;
3500 /* copy information */
3501 if (lpLVItem->mask & LVIF_TEXT)
3502 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3504 if (lpLVItem->mask & LVIF_IMAGE)
3505 lpItem->hdr.iImage = lpLVItem->iImage;
3507 if (lpLVItem->mask & LVIF_PARAM)
3508 lpItem->lParam = lpLVItem->lParam;
3510 if (lpLVItem->mask & LVIF_INDENT)
3511 lpItem->iIndent = lpLVItem->iIndent;
3513 if (uChanged & LVIF_STATE)
3515 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3517 lpItem->state &= ~lpLVItem->stateMask;
3518 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3520 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3522 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3523 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3525 else if (lpLVItem->stateMask & LVIS_SELECTED)
3526 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3528 /* if we are asked to change focus, and we manage it, do it */
3529 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3531 if (lpLVItem->state & LVIS_FOCUSED)
3533 LISTVIEW_SetItemFocus(infoPtr, -1);
3534 infoPtr->nFocusedItem = lpLVItem->iItem;
3535 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3537 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3538 infoPtr->nFocusedItem = -1;
3542 /* if we're inserting the item, we're done */
3543 if (isNew) return TRUE;
3545 /* send LVN_ITEMCHANGED notification */
3546 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3547 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3549 return TRUE;
3552 /***
3553 * DESCRIPTION:
3554 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3556 * PARAMETER(S):
3557 * [I] infoPtr : valid pointer to the listview structure
3558 * [I] lpLVItem : valid pointer to new subitem attributes
3559 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3560 * [O] bChanged : will be set to TRUE if the item really changed
3562 * RETURN:
3563 * SUCCESS : TRUE
3564 * FAILURE : FALSE
3566 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3568 HDPA hdpaSubItems;
3569 SUBITEM_INFO *lpSubItem;
3571 /* we do not support subitems for virtual listviews */
3572 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3574 /* set subitem only if column is present */
3575 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3577 /* First do some sanity checks */
3578 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
3579 particularly useful. We currently do not actually do anything with
3580 the flag on subitems.
3582 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) return FALSE;
3583 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
3585 /* get the subitem structure, and create it if not there */
3586 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3587 assert (hdpaSubItems);
3589 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3590 if (!lpSubItem)
3592 SUBITEM_INFO *tmpSubItem;
3593 INT i;
3595 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
3596 if (!lpSubItem) return FALSE;
3597 /* we could binary search here, if need be...*/
3598 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3600 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
3601 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3603 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3605 Free(lpSubItem);
3606 return FALSE;
3608 lpSubItem->iSubItem = lpLVItem->iSubItem;
3609 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3610 *bChanged = TRUE;
3613 if (lpLVItem->mask & LVIF_IMAGE)
3614 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3616 lpSubItem->hdr.iImage = lpLVItem->iImage;
3617 *bChanged = TRUE;
3620 if (lpLVItem->mask & LVIF_TEXT)
3621 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3623 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3624 *bChanged = TRUE;
3627 return TRUE;
3630 /***
3631 * DESCRIPTION:
3632 * Sets item attributes.
3634 * PARAMETER(S):
3635 * [I] infoPtr : valid pointer to the listview structure
3636 * [I] lpLVItem : new item attributes
3637 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3639 * RETURN:
3640 * SUCCESS : TRUE
3641 * FAILURE : FALSE
3643 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
3645 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3646 HWND hwndSelf = infoPtr->hwndSelf;
3647 LPWSTR pszText = NULL;
3648 BOOL bResult, bChanged = FALSE;
3650 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3652 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3653 return FALSE;
3655 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3656 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3658 pszText = lpLVItem->pszText;
3659 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
3662 /* actually set the fields */
3663 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3665 if (lpLVItem->iSubItem)
3666 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3667 else
3668 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3669 if (!IsWindow(hwndSelf))
3670 return FALSE;
3672 /* redraw item, if necessary */
3673 if (bChanged && !infoPtr->bIsDrawing)
3675 /* this little optimization eliminates some nasty flicker */
3676 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3677 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
3678 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
3679 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3680 else
3681 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3683 /* restore text */
3684 if (pszText)
3686 textfreeT(lpLVItem->pszText, isW);
3687 lpLVItem->pszText = pszText;
3690 return bResult;
3693 /***
3694 * DESCRIPTION:
3695 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3697 * PARAMETER(S):
3698 * [I] infoPtr : valid pointer to the listview structure
3700 * RETURN:
3701 * item index
3703 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
3705 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3706 INT nItem = 0;
3707 SCROLLINFO scrollInfo;
3709 scrollInfo.cbSize = sizeof(SCROLLINFO);
3710 scrollInfo.fMask = SIF_POS;
3712 if (uView == LVS_LIST)
3714 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3715 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3717 else if (uView == LVS_REPORT)
3719 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3720 nItem = scrollInfo.nPos;
3722 else
3724 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3725 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3728 TRACE("nItem=%d\n", nItem);
3730 return nItem;
3734 /***
3735 * DESCRIPTION:
3736 * Erases the background of the given rectangle
3738 * PARAMETER(S):
3739 * [I] infoPtr : valid pointer to the listview structure
3740 * [I] hdc : device context handle
3741 * [I] lprcBox : clipping rectangle
3743 * RETURN:
3744 * Success: TRUE
3745 * Failure: FALSE
3747 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3749 if (!infoPtr->hBkBrush) return FALSE;
3751 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
3753 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3756 /***
3757 * DESCRIPTION:
3758 * Draws an item.
3760 * PARAMETER(S):
3761 * [I] infoPtr : valid pointer to the listview structure
3762 * [I] hdc : device context handle
3763 * [I] nItem : item index
3764 * [I] nSubItem : subitem index
3765 * [I] pos : item position in client coordinates
3766 * [I] cdmode : custom draw mode
3768 * RETURN:
3769 * Success: TRUE
3770 * Failure: FALSE
3772 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3774 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3775 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3776 static WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3777 DWORD cdsubitemmode = CDRF_DODEFAULT;
3778 LPRECT lprcFocus;
3779 RECT rcSelect, rcBox, rcIcon, rcLabel, rcStateIcon;
3780 NMLVCUSTOMDRAW nmlvcd;
3781 HIMAGELIST himl;
3782 LVITEMW lvItem;
3783 HFONT hOldFont;
3785 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, wine_dbgstr_point(&pos));
3787 /* get information needed for drawing the item */
3788 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
3789 if (nSubItem == 0) lvItem.mask |= LVIF_STATE;
3790 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3791 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3792 lvItem.iItem = nItem;
3793 lvItem.iSubItem = nSubItem;
3794 lvItem.state = 0;
3795 lvItem.lParam = 0;
3796 lvItem.cchTextMax = DISP_TEXT_SIZE;
3797 lvItem.pszText = szDispText;
3798 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3799 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3800 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3801 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3802 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3804 /* now check if we need to update the focus rectangle */
3805 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3807 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3808 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
3809 OffsetRect(&rcBox, pos.x, pos.y);
3810 OffsetRect(&rcSelect, pos.x, pos.y);
3811 OffsetRect(&rcIcon, pos.x, pos.y);
3812 OffsetRect(&rcStateIcon, pos.x, pos.y);
3813 OffsetRect(&rcLabel, pos.x, pos.y);
3814 TRACE(" rcBox=%s, rcSelect=%s, rcIcon=%s. rcLabel=%s\n",
3815 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
3816 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
3818 /* fill in the custom draw structure */
3819 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3821 hOldFont = GetCurrentObject(hdc, OBJ_FONT);
3822 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3823 if (cdmode & CDRF_SKIPDEFAULT) goto postpaint;
3824 if (cdmode & CDRF_NOTIFYITEMDRAW)
3825 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3826 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3827 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3828 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3829 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3831 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3832 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3834 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3835 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
3836 else if ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) == FALSE)
3837 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
3839 /* in full row select, subitems, will just use main item's colors */
3840 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3841 nmlvcd.clrTextBk = CLR_NONE;
3843 /* state icons */
3844 if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && (nSubItem == 0))
3846 UINT uStateImage = STATEIMAGEINDEX(lvItem.state);
3847 if (uStateImage)
3849 TRACE("uStateImage=%d\n", uStateImage);
3850 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc,
3851 rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
3855 /* small icons */
3856 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3857 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3859 TRACE("iImage=%d\n", lvItem.iImage);
3860 ImageList_DrawEx(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3861 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk, CLR_DEFAULT,
3862 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3865 /* Don't bother painting item being edited */
3866 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3868 /* FIXME: temporary hack */
3869 rcSelect.left = rcLabel.left;
3871 /* draw the selection background, if we're drawing the main item */
3872 if (nSubItem == 0)
3874 /* in icon mode, the label rect is really what we want to draw the
3875 * background for */
3876 if (uView == LVS_ICON)
3877 rcSelect = rcLabel;
3879 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3880 rcSelect.right = rcBox.right;
3882 if (nmlvcd.clrTextBk != CLR_NONE)
3883 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3884 if(lprcFocus) *lprcFocus = rcSelect;
3887 /* figure out the text drawing flags */
3888 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3889 if (uView == LVS_ICON)
3890 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3891 else if (nSubItem)
3893 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3895 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3896 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3897 default: uFormat |= DT_LEFT;
3900 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3902 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3903 else rcLabel.left += LABEL_HOR_PADDING;
3905 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3907 /* for GRIDLINES reduce the bottom so the text formats correctly */
3908 if (uView == LVS_REPORT && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
3909 rcLabel.bottom--;
3911 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3913 postpaint:
3914 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3915 notify_postpaint(infoPtr, &nmlvcd);
3916 if (cdsubitemmode & CDRF_NEWFONT)
3917 SelectObject(hdc, hOldFont);
3918 return TRUE;
3921 /***
3922 * DESCRIPTION:
3923 * Draws listview items when in owner draw mode.
3925 * PARAMETER(S):
3926 * [I] infoPtr : valid pointer to the listview structure
3927 * [I] hdc : device context handle
3929 * RETURN:
3930 * None
3932 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3934 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
3935 DWORD cditemmode = CDRF_DODEFAULT;
3936 NMLVCUSTOMDRAW nmlvcd;
3937 POINT Origin, Position;
3938 DRAWITEMSTRUCT dis;
3939 LVITEMW item;
3941 TRACE("()\n");
3943 ZeroMemory(&dis, sizeof(dis));
3945 /* Get scroll info once before loop */
3946 LISTVIEW_GetOrigin(infoPtr, &Origin);
3948 /* iterate through the invalidated rows */
3949 while(iterator_next(i))
3951 item.iItem = i->nItem;
3952 item.iSubItem = 0;
3953 item.mask = LVIF_PARAM | LVIF_STATE;
3954 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3955 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3957 dis.CtlType = ODT_LISTVIEW;
3958 dis.CtlID = uID;
3959 dis.itemID = item.iItem;
3960 dis.itemAction = ODA_DRAWENTIRE;
3961 dis.itemState = 0;
3962 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3963 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3964 dis.hwndItem = infoPtr->hwndSelf;
3965 dis.hDC = hdc;
3966 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3967 dis.rcItem.left = Position.x + Origin.x;
3968 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3969 dis.rcItem.top = Position.y + Origin.y;
3970 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3971 dis.itemData = item.lParam;
3973 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
3976 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3977 * structure for the rest. of the paint cycle
3979 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3980 if (cdmode & CDRF_NOTIFYITEMDRAW)
3981 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3983 if (!(cditemmode & CDRF_SKIPDEFAULT))
3985 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
3986 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3989 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3990 notify_postpaint(infoPtr, &nmlvcd);
3994 /***
3995 * DESCRIPTION:
3996 * Draws listview items when in report display mode.
3998 * PARAMETER(S):
3999 * [I] infoPtr : valid pointer to the listview structure
4000 * [I] hdc : device context handle
4001 * [I] cdmode : custom draw mode
4003 * RETURN:
4004 * None
4006 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4008 INT rgntype;
4009 RECT rcClip, rcItem;
4010 POINT Origin, Position;
4011 RANGE colRange;
4012 ITERATOR j;
4014 TRACE("()\n");
4016 /* figure out what to draw */
4017 rgntype = GetClipBox(hdc, &rcClip);
4018 if (rgntype == NULLREGION) return;
4020 /* Get scroll info once before loop */
4021 LISTVIEW_GetOrigin(infoPtr, &Origin);
4023 /* narrow down the columns we need to paint */
4024 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4026 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4027 if (rcItem.right + Origin.x >= rcClip.left) break;
4029 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4031 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4032 if (rcItem.left + Origin.x < rcClip.right) break;
4034 iterator_rangeitems(&j, colRange);
4036 /* in full row select, we _have_ to draw the main item */
4037 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4038 j.nSpecial = 0;
4040 /* iterate through the invalidated rows */
4041 while(iterator_next(i))
4043 /* iterate through the invalidated columns */
4044 while(iterator_next(&j))
4046 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4047 Position.x += Origin.x;
4048 Position.y += Origin.y;
4050 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4052 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4053 rcItem.top = 0;
4054 rcItem.bottom = infoPtr->nItemHeight;
4055 OffsetRect(&rcItem, Position.x, Position.y);
4056 if (!RectVisible(hdc, &rcItem)) continue;
4059 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
4062 iterator_destroy(&j);
4065 /***
4066 * DESCRIPTION:
4067 * Draws the gridlines if necessary when in report display mode.
4069 * PARAMETER(S):
4070 * [I] infoPtr : valid pointer to the listview structure
4071 * [I] hdc : device context handle
4073 * RETURN:
4074 * None
4076 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
4078 INT rgntype;
4079 INT y, itemheight;
4080 HPEN hPen, hOldPen;
4081 RECT rcClip, rcItem;
4082 POINT Origin;
4083 RANGE colRange;
4084 ITERATOR j;
4086 TRACE("()\n");
4088 /* figure out what to draw */
4089 rgntype = GetClipBox(hdc, &rcClip);
4090 if (rgntype == NULLREGION) return;
4092 /* Get scroll info once before loop */
4093 LISTVIEW_GetOrigin(infoPtr, &Origin);
4095 /* narrow down the columns we need to paint */
4096 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
4098 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
4099 if (rcItem.right + Origin.x >= rcClip.left) break;
4101 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
4103 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
4104 if (rcItem.left + Origin.x < rcClip.right) break;
4106 iterator_rangeitems(&j, colRange);
4108 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
4110 hOldPen = SelectObject ( hdc, hPen );
4112 /* draw the vertical lines for the columns */
4113 iterator_rangeitems(&j, colRange);
4114 while(iterator_next(&j))
4116 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4117 if (rcItem.left == 0) continue; /* skip first column */
4118 rcItem.left += Origin.x;
4119 rcItem.right += Origin.x;
4120 rcItem.top = infoPtr->rcList.top;
4121 rcItem.bottom = infoPtr->rcList.bottom;
4122 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
4123 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4124 LineTo (hdc, rcItem.left, rcItem.bottom);
4126 iterator_destroy(&j);
4128 /* draw the horizontial lines for the rows */
4129 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
4130 rcItem.left = infoPtr->rcList.left + Origin.x;
4131 rcItem.right = infoPtr->rcList.right + Origin.x;
4132 rcItem.bottom = rcItem.top = Origin.y - 1;
4133 MoveToEx(hdc, rcItem.left, rcItem.top, NULL);
4134 LineTo(hdc, rcItem.right, rcItem.top);
4135 for(y=itemheight-1+Origin.y; y<=infoPtr->rcList.bottom; y+=itemheight)
4137 rcItem.bottom = rcItem.top = y;
4138 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
4139 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
4140 LineTo (hdc, rcItem.right, rcItem.top);
4143 SelectObject( hdc, hOldPen );
4144 DeleteObject( hPen );
4148 /***
4149 * DESCRIPTION:
4150 * Draws listview items when in list display mode.
4152 * PARAMETER(S):
4153 * [I] infoPtr : valid pointer to the listview structure
4154 * [I] hdc : device context handle
4155 * [I] cdmode : custom draw mode
4157 * RETURN:
4158 * None
4160 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4162 POINT Origin, Position;
4164 /* Get scroll info once before loop */
4165 LISTVIEW_GetOrigin(infoPtr, &Origin);
4167 while(iterator_prev(i))
4169 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4170 Position.x += Origin.x;
4171 Position.y += Origin.y;
4173 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
4178 /***
4179 * DESCRIPTION:
4180 * Draws listview items.
4182 * PARAMETER(S):
4183 * [I] infoPtr : valid pointer to the listview structure
4184 * [I] hdc : device context handle
4185 * [I] prcErase : rect to be erased before refresh (may be NULL)
4187 * RETURN:
4188 * NoneX
4190 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
4192 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4193 COLORREF oldTextColor = 0, oldBkColor = 0, oldClrTextBk, oldClrText;
4194 NMLVCUSTOMDRAW nmlvcd;
4195 HFONT hOldFont = 0;
4196 DWORD cdmode;
4197 INT oldBkMode = 0;
4198 RECT rcClient;
4199 ITERATOR i;
4200 HDC hdcOrig = hdc;
4201 HBITMAP hbmp = NULL;
4203 LISTVIEW_DUMP(infoPtr);
4205 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4206 TRACE("double buffering\n");
4208 hdc = CreateCompatibleDC(hdcOrig);
4209 if (!hdc) {
4210 ERR("Failed to create DC for backbuffer\n");
4211 return;
4213 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
4214 infoPtr->rcList.bottom);
4215 if (!hbmp) {
4216 ERR("Failed to create bitmap for backbuffer\n");
4217 DeleteDC(hdc);
4218 return;
4221 SelectObject(hdc, hbmp);
4222 SelectObject(hdc, infoPtr->hFont);
4223 } else {
4224 /* Save dc values we're gonna trash while drawing
4225 * FIXME: Should be done in LISTVIEW_DrawItem() */
4226 hOldFont = SelectObject(hdc, infoPtr->hFont);
4227 oldBkMode = GetBkMode(hdc);
4228 oldBkColor = GetBkColor(hdc);
4229 oldTextColor = GetTextColor(hdc);
4232 infoPtr->bIsDrawing = TRUE;
4234 if (prcErase) {
4235 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
4236 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
4237 /* If no erasing was done (usually because RedrawWindow was called
4238 * with RDW_INVALIDATE only) we need to copy the old contents into
4239 * the backbuffer before continuing. */
4240 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
4241 infoPtr->rcList.right - infoPtr->rcList.left,
4242 infoPtr->rcList.bottom - infoPtr->rcList.top,
4243 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4246 /* FIXME: Shouldn't need to do this */
4247 oldClrTextBk = infoPtr->clrTextBk;
4248 oldClrText = infoPtr->clrText;
4250 infoPtr->cditemmode = CDRF_DODEFAULT;
4252 GetClientRect(infoPtr->hwndSelf, &rcClient);
4253 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
4254 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4255 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
4256 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4258 /* Use these colors to draw the items */
4259 infoPtr->clrTextBk = nmlvcd.clrTextBk;
4260 infoPtr->clrText = nmlvcd.clrText;
4262 /* nothing to draw */
4263 if(infoPtr->nItemCount == 0) goto enddraw;
4265 /* figure out what we need to draw */
4266 iterator_visibleitems(&i, infoPtr, hdc);
4268 /* send cache hint notification */
4269 if (infoPtr->dwStyle & LVS_OWNERDATA)
4271 RANGE range = iterator_range(&i);
4272 NMLVCACHEHINT nmlv;
4274 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
4275 nmlv.iFrom = range.lower;
4276 nmlv.iTo = range.upper - 1;
4277 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
4280 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
4281 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
4282 else
4284 if (uView == LVS_REPORT)
4285 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
4286 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
4287 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
4289 /* if we have a focus rect, draw it */
4290 if (infoPtr->bFocus)
4291 DrawFocusRect(hdc, &infoPtr->rcFocus);
4293 iterator_destroy(&i);
4295 enddraw:
4296 /* For LVS_EX_GRIDLINES go and draw lines */
4297 /* This includes the case where there were *no* items */
4298 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT &&
4299 infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4300 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
4302 if (cdmode & CDRF_NOTIFYPOSTPAINT)
4303 notify_postpaint(infoPtr, &nmlvcd);
4305 infoPtr->clrTextBk = oldClrTextBk;
4306 infoPtr->clrText = oldClrText;
4308 if(hbmp) {
4309 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
4310 infoPtr->rcList.right - infoPtr->rcList.left,
4311 infoPtr->rcList.bottom - infoPtr->rcList.top,
4312 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
4314 DeleteObject(hbmp);
4315 DeleteDC(hdc);
4316 } else {
4317 SelectObject(hdc, hOldFont);
4318 SetBkMode(hdc, oldBkMode);
4319 SetBkColor(hdc, oldBkColor);
4320 SetTextColor(hdc, oldTextColor);
4323 infoPtr->bIsDrawing = FALSE;
4327 /***
4328 * DESCRIPTION:
4329 * Calculates the approximate width and height of a given number of items.
4331 * PARAMETER(S):
4332 * [I] infoPtr : valid pointer to the listview structure
4333 * [I] nItemCount : number of items
4334 * [I] wWidth : width
4335 * [I] wHeight : height
4337 * RETURN:
4338 * Returns a DWORD. The width in the low word and the height in high word.
4340 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
4341 WORD wWidth, WORD wHeight)
4343 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4344 INT nItemCountPerColumn = 1;
4345 INT nColumnCount = 0;
4346 DWORD dwViewRect = 0;
4348 if (nItemCount == -1)
4349 nItemCount = infoPtr->nItemCount;
4351 if (uView == LVS_LIST)
4353 if (wHeight == 0xFFFF)
4355 /* use current height */
4356 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4359 if (wHeight < infoPtr->nItemHeight)
4360 wHeight = infoPtr->nItemHeight;
4362 if (nItemCount > 0)
4364 if (infoPtr->nItemHeight > 0)
4366 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
4367 if (nItemCountPerColumn == 0)
4368 nItemCountPerColumn = 1;
4370 if (nItemCount % nItemCountPerColumn != 0)
4371 nColumnCount = nItemCount / nItemCountPerColumn;
4372 else
4373 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4377 /* Microsoft padding magic */
4378 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4379 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4381 dwViewRect = MAKELONG(wWidth, wHeight);
4383 else if (uView == LVS_REPORT)
4385 RECT rcBox;
4387 if (infoPtr->nItemCount > 0)
4389 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4390 wWidth = rcBox.right - rcBox.left;
4391 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4393 else
4395 /* use current height and width */
4396 if (wHeight == 0xffff)
4397 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4398 if (wWidth == 0xffff)
4399 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4402 dwViewRect = MAKELONG(wWidth, wHeight);
4404 else if (uView == LVS_SMALLICON)
4405 FIXME("uView == LVS_SMALLICON: not implemented\n");
4406 else if (uView == LVS_ICON)
4407 FIXME("uView == LVS_ICON: not implemented\n");
4409 return dwViewRect;
4413 /***
4414 * DESCRIPTION:
4415 * Create a drag image list for the specified item.
4417 * PARAMETER(S):
4418 * [I] infoPtr : valid pointer to the listview structure
4419 * [I] iItem : index of item
4420 * [O] lppt : Upperr-left corner of the image
4422 * RETURN:
4423 * Returns a handle to the image list if successful, NULL otherwise.
4425 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4427 RECT rcItem;
4428 SIZE size;
4429 POINT pos;
4430 HDC hdc, hdcOrig;
4431 HBITMAP hbmp, hOldbmp;
4432 HIMAGELIST dragList = 0;
4433 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
4435 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4436 return 0;
4438 rcItem.left = LVIR_BOUNDS;
4439 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4440 return 0;
4442 lppt->x = rcItem.left;
4443 lppt->y = rcItem.top;
4445 size.cx = rcItem.right - rcItem.left;
4446 size.cy = rcItem.bottom - rcItem.top;
4448 hdcOrig = GetDC(infoPtr->hwndSelf);
4449 hdc = CreateCompatibleDC(hdcOrig);
4450 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4451 hOldbmp = SelectObject(hdc, hbmp);
4453 rcItem.left = rcItem.top = 0;
4454 rcItem.right = size.cx;
4455 rcItem.bottom = size.cy;
4456 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4458 pos.x = pos.y = 0;
4459 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4461 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4462 SelectObject(hdc, hOldbmp);
4463 ImageList_Add(dragList, hbmp, 0);
4465 else
4466 SelectObject(hdc, hOldbmp);
4468 DeleteObject(hbmp);
4469 DeleteDC(hdc);
4470 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4472 TRACE("ret=%p\n", dragList);
4474 return dragList;
4478 /***
4479 * DESCRIPTION:
4480 * Removes all listview items and subitems.
4482 * PARAMETER(S):
4483 * [I] infoPtr : valid pointer to the listview structure
4485 * RETURN:
4486 * SUCCESS : TRUE
4487 * FAILURE : FALSE
4489 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
4491 NMLISTVIEW nmlv;
4492 HDPA hdpaSubItems = NULL;
4493 BOOL bSuppress;
4494 ITEMHDR *hdrItem;
4495 INT i, j;
4497 TRACE("()\n");
4499 /* we do it directly, to avoid notifications */
4500 ranges_clear(infoPtr->selectionRanges);
4501 infoPtr->nSelectionMark = -1;
4502 infoPtr->nFocusedItem = -1;
4503 SetRectEmpty(&infoPtr->rcFocus);
4504 /* But we are supposed to leave nHotItem as is! */
4507 /* send LVN_DELETEALLITEMS notification */
4508 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4509 nmlv.iItem = -1;
4510 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4512 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4514 /* send LVN_DELETEITEM notification, if not suppressed */
4515 if (!bSuppress) notify_deleteitem(infoPtr, i);
4516 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4518 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
4519 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4521 hdrItem = DPA_GetPtr(hdpaSubItems, j);
4522 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4523 Free(hdrItem);
4525 DPA_Destroy(hdpaSubItems);
4526 DPA_DeletePtr(infoPtr->hdpaItems, i);
4528 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4529 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4530 infoPtr->nItemCount --;
4533 if (!destroy)
4535 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4536 LISTVIEW_UpdateScroll(infoPtr);
4538 LISTVIEW_InvalidateList(infoPtr);
4540 return TRUE;
4543 /***
4544 * DESCRIPTION:
4545 * Scrolls, and updates the columns, when a column is changing width.
4547 * PARAMETER(S):
4548 * [I] infoPtr : valid pointer to the listview structure
4549 * [I] nColumn : column to scroll
4550 * [I] dx : amount of scroll, in pixels
4552 * RETURN:
4553 * None.
4555 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4557 COLUMN_INFO *lpColumnInfo;
4558 RECT rcOld, rcCol;
4559 POINT ptOrigin;
4560 INT nCol;
4562 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4563 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4564 rcCol = lpColumnInfo->rcHeader;
4565 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4566 rcCol.left = rcCol.right;
4568 /* adjust the other columns */
4569 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4571 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4572 lpColumnInfo->rcHeader.left += dx;
4573 lpColumnInfo->rcHeader.right += dx;
4576 /* do not update screen if not in report mode */
4577 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4579 /* if we have a focus, we must first erase the focus rect */
4580 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4582 /* Need to reset the item width when inserting a new column */
4583 infoPtr->nItemWidth += dx;
4585 LISTVIEW_UpdateScroll(infoPtr);
4586 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
4588 /* scroll to cover the deleted column, and invalidate for redraw */
4589 rcOld = infoPtr->rcList;
4590 rcOld.left = ptOrigin.x + rcCol.left + dx;
4591 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4593 /* we can restore focus now */
4594 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4597 /***
4598 * DESCRIPTION:
4599 * Removes a column from the listview control.
4601 * PARAMETER(S):
4602 * [I] infoPtr : valid pointer to the listview structure
4603 * [I] nColumn : column index
4605 * RETURN:
4606 * SUCCESS : TRUE
4607 * FAILURE : FALSE
4609 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4611 RECT rcCol;
4613 TRACE("nColumn=%d\n", nColumn);
4615 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4616 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4618 /* While the MSDN specifically says that column zero should not be deleted,
4619 what actually happens is that the column itself is deleted but no items or subitems
4620 are removed.
4623 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4625 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4626 return FALSE;
4628 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4629 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4631 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4633 SUBITEM_INFO *lpSubItem, *lpDelItem;
4634 HDPA hdpaSubItems;
4635 INT nItem, nSubItem, i;
4637 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4639 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
4640 nSubItem = 0;
4641 lpDelItem = 0;
4642 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4644 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
4645 if (lpSubItem->iSubItem == nColumn)
4647 nSubItem = i;
4648 lpDelItem = lpSubItem;
4650 else if (lpSubItem->iSubItem > nColumn)
4652 lpSubItem->iSubItem--;
4656 /* if we found our subitem, zapp it */
4657 if (nSubItem > 0)
4659 /* free string */
4660 if (is_textW(lpDelItem->hdr.pszText))
4661 Free(lpDelItem->hdr.pszText);
4663 /* free item */
4664 Free(lpDelItem);
4666 /* free dpa memory */
4667 DPA_DeletePtr(hdpaSubItems, nSubItem);
4672 /* update the other column info */
4673 LISTVIEW_UpdateItemSize(infoPtr);
4674 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4675 LISTVIEW_InvalidateList(infoPtr);
4676 else
4677 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4679 return TRUE;
4682 /***
4683 * DESCRIPTION:
4684 * Invalidates the listview after an item's insertion or deletion.
4686 * PARAMETER(S):
4687 * [I] infoPtr : valid pointer to the listview structure
4688 * [I] nItem : item index
4689 * [I] dir : -1 if deleting, 1 if inserting
4691 * RETURN:
4692 * None
4694 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4696 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4697 INT nPerCol, nItemCol, nItemRow;
4698 RECT rcScroll;
4699 POINT Origin;
4701 /* if we don't refresh, what's the point of scrolling? */
4702 if (!is_redrawing(infoPtr)) return;
4704 assert (abs(dir) == 1);
4706 /* arrange icons if autoarrange is on */
4707 if (is_autoarrange(infoPtr))
4709 BOOL arrange = TRUE;
4710 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4711 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4712 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4715 /* scrollbars need updating */
4716 LISTVIEW_UpdateScroll(infoPtr);
4718 /* figure out the item's position */
4719 if (uView == LVS_REPORT)
4720 nPerCol = infoPtr->nItemCount + 1;
4721 else if (uView == LVS_LIST)
4722 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4723 else /* LVS_ICON, or LVS_SMALLICON */
4724 return;
4726 nItemCol = nItem / nPerCol;
4727 nItemRow = nItem % nPerCol;
4728 LISTVIEW_GetOrigin(infoPtr, &Origin);
4730 /* move the items below up a slot */
4731 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4732 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4733 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4734 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4735 OffsetRect(&rcScroll, Origin.x, Origin.y);
4736 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
4737 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4739 TRACE("Scrolling rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
4740 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4741 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4744 /* report has only that column, so we're done */
4745 if (uView == LVS_REPORT) return;
4747 /* now for LISTs, we have to deal with the columns to the right */
4748 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4749 rcScroll.top = 0;
4750 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4751 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4752 OffsetRect(&rcScroll, Origin.x, Origin.y);
4753 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4754 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4755 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4758 /***
4759 * DESCRIPTION:
4760 * Removes an item from the listview control.
4762 * PARAMETER(S):
4763 * [I] infoPtr : valid pointer to the listview structure
4764 * [I] nItem : item index
4766 * RETURN:
4767 * SUCCESS : TRUE
4768 * FAILURE : FALSE
4770 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4772 LVITEMW item;
4773 const UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4774 const BOOL is_icon = (uView == LVS_SMALLICON || uView == LVS_ICON);
4776 TRACE("(nItem=%d)\n", nItem);
4778 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4780 /* remove selection, and focus */
4781 item.state = 0;
4782 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4783 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4785 /* send LVN_DELETEITEM notification. */
4786 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
4788 /* we need to do this here, because we'll be deleting stuff */
4789 if (is_icon)
4790 LISTVIEW_InvalidateItem(infoPtr, nItem);
4792 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4794 HDPA hdpaSubItems;
4795 ITEMHDR *hdrItem;
4796 INT i;
4798 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4799 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4801 hdrItem = DPA_GetPtr(hdpaSubItems, i);
4802 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4803 Free(hdrItem);
4805 DPA_Destroy(hdpaSubItems);
4808 if (is_icon)
4810 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4811 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4814 infoPtr->nItemCount--;
4815 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4817 /* now is the invalidation fun */
4818 if (!is_icon)
4819 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4820 return TRUE;
4824 /***
4825 * DESCRIPTION:
4826 * Callback implementation for editlabel control
4828 * PARAMETER(S):
4829 * [I] infoPtr : valid pointer to the listview structure
4830 * [I] pszText : modified text
4831 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4833 * RETURN:
4834 * SUCCESS : TRUE
4835 * FAILURE : FALSE
4837 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4839 HWND hwndSelf = infoPtr->hwndSelf;
4840 NMLVDISPINFOW dispInfo;
4841 INT editedItem = infoPtr->nEditLabelItem;
4842 BOOL bSame;
4844 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4846 infoPtr->nEditLabelItem = -1;
4848 ZeroMemory(&dispInfo, sizeof(dispInfo));
4849 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4850 dispInfo.item.iItem = editedItem;
4851 dispInfo.item.iSubItem = 0;
4852 dispInfo.item.stateMask = ~0;
4853 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4855 if (isW)
4856 bSame = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
4857 else
4859 LPWSTR tmp = textdupTtoW(pszText, FALSE);
4860 bSame = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
4861 textfreeT(tmp, FALSE);
4863 if (bSame) return TRUE;
4865 /* add the text from the edit in */
4866 dispInfo.item.mask |= LVIF_TEXT;
4867 dispInfo.item.pszText = pszText;
4868 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4870 /* Do we need to update the Item Text */
4871 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4872 if (!IsWindow(hwndSelf))
4873 return FALSE;
4874 if (!pszText) return TRUE;
4876 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4878 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
4879 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
4880 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
4882 LISTVIEW_InvalidateItem(infoPtr, editedItem);
4883 return TRUE;
4887 ZeroMemory(&dispInfo, sizeof(dispInfo));
4888 dispInfo.item.mask = LVIF_TEXT;
4889 dispInfo.item.iItem = editedItem;
4890 dispInfo.item.iSubItem = 0;
4891 dispInfo.item.pszText = pszText;
4892 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4893 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4896 /***
4897 * DESCRIPTION:
4898 * Begin in place editing of specified list view item
4900 * PARAMETER(S):
4901 * [I] infoPtr : valid pointer to the listview structure
4902 * [I] nItem : item index
4903 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4905 * RETURN:
4906 * SUCCESS : TRUE
4907 * FAILURE : FALSE
4909 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4911 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4912 NMLVDISPINFOW dispInfo;
4913 RECT rect;
4914 HWND hwndSelf = infoPtr->hwndSelf;
4916 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4918 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4919 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4921 infoPtr->nEditLabelItem = nItem;
4923 /* Is the EditBox still there, if so remove it */
4924 if(infoPtr->hwndEdit != 0)
4926 SetFocus(infoPtr->hwndSelf);
4927 infoPtr->hwndEdit = 0;
4930 LISTVIEW_SetSelection(infoPtr, nItem);
4931 LISTVIEW_SetItemFocus(infoPtr, nItem);
4932 LISTVIEW_InvalidateItem(infoPtr, nItem);
4934 rect.left = LVIR_LABEL;
4935 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4937 ZeroMemory(&dispInfo, sizeof(dispInfo));
4938 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4939 dispInfo.item.iItem = nItem;
4940 dispInfo.item.iSubItem = 0;
4941 dispInfo.item.stateMask = ~0;
4942 dispInfo.item.pszText = szDispText;
4943 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4944 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4946 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4947 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4948 if (!infoPtr->hwndEdit) return 0;
4950 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4952 if (!IsWindow(hwndSelf))
4953 return 0;
4954 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4955 infoPtr->hwndEdit = 0;
4956 return 0;
4959 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4960 SetFocus(infoPtr->hwndEdit);
4961 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4962 return infoPtr->hwndEdit;
4966 /***
4967 * DESCRIPTION:
4968 * Ensures the specified item is visible, scrolling into view if necessary.
4970 * PARAMETER(S):
4971 * [I] infoPtr : valid pointer to the listview structure
4972 * [I] nItem : item index
4973 * [I] bPartial : partially or entirely visible
4975 * RETURN:
4976 * SUCCESS : TRUE
4977 * FAILURE : FALSE
4979 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4981 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4982 INT nScrollPosHeight = 0;
4983 INT nScrollPosWidth = 0;
4984 INT nHorzAdjust = 0;
4985 INT nVertAdjust = 0;
4986 INT nHorzDiff = 0;
4987 INT nVertDiff = 0;
4988 RECT rcItem, rcTemp;
4990 rcItem.left = LVIR_BOUNDS;
4991 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4993 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4995 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4997 /* scroll left/right, but in LVS_REPORT mode */
4998 if (uView == LVS_LIST)
4999 nScrollPosWidth = infoPtr->nItemWidth;
5000 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5001 nScrollPosWidth = 1;
5003 if (rcItem.left < infoPtr->rcList.left)
5005 nHorzAdjust = -1;
5006 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
5008 else
5010 nHorzAdjust = 1;
5011 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
5015 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
5017 /* scroll up/down, but not in LVS_LIST mode */
5018 if (uView == LVS_REPORT)
5019 nScrollPosHeight = infoPtr->nItemHeight;
5020 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
5021 nScrollPosHeight = 1;
5023 if (rcItem.top < infoPtr->rcList.top)
5025 nVertAdjust = -1;
5026 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
5028 else
5030 nVertAdjust = 1;
5031 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
5035 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
5037 if (nScrollPosWidth)
5039 INT diff = nHorzDiff / nScrollPosWidth;
5040 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
5041 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
5044 if (nScrollPosHeight)
5046 INT diff = nVertDiff / nScrollPosHeight;
5047 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
5048 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
5051 return TRUE;
5054 /***
5055 * DESCRIPTION:
5056 * Searches for an item with specific characteristics.
5058 * PARAMETER(S):
5059 * [I] hwnd : window handle
5060 * [I] nStart : base item index
5061 * [I] lpFindInfo : item information to look for
5063 * RETURN:
5064 * SUCCESS : index of item
5065 * FAILURE : -1
5067 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
5068 const LVFINDINFOW *lpFindInfo)
5070 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5071 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5072 BOOL bWrap = FALSE, bNearest = FALSE;
5073 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
5074 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
5075 POINT Position, Destination;
5076 LVITEMW lvItem;
5078 if (!lpFindInfo || nItem < 0) return -1;
5080 lvItem.mask = 0;
5081 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
5083 lvItem.mask |= LVIF_TEXT;
5084 lvItem.pszText = szDispText;
5085 lvItem.cchTextMax = DISP_TEXT_SIZE;
5088 if (lpFindInfo->flags & LVFI_WRAP)
5089 bWrap = TRUE;
5091 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
5092 (uView == LVS_ICON || uView ==LVS_SMALLICON))
5094 POINT Origin;
5095 RECT rcArea;
5097 LISTVIEW_GetOrigin(infoPtr, &Origin);
5098 Destination.x = lpFindInfo->pt.x - Origin.x;
5099 Destination.y = lpFindInfo->pt.y - Origin.y;
5100 switch(lpFindInfo->vkDirection)
5102 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
5103 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
5104 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
5105 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
5106 case VK_HOME: Destination.x = Destination.y = 0; break;
5107 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5108 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
5109 case VK_END:
5110 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
5111 Destination.x = rcArea.right;
5112 Destination.y = rcArea.bottom;
5113 break;
5114 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
5116 bNearest = TRUE;
5118 else Destination.x = Destination.y = 0;
5120 /* if LVFI_PARAM is specified, all other flags are ignored */
5121 if (lpFindInfo->flags & LVFI_PARAM)
5123 lvItem.mask |= LVIF_PARAM;
5124 bNearest = FALSE;
5125 lvItem.mask &= ~LVIF_TEXT;
5128 again:
5129 for (; nItem < nLast; nItem++)
5131 lvItem.iItem = nItem;
5132 lvItem.iSubItem = 0;
5133 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
5135 if (lvItem.mask & LVIF_PARAM)
5137 if (lpFindInfo->lParam == lvItem.lParam)
5138 return nItem;
5139 else
5140 continue;
5143 if (lvItem.mask & LVIF_TEXT)
5145 if (lpFindInfo->flags & LVFI_PARTIAL)
5147 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
5149 else
5151 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
5155 if (!bNearest) return nItem;
5157 /* This is very inefficient. To do a good job here,
5158 * we need a sorted array of (x,y) item positions */
5159 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5161 /* compute the distance^2 to the destination */
5162 xdist = Destination.x - Position.x;
5163 ydist = Destination.y - Position.y;
5164 dist = xdist * xdist + ydist * ydist;
5166 /* remember the distance, and item if it's closer */
5167 if (dist < mindist)
5169 mindist = dist;
5170 nNearestItem = nItem;
5174 if (bWrap)
5176 nItem = 0;
5177 nLast = min(nStart + 1, infoPtr->nItemCount);
5178 bWrap = FALSE;
5179 goto again;
5182 return nNearestItem;
5185 /***
5186 * DESCRIPTION:
5187 * Searches for an item with specific characteristics.
5189 * PARAMETER(S):
5190 * [I] hwnd : window handle
5191 * [I] nStart : base item index
5192 * [I] lpFindInfo : item information to look for
5194 * RETURN:
5195 * SUCCESS : index of item
5196 * FAILURE : -1
5198 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
5199 const LVFINDINFOA *lpFindInfo)
5201 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
5202 LVFINDINFOW fiw;
5203 INT res;
5204 LPWSTR strW = NULL;
5206 memcpy(&fiw, lpFindInfo, sizeof(fiw));
5207 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
5208 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
5209 textfreeT(strW, FALSE);
5210 return res;
5213 /***
5214 * DESCRIPTION:
5215 * Retrieves the background image of the listview control.
5217 * PARAMETER(S):
5218 * [I] infoPtr : valid pointer to the listview structure
5219 * [O] lpBkImage : background image attributes
5221 * RETURN:
5222 * SUCCESS : TRUE
5223 * FAILURE : FALSE
5225 /* static BOOL LISTVIEW_GetBkImage(const LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
5226 /* { */
5227 /* FIXME (listview, "empty stub!\n"); */
5228 /* return FALSE; */
5229 /* } */
5231 /***
5232 * DESCRIPTION:
5233 * Retrieves column attributes.
5235 * PARAMETER(S):
5236 * [I] infoPtr : valid pointer to the listview structure
5237 * [I] nColumn : column index
5238 * [IO] lpColumn : column information
5239 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
5240 * otherwise it is in fact a LPLVCOLUMNA
5242 * RETURN:
5243 * SUCCESS : TRUE
5244 * FAILURE : FALSE
5246 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
5248 COLUMN_INFO *lpColumnInfo;
5249 HDITEMW hdi;
5251 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5252 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
5254 /* initialize memory */
5255 ZeroMemory(&hdi, sizeof(hdi));
5257 if (lpColumn->mask & LVCF_TEXT)
5259 hdi.mask |= HDI_TEXT;
5260 hdi.pszText = lpColumn->pszText;
5261 hdi.cchTextMax = lpColumn->cchTextMax;
5264 if (lpColumn->mask & LVCF_IMAGE)
5265 hdi.mask |= HDI_IMAGE;
5267 if (lpColumn->mask & LVCF_ORDER)
5268 hdi.mask |= HDI_ORDER;
5270 if (lpColumn->mask & LVCF_SUBITEM)
5271 hdi.mask |= HDI_LPARAM;
5273 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
5275 if (lpColumn->mask & LVCF_FMT)
5276 lpColumn->fmt = lpColumnInfo->fmt;
5278 if (lpColumn->mask & LVCF_WIDTH)
5279 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
5281 if (lpColumn->mask & LVCF_IMAGE)
5282 lpColumn->iImage = hdi.iImage;
5284 if (lpColumn->mask & LVCF_ORDER)
5285 lpColumn->iOrder = hdi.iOrder;
5287 if (lpColumn->mask & LVCF_SUBITEM)
5288 lpColumn->iSubItem = hdi.lParam;
5290 return TRUE;
5294 static BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
5296 INT i;
5298 if (!lpiArray)
5299 return FALSE;
5301 /* FIXME: little hack */
5302 for (i = 0; i < iCount; i++)
5303 lpiArray[i] = i;
5305 return TRUE;
5308 /***
5309 * DESCRIPTION:
5310 * Retrieves the column width.
5312 * PARAMETER(S):
5313 * [I] infoPtr : valid pointer to the listview structure
5314 * [I] int : column index
5316 * RETURN:
5317 * SUCCESS : column width
5318 * FAILURE : zero
5320 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
5322 INT nColumnWidth = 0;
5323 HDITEMW hdItem;
5325 TRACE("nColumn=%d\n", nColumn);
5327 /* we have a 'column' in LIST and REPORT mode only */
5328 switch(infoPtr->dwStyle & LVS_TYPEMASK)
5330 case LVS_LIST:
5331 nColumnWidth = infoPtr->nItemWidth;
5332 break;
5333 case LVS_REPORT:
5334 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDM_ITEMCHANGED.
5335 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
5336 * HDM_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
5338 * TODO: should we do the same in LVM_GETCOLUMN?
5340 hdItem.mask = HDI_WIDTH;
5341 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
5343 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
5344 return 0;
5346 nColumnWidth = hdItem.cxy;
5347 break;
5350 TRACE("nColumnWidth=%d\n", nColumnWidth);
5351 return nColumnWidth;
5354 /***
5355 * DESCRIPTION:
5356 * In list or report display mode, retrieves the number of items that can fit
5357 * vertically in the visible area. In icon or small icon display mode,
5358 * retrieves the total number of visible items.
5360 * PARAMETER(S):
5361 * [I] infoPtr : valid pointer to the listview structure
5363 * RETURN:
5364 * Number of fully visible items.
5366 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
5368 switch (infoPtr->dwStyle & LVS_TYPEMASK)
5370 case LVS_ICON:
5371 case LVS_SMALLICON:
5372 return infoPtr->nItemCount;
5373 case LVS_REPORT:
5374 return LISTVIEW_GetCountPerColumn(infoPtr);
5375 case LVS_LIST:
5376 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
5378 assert(FALSE);
5379 return 0;
5382 /***
5383 * DESCRIPTION:
5384 * Retrieves an image list handle.
5386 * PARAMETER(S):
5387 * [I] infoPtr : valid pointer to the listview structure
5388 * [I] nImageList : image list identifier
5390 * RETURN:
5391 * SUCCESS : image list handle
5392 * FAILURE : NULL
5394 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
5396 switch (nImageList)
5398 case LVSIL_NORMAL: return infoPtr->himlNormal;
5399 case LVSIL_SMALL: return infoPtr->himlSmall;
5400 case LVSIL_STATE: return infoPtr->himlState;
5402 return NULL;
5405 /* LISTVIEW_GetISearchString */
5407 /***
5408 * DESCRIPTION:
5409 * Retrieves item attributes.
5411 * PARAMETER(S):
5412 * [I] hwnd : window handle
5413 * [IO] lpLVItem : item info
5414 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5415 * if FALSE, then lpLVItem is a LPLVITEMA.
5417 * NOTE:
5418 * This is the internal 'GetItem' interface -- it tries to
5419 * be smart and avoid text copies, if possible, by modifying
5420 * lpLVItem->pszText to point to the text string. Please note
5421 * that this is not always possible (e.g. OWNERDATA), so on
5422 * entry you *must* supply valid values for pszText, and cchTextMax.
5423 * The only difference to the documented interface is that upon
5424 * return, you should use *only* the lpLVItem->pszText, rather than
5425 * the buffer pointer you provided on input. Most code already does
5426 * that, so it's not a problem.
5427 * For the two cases when the text must be copied (that is,
5428 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
5430 * RETURN:
5431 * SUCCESS : TRUE
5432 * FAILURE : FALSE
5434 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5436 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5437 NMLVDISPINFOW dispInfo;
5438 ITEM_INFO *lpItem;
5439 ITEMHDR* pItemHdr;
5440 HDPA hdpaSubItems;
5441 INT isubitem;
5443 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5445 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5446 return FALSE;
5448 if (lpLVItem->mask == 0) return TRUE;
5450 /* make a local copy */
5451 isubitem = lpLVItem->iSubItem;
5453 /* a quick optimization if all we're asked is the focus state
5454 * these queries are worth optimising since they are common,
5455 * and can be answered in constant time, without the heavy accesses */
5456 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5457 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5459 lpLVItem->state = 0;
5460 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5461 lpLVItem->state |= LVIS_FOCUSED;
5462 return TRUE;
5465 ZeroMemory(&dispInfo, sizeof(dispInfo));
5467 /* if the app stores all the data, handle it separately */
5468 if (infoPtr->dwStyle & LVS_OWNERDATA)
5470 dispInfo.item.state = 0;
5472 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
5473 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5475 /* NOTE: copy only fields which we _know_ are initialized, some apps
5476 * depend on the uninitialized fields being 0 */
5477 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5478 dispInfo.item.iItem = lpLVItem->iItem;
5479 dispInfo.item.iSubItem = isubitem;
5480 if (lpLVItem->mask & LVIF_TEXT)
5482 dispInfo.item.pszText = lpLVItem->pszText;
5483 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5485 if (lpLVItem->mask & LVIF_STATE)
5486 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5487 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5488 dispInfo.item.stateMask = lpLVItem->stateMask;
5489 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5491 /* full size structure expected - _WIN32IE >= 0x560 */
5492 *lpLVItem = dispInfo.item;
5494 else if (lpLVItem->mask & LVIF_INDENT)
5496 /* indent member expected - _WIN32IE >= 0x300 */
5497 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5499 else
5501 /* minimal structure expected */
5502 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5504 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5507 /* make sure lParam is zeroed out */
5508 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5510 /* we store only a little state, so if we're not asked, we're done */
5511 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5513 /* if focus is handled by us, report it */
5514 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5516 lpLVItem->state &= ~LVIS_FOCUSED;
5517 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5518 lpLVItem->state |= LVIS_FOCUSED;
5521 /* and do the same for selection, if we handle it */
5522 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5524 lpLVItem->state &= ~LVIS_SELECTED;
5525 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5526 lpLVItem->state |= LVIS_SELECTED;
5529 return TRUE;
5532 /* find the item and subitem structures before we proceed */
5533 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5534 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5535 assert (lpItem);
5537 if (isubitem)
5539 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5540 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5541 if (!lpSubItem)
5543 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5544 isubitem = 0;
5547 else
5548 pItemHdr = &lpItem->hdr;
5550 /* Do we need to query the state from the app? */
5551 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5553 dispInfo.item.mask |= LVIF_STATE;
5554 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5557 /* Do we need to enquire about the image? */
5558 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5559 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5561 dispInfo.item.mask |= LVIF_IMAGE;
5562 dispInfo.item.iImage = I_IMAGECALLBACK;
5565 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5566 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5568 dispInfo.item.mask |= LVIF_TEXT;
5569 dispInfo.item.pszText = lpLVItem->pszText;
5570 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5571 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5572 *dispInfo.item.pszText = '\0';
5575 /* If we don't have all the requested info, query the application */
5576 if (dispInfo.item.mask != 0)
5578 dispInfo.item.iItem = lpLVItem->iItem;
5579 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5580 dispInfo.item.lParam = lpItem->lParam;
5581 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5582 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5585 /* we should not store values for subitems */
5586 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5588 /* Now, handle the iImage field */
5589 if (dispInfo.item.mask & LVIF_IMAGE)
5591 lpLVItem->iImage = dispInfo.item.iImage;
5592 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5593 pItemHdr->iImage = dispInfo.item.iImage;
5595 else if (lpLVItem->mask & LVIF_IMAGE)
5597 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5598 lpLVItem->iImage = pItemHdr->iImage;
5599 else
5600 lpLVItem->iImage = 0;
5603 /* The pszText field */
5604 if (dispInfo.item.mask & LVIF_TEXT)
5606 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5607 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5609 lpLVItem->pszText = dispInfo.item.pszText;
5611 else if (lpLVItem->mask & LVIF_TEXT)
5613 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5614 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5617 /* Next is the lParam field */
5618 if (dispInfo.item.mask & LVIF_PARAM)
5620 lpLVItem->lParam = dispInfo.item.lParam;
5621 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5622 lpItem->lParam = dispInfo.item.lParam;
5624 else if (lpLVItem->mask & LVIF_PARAM)
5625 lpLVItem->lParam = lpItem->lParam;
5627 /* if this is a subitem, we're done */
5628 if (isubitem) return TRUE;
5630 /* ... the state field (this one is different due to uCallbackmask) */
5631 if (lpLVItem->mask & LVIF_STATE)
5633 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
5634 if (dispInfo.item.mask & LVIF_STATE)
5636 lpLVItem->state &= ~dispInfo.item.stateMask;
5637 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5639 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5641 lpLVItem->state &= ~LVIS_FOCUSED;
5642 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5643 lpLVItem->state |= LVIS_FOCUSED;
5645 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5647 lpLVItem->state &= ~LVIS_SELECTED;
5648 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5649 lpLVItem->state |= LVIS_SELECTED;
5653 /* and last, but not least, the indent field */
5654 if (lpLVItem->mask & LVIF_INDENT)
5655 lpLVItem->iIndent = lpItem->iIndent;
5657 return TRUE;
5660 /***
5661 * DESCRIPTION:
5662 * Retrieves item attributes.
5664 * PARAMETER(S):
5665 * [I] hwnd : window handle
5666 * [IO] lpLVItem : item info
5667 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5668 * if FALSE, then lpLVItem is a LPLVITEMA.
5670 * NOTE:
5671 * This is the external 'GetItem' interface -- it properly copies
5672 * the text in the provided buffer.
5674 * RETURN:
5675 * SUCCESS : TRUE
5676 * FAILURE : FALSE
5678 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5680 LPWSTR pszText;
5681 BOOL bResult;
5683 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5684 return FALSE;
5686 pszText = lpLVItem->pszText;
5687 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5688 if (bResult && lpLVItem->pszText != pszText)
5689 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5690 lpLVItem->pszText = pszText;
5692 return bResult;
5696 /***
5697 * DESCRIPTION:
5698 * Retrieves the position (upper-left) of the listview control item.
5699 * Note that for LVS_ICON style, the upper-left is that of the icon
5700 * and not the bounding box.
5702 * PARAMETER(S):
5703 * [I] infoPtr : valid pointer to the listview structure
5704 * [I] nItem : item index
5705 * [O] lpptPosition : coordinate information
5707 * RETURN:
5708 * SUCCESS : TRUE
5709 * FAILURE : FALSE
5711 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5713 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5714 POINT Origin;
5716 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5718 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5720 LISTVIEW_GetOrigin(infoPtr, &Origin);
5721 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5723 if (uView == LVS_ICON)
5725 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5726 lpptPosition->y += ICON_TOP_PADDING;
5728 lpptPosition->x += Origin.x;
5729 lpptPosition->y += Origin.y;
5731 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
5732 return TRUE;
5736 /***
5737 * DESCRIPTION:
5738 * Retrieves the bounding rectangle for a listview control item.
5740 * PARAMETER(S):
5741 * [I] infoPtr : valid pointer to the listview structure
5742 * [I] nItem : item index
5743 * [IO] lprc : bounding rectangle coordinates
5744 * lprc->left specifies the portion of the item for which the bounding
5745 * rectangle will be retrieved.
5747 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5748 * including the icon and label.
5750 * * For LVS_ICON
5751 * * Experiment shows that native control returns:
5752 * * width = min (48, length of text line)
5753 * * .left = position.x - (width - iconsize.cx)/2
5754 * * .right = .left + width
5755 * * height = #lines of text * ntmHeight + icon height + 8
5756 * * .top = position.y - 2
5757 * * .bottom = .top + height
5758 * * separation between items .y = itemSpacing.cy - height
5759 * * .x = itemSpacing.cx - width
5760 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5762 * * For LVS_ICON
5763 * * Experiment shows that native control returns:
5764 * * width = iconSize.cx + 16
5765 * * .left = position.x - (width - iconsize.cx)/2
5766 * * .right = .left + width
5767 * * height = iconSize.cy + 4
5768 * * .top = position.y - 2
5769 * * .bottom = .top + height
5770 * * separation between items .y = itemSpacing.cy - height
5771 * * .x = itemSpacing.cx - width
5772 * LVIR_LABEL Returns the bounding rectangle of the item text.
5774 * * For LVS_ICON
5775 * * Experiment shows that native control returns:
5776 * * width = text length
5777 * * .left = position.x - width/2
5778 * * .right = .left + width
5779 * * height = ntmH * linecount + 2
5780 * * .top = position.y + iconSize.cy + 6
5781 * * .bottom = .top + height
5782 * * separation between items .y = itemSpacing.cy - height
5783 * * .x = itemSpacing.cx - width
5784 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5785 * rectangles, but excludes columns in report view.
5787 * RETURN:
5788 * SUCCESS : TRUE
5789 * FAILURE : FALSE
5791 * NOTES
5792 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5793 * upon whether the window has the focus currently and on whether the item
5794 * is the one with the focus. Ensure that the control's record of which
5795 * item has the focus agrees with the items' records.
5797 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5799 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5800 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5801 BOOL doLabel = TRUE, oversizedBox = FALSE;
5802 POINT Position, Origin;
5803 LVITEMW lvItem;
5805 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5807 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5809 LISTVIEW_GetOrigin(infoPtr, &Origin);
5810 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5812 /* Be smart and try to figure out the minimum we have to do */
5813 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5814 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5815 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5816 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5817 oversizedBox = TRUE;
5819 /* get what we need from the item before hand, so we make
5820 * only one request. This can speed up things, if data
5821 * is stored on the app side */
5822 lvItem.mask = 0;
5823 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5824 if (doLabel) lvItem.mask |= LVIF_TEXT;
5825 lvItem.iItem = nItem;
5826 lvItem.iSubItem = 0;
5827 lvItem.pszText = szDispText;
5828 lvItem.cchTextMax = DISP_TEXT_SIZE;
5829 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5830 /* we got the state already up, simulate it here, to avoid a reget */
5831 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5833 lvItem.mask |= LVIF_STATE;
5834 lvItem.stateMask = LVIS_FOCUSED;
5835 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5838 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5839 lprc->left = LVIR_BOUNDS;
5840 switch(lprc->left)
5842 case LVIR_ICON:
5843 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5844 break;
5846 case LVIR_LABEL:
5847 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
5848 break;
5850 case LVIR_BOUNDS:
5851 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5852 break;
5854 case LVIR_SELECTBOUNDS:
5855 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
5856 break;
5858 default:
5859 WARN("Unknown value: %d\n", lprc->left);
5860 return FALSE;
5863 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5865 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
5867 return TRUE;
5870 /***
5871 * DESCRIPTION:
5872 * Retrieves the spacing between listview control items.
5874 * PARAMETER(S):
5875 * [I] infoPtr : valid pointer to the listview structure
5876 * [IO] lprc : rectangle to receive the output
5877 * on input, lprc->top = nSubItem
5878 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5880 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5881 * not only those of the first column.
5882 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5884 * RETURN:
5885 * TRUE: success
5886 * FALSE: failure
5888 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5890 POINT Position;
5891 LVITEMW lvItem;
5892 INT nColumn;
5894 if (!lprc) return FALSE;
5896 nColumn = lprc->top;
5898 TRACE("(nItem=%d, nSubItem=%d)\n", nItem, lprc->top);
5899 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5900 if (lprc->top == 0)
5901 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5903 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5905 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5907 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
5909 lvItem.mask = 0;
5910 lvItem.iItem = nItem;
5911 lvItem.iSubItem = nColumn;
5913 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5914 switch(lprc->left)
5916 case LVIR_ICON:
5917 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
5918 break;
5920 case LVIR_LABEL:
5921 case LVIR_BOUNDS:
5922 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
5923 break;
5925 default:
5926 ERR("Unknown bounds=%d\n", lprc->left);
5927 return FALSE;
5930 OffsetRect(lprc, Position.x, Position.y);
5931 return TRUE;
5935 /***
5936 * DESCRIPTION:
5937 * Retrieves the width of a label.
5939 * PARAMETER(S):
5940 * [I] infoPtr : valid pointer to the listview structure
5942 * RETURN:
5943 * SUCCESS : string width (in pixels)
5944 * FAILURE : zero
5946 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *infoPtr, INT nItem)
5948 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5949 LVITEMW lvItem;
5951 TRACE("(nItem=%d)\n", nItem);
5953 lvItem.mask = LVIF_TEXT;
5954 lvItem.iItem = nItem;
5955 lvItem.iSubItem = 0;
5956 lvItem.pszText = szDispText;
5957 lvItem.cchTextMax = DISP_TEXT_SIZE;
5958 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5960 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5963 /***
5964 * DESCRIPTION:
5965 * Retrieves the spacing between listview control items.
5967 * PARAMETER(S):
5968 * [I] infoPtr : valid pointer to the listview structure
5969 * [I] bSmall : flag for small or large icon
5971 * RETURN:
5972 * Horizontal + vertical spacing
5974 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
5976 LONG lResult;
5978 if (!bSmall)
5980 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5982 else
5984 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5985 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5986 else
5987 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5989 return lResult;
5992 /***
5993 * DESCRIPTION:
5994 * Retrieves the state of a listview control item.
5996 * PARAMETER(S):
5997 * [I] infoPtr : valid pointer to the listview structure
5998 * [I] nItem : item index
5999 * [I] uMask : state mask
6001 * RETURN:
6002 * State specified by the mask.
6004 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
6006 LVITEMW lvItem;
6008 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6010 lvItem.iItem = nItem;
6011 lvItem.iSubItem = 0;
6012 lvItem.mask = LVIF_STATE;
6013 lvItem.stateMask = uMask;
6014 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
6016 return lvItem.state & uMask;
6019 /***
6020 * DESCRIPTION:
6021 * Retrieves the text of a listview control item or subitem.
6023 * PARAMETER(S):
6024 * [I] hwnd : window handle
6025 * [I] nItem : item index
6026 * [IO] lpLVItem : item information
6027 * [I] isW : TRUE if lpLVItem is Unicode
6029 * RETURN:
6030 * SUCCESS : string length
6031 * FAILURE : 0
6033 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
6035 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6037 lpLVItem->mask = LVIF_TEXT;
6038 lpLVItem->iItem = nItem;
6039 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
6041 return textlenT(lpLVItem->pszText, isW);
6044 /***
6045 * DESCRIPTION:
6046 * Searches for an item based on properties + relationships.
6048 * PARAMETER(S):
6049 * [I] infoPtr : valid pointer to the listview structure
6050 * [I] nItem : item index
6051 * [I] uFlags : relationship flag
6053 * RETURN:
6054 * SUCCESS : item index
6055 * FAILURE : -1
6057 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
6059 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6060 UINT uMask = 0;
6061 LVFINDINFOW lvFindInfo;
6062 INT nCountPerColumn;
6063 INT nCountPerRow;
6064 INT i;
6066 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
6067 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
6069 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
6071 if (uFlags & LVNI_CUT)
6072 uMask |= LVIS_CUT;
6074 if (uFlags & LVNI_DROPHILITED)
6075 uMask |= LVIS_DROPHILITED;
6077 if (uFlags & LVNI_FOCUSED)
6078 uMask |= LVIS_FOCUSED;
6080 if (uFlags & LVNI_SELECTED)
6081 uMask |= LVIS_SELECTED;
6083 /* if we're asked for the focused item, that's only one,
6084 * so it's worth optimizing */
6085 if (uFlags & LVNI_FOCUSED)
6087 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
6088 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
6091 if (uFlags & LVNI_ABOVE)
6093 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6095 while (nItem >= 0)
6097 nItem--;
6098 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6099 return nItem;
6102 else
6104 /* Special case for autoarrange - move 'til the top of a list */
6105 if (is_autoarrange(infoPtr))
6107 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6108 while (nItem - nCountPerRow >= 0)
6110 nItem -= nCountPerRow;
6111 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6112 return nItem;
6114 return -1;
6116 lvFindInfo.flags = LVFI_NEARESTXY;
6117 lvFindInfo.vkDirection = VK_UP;
6118 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6119 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6121 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6122 return nItem;
6126 else if (uFlags & LVNI_BELOW)
6128 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
6130 while (nItem < infoPtr->nItemCount)
6132 nItem++;
6133 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6134 return nItem;
6137 else
6139 /* Special case for autoarrange - move 'til the bottom of a list */
6140 if (is_autoarrange(infoPtr))
6142 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6143 while (nItem + nCountPerRow < infoPtr->nItemCount )
6145 nItem += nCountPerRow;
6146 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6147 return nItem;
6149 return -1;
6151 lvFindInfo.flags = LVFI_NEARESTXY;
6152 lvFindInfo.vkDirection = VK_DOWN;
6153 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6154 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6156 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6157 return nItem;
6161 else if (uFlags & LVNI_TOLEFT)
6163 if (uView == LVS_LIST)
6165 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6166 while (nItem - nCountPerColumn >= 0)
6168 nItem -= nCountPerColumn;
6169 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6170 return nItem;
6173 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6175 /* Special case for autoarrange - move 'ti the beginning of a row */
6176 if (is_autoarrange(infoPtr))
6178 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6179 while (nItem % nCountPerRow > 0)
6181 nItem --;
6182 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6183 return nItem;
6185 return -1;
6187 lvFindInfo.flags = LVFI_NEARESTXY;
6188 lvFindInfo.vkDirection = VK_LEFT;
6189 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6190 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6192 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6193 return nItem;
6197 else if (uFlags & LVNI_TORIGHT)
6199 if (uView == LVS_LIST)
6201 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
6202 while (nItem + nCountPerColumn < infoPtr->nItemCount)
6204 nItem += nCountPerColumn;
6205 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
6206 return nItem;
6209 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6211 /* Special case for autoarrange - move 'til the end of a row */
6212 if (is_autoarrange(infoPtr))
6214 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
6215 while (nItem % nCountPerRow < nCountPerRow - 1 )
6217 nItem ++;
6218 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6219 return nItem;
6221 return -1;
6223 lvFindInfo.flags = LVFI_NEARESTXY;
6224 lvFindInfo.vkDirection = VK_RIGHT;
6225 SendMessageW( infoPtr->hwndSelf, LVM_GETITEMPOSITION, nItem, (LPARAM)&lvFindInfo.pt );
6226 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
6228 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
6229 return nItem;
6233 else
6235 nItem++;
6237 /* search by index */
6238 for (i = nItem; i < infoPtr->nItemCount; i++)
6240 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
6241 return i;
6245 return -1;
6248 /* LISTVIEW_GetNumberOfWorkAreas */
6250 /***
6251 * DESCRIPTION:
6252 * Retrieves the origin coordinates when in icon or small icon display mode.
6254 * PARAMETER(S):
6255 * [I] infoPtr : valid pointer to the listview structure
6256 * [O] lpptOrigin : coordinate information
6258 * RETURN:
6259 * None.
6261 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
6263 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6264 INT nHorzPos = 0, nVertPos = 0;
6265 SCROLLINFO scrollInfo;
6267 scrollInfo.cbSize = sizeof(SCROLLINFO);
6268 scrollInfo.fMask = SIF_POS;
6270 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
6271 nHorzPos = scrollInfo.nPos;
6272 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
6273 nVertPos = scrollInfo.nPos;
6275 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
6277 lpptOrigin->x = infoPtr->rcList.left;
6278 lpptOrigin->y = infoPtr->rcList.top;
6279 if (uView == LVS_LIST)
6280 nHorzPos *= infoPtr->nItemWidth;
6281 else if (uView == LVS_REPORT)
6282 nVertPos *= infoPtr->nItemHeight;
6284 lpptOrigin->x -= nHorzPos;
6285 lpptOrigin->y -= nVertPos;
6287 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
6290 /***
6291 * DESCRIPTION:
6292 * Retrieves the width of a string.
6294 * PARAMETER(S):
6295 * [I] hwnd : window handle
6296 * [I] lpszText : text string to process
6297 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
6299 * RETURN:
6300 * SUCCESS : string width (in pixels)
6301 * FAILURE : zero
6303 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
6305 SIZE stringSize;
6307 stringSize.cx = 0;
6308 if (is_textT(lpszText, isW))
6310 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
6311 HDC hdc = GetDC(infoPtr->hwndSelf);
6312 HFONT hOldFont = SelectObject(hdc, hFont);
6314 if (isW)
6315 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
6316 else
6317 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
6318 SelectObject(hdc, hOldFont);
6319 ReleaseDC(infoPtr->hwndSelf, hdc);
6321 return stringSize.cx;
6324 /***
6325 * DESCRIPTION:
6326 * Determines which listview item is located at the specified position.
6328 * PARAMETER(S):
6329 * [I] infoPtr : valid pointer to the listview structure
6330 * [IO] lpht : hit test information
6331 * [I] subitem : fill out iSubItem.
6332 * [I] select : return the index only if the hit selects the item
6334 * NOTE:
6335 * (mm 20001022): We must not allow iSubItem to be touched, for
6336 * an app might pass only a structure with space up to iItem!
6337 * (MS Office 97 does that for instance in the file open dialog)
6339 * RETURN:
6340 * SUCCESS : item index
6341 * FAILURE : -1
6343 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
6345 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6346 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6347 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
6348 POINT Origin, Position, opt;
6349 LVITEMW lvItem;
6350 ITERATOR i;
6351 INT iItem;
6353 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
6355 lpht->flags = 0;
6356 lpht->iItem = -1;
6357 if (subitem) lpht->iSubItem = 0;
6359 if (infoPtr->rcList.left > lpht->pt.x)
6360 lpht->flags |= LVHT_TOLEFT;
6361 else if (infoPtr->rcList.right < lpht->pt.x)
6362 lpht->flags |= LVHT_TORIGHT;
6364 if (infoPtr->rcList.top > lpht->pt.y)
6365 lpht->flags |= LVHT_ABOVE;
6366 else if (infoPtr->rcList.bottom < lpht->pt.y)
6367 lpht->flags |= LVHT_BELOW;
6369 TRACE("lpht->flags=0x%x\n", lpht->flags);
6370 if (lpht->flags) return -1;
6372 lpht->flags |= LVHT_NOWHERE;
6374 LISTVIEW_GetOrigin(infoPtr, &Origin);
6376 /* first deal with the large items */
6377 rcSearch.left = lpht->pt.x;
6378 rcSearch.top = lpht->pt.y;
6379 rcSearch.right = rcSearch.left + 1;
6380 rcSearch.bottom = rcSearch.top + 1;
6382 iterator_frameditems(&i, infoPtr, &rcSearch);
6383 iterator_next(&i); /* go to first item in the sequence */
6384 iItem = i.nItem;
6385 iterator_destroy(&i);
6387 TRACE("lpht->iItem=%d\n", iItem);
6388 if (iItem == -1) return -1;
6390 lvItem.mask = LVIF_STATE | LVIF_TEXT;
6391 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
6392 lvItem.stateMask = LVIS_STATEIMAGEMASK;
6393 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
6394 lvItem.iItem = iItem;
6395 lvItem.iSubItem = 0;
6396 lvItem.pszText = szDispText;
6397 lvItem.cchTextMax = DISP_TEXT_SIZE;
6398 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
6399 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
6401 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
6402 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
6403 opt.x = lpht->pt.x - Position.x - Origin.x;
6404 opt.y = lpht->pt.y - Position.y - Origin.y;
6406 if (uView == LVS_REPORT)
6407 rcBounds = rcBox;
6408 else
6410 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6411 UnionRect(&rcBounds, &rcBounds, &rcState);
6413 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
6414 if (!PtInRect(&rcBounds, opt)) return -1;
6416 if (PtInRect(&rcIcon, opt))
6417 lpht->flags |= LVHT_ONITEMICON;
6418 else if (PtInRect(&rcLabel, opt))
6419 lpht->flags |= LVHT_ONITEMLABEL;
6420 else if (infoPtr->himlState && STATEIMAGEINDEX(lvItem.state) && PtInRect(&rcState, opt))
6421 lpht->flags |= LVHT_ONITEMSTATEICON;
6422 if (lpht->flags & LVHT_ONITEM)
6423 lpht->flags &= ~LVHT_NOWHERE;
6425 TRACE("lpht->flags=0x%x\n", lpht->flags);
6426 if (uView == LVS_REPORT && subitem)
6428 INT j;
6430 rcBounds.right = rcBounds.left;
6431 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
6433 rcBounds.left = rcBounds.right;
6434 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
6435 if (PtInRect(&rcBounds, opt))
6437 lpht->iSubItem = j;
6438 break;
6443 if (select && !(uView == LVS_REPORT &&
6444 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6445 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6447 if (uView == LVS_REPORT)
6449 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6450 UnionRect(&rcBounds, &rcBounds, &rcState);
6452 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6454 return lpht->iItem = iItem;
6458 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6459 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6460 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6461 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6462 their own sort proc. when sending LVM_SORTITEMS.
6464 /* Platform SDK:
6465 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6467 LVS_SORTXXX must be specified,
6468 LVS_OWNERDRAW is not set,
6469 <item>.pszText is not LPSTR_TEXTCALLBACK.
6471 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6472 are sorted based on item text..."
6474 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6476 ITEM_INFO* lv_first = DPA_GetPtr( (HDPA)first, 0 );
6477 ITEM_INFO* lv_second = DPA_GetPtr( (HDPA)second, 0 );
6478 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6480 /* if we're sorting descending, negate the return value */
6481 return (((const LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6484 /***
6485 * DESCRIPTION:
6486 * Inserts a new item in the listview control.
6488 * PARAMETER(S):
6489 * [I] infoPtr : valid pointer to the listview structure
6490 * [I] lpLVItem : item information
6491 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6493 * RETURN:
6494 * SUCCESS : new item index
6495 * FAILURE : -1
6497 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6499 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6500 INT nItem;
6501 HDPA hdpaSubItems;
6502 NMLISTVIEW nmlv;
6503 ITEM_INFO *lpItem;
6504 BOOL is_sorted, has_changed;
6505 LVITEMW item;
6506 HWND hwndSelf = infoPtr->hwndSelf;
6508 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6510 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6512 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6513 if (!lpLVItem || lpLVItem->iSubItem) return -1;
6515 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6517 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
6519 /* insert item in listview control data structure */
6520 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6521 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6523 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6524 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6526 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
6528 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6529 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6530 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6531 if (nItem == -1) goto fail;
6532 infoPtr->nItemCount++;
6534 /* shift indices first so they don't get tangled */
6535 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6537 /* set the item attributes */
6538 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6540 /* full size structure expected - _WIN32IE >= 0x560 */
6541 item = *lpLVItem;
6543 else if (lpLVItem->mask & LVIF_INDENT)
6545 /* indent member expected - _WIN32IE >= 0x300 */
6546 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6548 else
6550 /* minimal structure expected */
6551 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6553 item.iItem = nItem;
6554 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6556 item.mask |= LVIF_STATE;
6557 item.stateMask |= LVIS_STATEIMAGEMASK;
6558 item.state &= ~LVIS_STATEIMAGEMASK;
6559 item.state |= INDEXTOSTATEIMAGEMASK(1);
6561 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6563 /* if we're sorted, sort the list, and update the index */
6564 if (is_sorted)
6566 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6567 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6568 assert(nItem != -1);
6571 /* make room for the position, if we are in the right mode */
6572 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6574 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6575 goto undo;
6576 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6578 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6579 goto undo;
6583 /* send LVN_INSERTITEM notification */
6584 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6585 nmlv.iItem = nItem;
6586 nmlv.lParam = lpItem->lParam;
6587 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6588 if (!IsWindow(hwndSelf))
6589 return -1;
6591 /* align items (set position of each item) */
6592 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6594 POINT pt;
6596 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6597 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6598 else
6599 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6601 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6604 /* now is the invalidation fun */
6605 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6606 return nItem;
6608 undo:
6609 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6610 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6611 infoPtr->nItemCount--;
6612 fail:
6613 DPA_DeletePtr(hdpaSubItems, 0);
6614 DPA_Destroy (hdpaSubItems);
6615 Free (lpItem);
6616 return -1;
6619 /***
6620 * DESCRIPTION:
6621 * Redraws a range of items.
6623 * PARAMETER(S):
6624 * [I] infoPtr : valid pointer to the listview structure
6625 * [I] nFirst : first item
6626 * [I] nLast : last item
6628 * RETURN:
6629 * SUCCESS : TRUE
6630 * FAILURE : FALSE
6632 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6634 INT i;
6636 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6637 max(nFirst, nLast) >= infoPtr->nItemCount)
6638 return FALSE;
6640 for (i = nFirst; i <= nLast; i++)
6641 LISTVIEW_InvalidateItem(infoPtr, i);
6643 return TRUE;
6646 /***
6647 * DESCRIPTION:
6648 * Scroll the content of a listview.
6650 * PARAMETER(S):
6651 * [I] infoPtr : valid pointer to the listview structure
6652 * [I] dx : horizontal scroll amount in pixels
6653 * [I] dy : vertical scroll amount in pixels
6655 * RETURN:
6656 * SUCCESS : TRUE
6657 * FAILURE : FALSE
6659 * COMMENTS:
6660 * If the control is in report mode (LVS_REPORT) the control can
6661 * be scrolled only in line increments. "dy" will be rounded to the
6662 * nearest number of pixels that are a whole line. Ex: if line height
6663 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6664 * is passed, then the scroll will be 0. (per MSDN 7/2002)
6666 * For: (per experimentation with native control and CSpy ListView)
6667 * LVS_ICON dy=1 = 1 pixel (vertical only)
6668 * dx ignored
6669 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6670 * dx ignored
6671 * LVS_LIST dx=1 = 1 column (horizontal only)
6672 * but will only scroll 1 column per message
6673 * no matter what the value.
6674 * dy must be 0 or FALSE returned.
6675 * LVS_REPORT dx=1 = 1 pixel
6676 * dy= see above
6679 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6681 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6682 case LVS_REPORT:
6683 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6684 dy /= infoPtr->nItemHeight;
6685 break;
6686 case LVS_LIST:
6687 if (dy != 0) return FALSE;
6688 break;
6689 default: /* icon */
6690 dx = 0;
6691 break;
6694 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6695 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6697 return TRUE;
6700 /***
6701 * DESCRIPTION:
6702 * Sets the background color.
6704 * PARAMETER(S):
6705 * [I] infoPtr : valid pointer to the listview structure
6706 * [I] clrBk : background color
6708 * RETURN:
6709 * SUCCESS : TRUE
6710 * FAILURE : FALSE
6712 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6714 TRACE("(clrBk=%x)\n", clrBk);
6716 if(infoPtr->clrBk != clrBk) {
6717 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6718 infoPtr->clrBk = clrBk;
6719 if (clrBk == CLR_NONE)
6720 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
6721 else
6722 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6723 LISTVIEW_InvalidateList(infoPtr);
6726 return TRUE;
6729 /* LISTVIEW_SetBkImage */
6731 /*** Helper for {Insert,Set}ColumnT *only* */
6732 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
6733 const LVCOLUMNW *lpColumn, BOOL isW)
6735 if (lpColumn->mask & LVCF_FMT)
6737 /* format member is valid */
6738 lphdi->mask |= HDI_FORMAT;
6740 /* set text alignment (leftmost column must be left-aligned) */
6741 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
6742 lphdi->fmt |= HDF_LEFT;
6743 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
6744 lphdi->fmt |= HDF_RIGHT;
6745 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
6746 lphdi->fmt |= HDF_CENTER;
6748 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6749 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6751 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6753 lphdi->fmt |= HDF_IMAGE;
6754 lphdi->iImage = I_IMAGECALLBACK;
6758 if (lpColumn->mask & LVCF_WIDTH)
6760 lphdi->mask |= HDI_WIDTH;
6761 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6763 /* make it fill the remainder of the controls width */
6764 RECT rcHeader;
6765 INT item_index;
6767 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6769 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6770 lphdi->cxy += rcHeader.right - rcHeader.left;
6773 /* retrieve the layout of the header */
6774 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6775 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
6777 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6779 else
6780 lphdi->cxy = lpColumn->cx;
6783 if (lpColumn->mask & LVCF_TEXT)
6785 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6786 lphdi->fmt |= HDF_STRING;
6787 lphdi->pszText = lpColumn->pszText;
6788 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6791 if (lpColumn->mask & LVCF_IMAGE)
6793 lphdi->mask |= HDI_IMAGE;
6794 lphdi->iImage = lpColumn->iImage;
6797 if (lpColumn->mask & LVCF_ORDER)
6799 lphdi->mask |= HDI_ORDER;
6800 lphdi->iOrder = lpColumn->iOrder;
6805 /***
6806 * DESCRIPTION:
6807 * Inserts a new column.
6809 * PARAMETER(S):
6810 * [I] infoPtr : valid pointer to the listview structure
6811 * [I] nColumn : column index
6812 * [I] lpColumn : column information
6813 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6815 * RETURN:
6816 * SUCCESS : new column index
6817 * FAILURE : -1
6819 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6820 const LVCOLUMNW *lpColumn, BOOL isW)
6822 COLUMN_INFO *lpColumnInfo;
6823 INT nNewColumn;
6824 HDITEMW hdi;
6826 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6828 if (!lpColumn || nColumn < 0) return -1;
6829 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6831 ZeroMemory(&hdi, sizeof(HDITEMW));
6832 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6835 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
6836 * (can be seen in SPY) otherwise column never gets added.
6838 if (!(lpColumn->mask & LVCF_WIDTH)) {
6839 hdi.mask |= HDI_WIDTH;
6840 hdi.cxy = 10;
6844 * when the iSubItem is available Windows copies it to the header lParam. It seems
6845 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
6847 if (lpColumn->mask & LVCF_SUBITEM)
6849 hdi.mask |= HDI_LPARAM;
6850 hdi.lParam = lpColumn->iSubItem;
6853 /* insert item in header control */
6854 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6855 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6856 (WPARAM)nColumn, (LPARAM)&hdi);
6857 if (nNewColumn == -1) return -1;
6858 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6860 /* create our own column info */
6861 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6862 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6864 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6865 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6867 /* now we have to actually adjust the data */
6868 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6870 SUBITEM_INFO *lpSubItem;
6871 HDPA hdpaSubItems;
6872 INT nItem, i;
6874 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6876 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
6877 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6879 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
6880 if (lpSubItem->iSubItem >= nNewColumn)
6881 lpSubItem->iSubItem++;
6886 /* make space for the new column */
6887 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6888 LISTVIEW_UpdateItemSize(infoPtr);
6890 return nNewColumn;
6892 fail:
6893 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6894 if (lpColumnInfo)
6896 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6897 Free(lpColumnInfo);
6899 return -1;
6902 /***
6903 * DESCRIPTION:
6904 * Sets the attributes of a header item.
6906 * PARAMETER(S):
6907 * [I] infoPtr : valid pointer to the listview structure
6908 * [I] nColumn : column index
6909 * [I] lpColumn : column attributes
6910 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6912 * RETURN:
6913 * SUCCESS : TRUE
6914 * FAILURE : FALSE
6916 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
6917 const LVCOLUMNW *lpColumn, BOOL isW)
6919 HDITEMW hdi, hdiget;
6920 BOOL bResult;
6922 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6924 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6926 ZeroMemory(&hdi, sizeof(HDITEMW));
6927 if (lpColumn->mask & LVCF_FMT)
6929 hdi.mask |= HDI_FORMAT;
6930 hdiget.mask = HDI_FORMAT;
6931 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6932 hdi.fmt = hdiget.fmt & HDF_STRING;
6934 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6936 /* set header item attributes */
6937 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6938 if (!bResult) return FALSE;
6940 if (lpColumn->mask & LVCF_FMT)
6942 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6943 int oldFmt = lpColumnInfo->fmt;
6945 lpColumnInfo->fmt = lpColumn->fmt;
6946 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6948 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6949 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6953 return TRUE;
6956 /***
6957 * DESCRIPTION:
6958 * Sets the column order array
6960 * PARAMETERS:
6961 * [I] infoPtr : valid pointer to the listview structure
6962 * [I] iCount : number of elements in column order array
6963 * [I] lpiArray : pointer to column order array
6965 * RETURN:
6966 * SUCCESS : TRUE
6967 * FAILURE : FALSE
6969 static BOOL LISTVIEW_SetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6971 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6973 if (!lpiArray)
6974 return FALSE;
6976 return TRUE;
6980 /***
6981 * DESCRIPTION:
6982 * Sets the width of a column
6984 * PARAMETERS:
6985 * [I] infoPtr : valid pointer to the listview structure
6986 * [I] nColumn : column index
6987 * [I] cx : column width
6989 * RETURN:
6990 * SUCCESS : TRUE
6991 * FAILURE : FALSE
6993 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6995 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6996 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6997 INT max_cx = 0;
6998 HDITEMW hdi;
7000 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
7002 /* set column width only if in report or list mode */
7003 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
7005 /* take care of invalid cx values */
7006 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
7007 else if (uView == LVS_LIST && cx < 1) return FALSE;
7009 /* resize all columns if in LVS_LIST mode */
7010 if(uView == LVS_LIST)
7012 infoPtr->nItemWidth = cx;
7013 LISTVIEW_InvalidateList(infoPtr);
7014 return TRUE;
7017 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
7019 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
7021 INT nLabelWidth;
7022 LVITEMW lvItem;
7024 lvItem.mask = LVIF_TEXT;
7025 lvItem.iItem = 0;
7026 lvItem.iSubItem = nColumn;
7027 lvItem.pszText = szDispText;
7028 lvItem.cchTextMax = DISP_TEXT_SIZE;
7029 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7031 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
7032 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
7033 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
7035 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
7036 max_cx += infoPtr->iconSize.cx;
7037 max_cx += TRAILING_LABEL_PADDING;
7040 /* autosize based on listview items width */
7041 if(cx == LVSCW_AUTOSIZE)
7042 cx = max_cx;
7043 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
7045 /* if iCol is the last column make it fill the remainder of the controls width */
7046 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
7048 RECT rcHeader;
7049 POINT Origin;
7051 LISTVIEW_GetOrigin(infoPtr, &Origin);
7052 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
7054 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
7056 else
7058 /* Despite what the MS docs say, if this is not the last
7059 column, then MS resizes the column to the width of the
7060 largest text string in the column, including headers
7061 and items. This is different from LVSCW_AUTOSIZE in that
7062 LVSCW_AUTOSIZE ignores the header string length. */
7063 cx = 0;
7065 /* retrieve header text */
7066 hdi.mask = HDI_TEXT;
7067 hdi.cchTextMax = DISP_TEXT_SIZE;
7068 hdi.pszText = szDispText;
7069 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdi))
7071 HDC hdc = GetDC(infoPtr->hwndSelf);
7072 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
7073 SIZE size;
7075 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
7076 cx = size.cx + TRAILING_HEADER_PADDING;
7077 /* FIXME: Take into account the header image, if one is present */
7078 SelectObject(hdc, old_font);
7079 ReleaseDC(infoPtr->hwndSelf, hdc);
7081 cx = max (cx, max_cx);
7085 if (cx < 0) return FALSE;
7087 /* call header to update the column change */
7088 hdi.mask = HDI_WIDTH;
7089 hdi.cxy = cx;
7090 TRACE("hdi.cxy=%d\n", hdi.cxy);
7091 return Header_SetItemW(infoPtr->hwndHeader, nColumn, &hdi);
7094 /***
7095 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
7098 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
7100 HDC hdc_wnd, hdc;
7101 HBITMAP hbm_im, hbm_mask, hbm_orig;
7102 RECT rc;
7103 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
7104 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
7105 HIMAGELIST himl;
7107 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
7108 ILC_COLOR | ILC_MASK, 2, 2);
7109 hdc_wnd = GetDC(infoPtr->hwndSelf);
7110 hdc = CreateCompatibleDC(hdc_wnd);
7111 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
7112 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
7113 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
7115 rc.left = rc.top = 0;
7116 rc.right = GetSystemMetrics(SM_CXSMICON);
7117 rc.bottom = GetSystemMetrics(SM_CYSMICON);
7119 hbm_orig = SelectObject(hdc, hbm_mask);
7120 FillRect(hdc, &rc, hbr_white);
7121 InflateRect(&rc, -3, -3);
7122 FillRect(hdc, &rc, hbr_black);
7124 SelectObject(hdc, hbm_im);
7125 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
7126 SelectObject(hdc, hbm_orig);
7127 ImageList_Add(himl, hbm_im, hbm_mask);
7129 SelectObject(hdc, hbm_im);
7130 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
7131 SelectObject(hdc, hbm_orig);
7132 ImageList_Add(himl, hbm_im, hbm_mask);
7134 DeleteObject(hbm_mask);
7135 DeleteObject(hbm_im);
7136 DeleteDC(hdc);
7138 return himl;
7141 /***
7142 * DESCRIPTION:
7143 * Sets the extended listview style.
7145 * PARAMETERS:
7146 * [I] infoPtr : valid pointer to the listview structure
7147 * [I] dwMask : mask
7148 * [I] dwStyle : style
7150 * RETURN:
7151 * SUCCESS : previous style
7152 * FAILURE : 0
7154 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwExStyle)
7156 DWORD dwOldExStyle = infoPtr->dwLvExStyle;
7158 /* set new style */
7159 if (dwMask)
7160 infoPtr->dwLvExStyle = (dwOldExStyle & ~dwMask) | (dwExStyle & dwMask);
7161 else
7162 infoPtr->dwLvExStyle = dwExStyle;
7164 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_CHECKBOXES)
7166 HIMAGELIST himl = 0;
7167 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7169 LVITEMW item;
7170 item.mask = LVIF_STATE;
7171 item.stateMask = LVIS_STATEIMAGEMASK;
7172 item.state = INDEXTOSTATEIMAGEMASK(1);
7173 LISTVIEW_SetItemState(infoPtr, -1, &item);
7175 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
7177 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
7180 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_HEADERDRAGDROP)
7182 DWORD dwStyle = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
7183 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
7184 dwStyle |= HDS_DRAGDROP;
7185 else
7186 dwStyle &= ~HDS_DRAGDROP;
7187 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, dwStyle);
7190 /* GRIDLINES adds decoration at top so changes sizes */
7191 if((infoPtr->dwLvExStyle ^ dwOldExStyle) & LVS_EX_GRIDLINES)
7193 LISTVIEW_UpdateSize(infoPtr);
7197 LISTVIEW_InvalidateList(infoPtr);
7198 return dwOldExStyle;
7201 /***
7202 * DESCRIPTION:
7203 * Sets the new hot cursor used during hot tracking and hover selection.
7205 * PARAMETER(S):
7206 * [I] infoPtr : valid pointer to the listview structure
7207 * [I] hCursor : the new hot cursor handle
7209 * RETURN:
7210 * Returns the previous hot cursor
7212 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
7214 HCURSOR oldCursor = infoPtr->hHotCursor;
7216 infoPtr->hHotCursor = hCursor;
7218 return oldCursor;
7222 /***
7223 * DESCRIPTION:
7224 * Sets the hot item index.
7226 * PARAMETERS:
7227 * [I] infoPtr : valid pointer to the listview structure
7228 * [I] iIndex : index
7230 * RETURN:
7231 * SUCCESS : previous hot item index
7232 * FAILURE : -1 (no hot item)
7234 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
7236 INT iOldIndex = infoPtr->nHotItem;
7238 infoPtr->nHotItem = iIndex;
7240 return iOldIndex;
7244 /***
7245 * DESCRIPTION:
7246 * Sets the amount of time the cursor must hover over an item before it is selected.
7248 * PARAMETER(S):
7249 * [I] infoPtr : valid pointer to the listview structure
7250 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
7252 * RETURN:
7253 * Returns the previous hover time
7255 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
7257 DWORD oldHoverTime = infoPtr->dwHoverTime;
7259 infoPtr->dwHoverTime = dwHoverTime;
7261 return oldHoverTime;
7264 /***
7265 * DESCRIPTION:
7266 * Sets spacing for icons of LVS_ICON style.
7268 * PARAMETER(S):
7269 * [I] infoPtr : valid pointer to the listview structure
7270 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
7271 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
7273 * RETURN:
7274 * MAKELONG(oldcx, oldcy)
7276 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
7278 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7279 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7281 TRACE("requested=(%d,%d)\n", cx, cy);
7283 /* this is supported only for LVS_ICON style */
7284 if (uView != LVS_ICON) return oldspacing;
7286 /* set to defaults, if instructed to */
7287 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
7288 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
7290 /* if 0 then compute width
7291 * FIXME: Should scan each item and determine max width of
7292 * icon or label, then make that the width */
7293 if (cx == 0)
7294 cx = infoPtr->iconSpacing.cx;
7296 /* if 0 then compute height */
7297 if (cy == 0)
7298 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
7299 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
7302 infoPtr->iconSpacing.cx = cx;
7303 infoPtr->iconSpacing.cy = cy;
7305 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
7306 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
7307 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
7308 infoPtr->ntmHeight);
7310 /* these depend on the iconSpacing */
7311 LISTVIEW_UpdateItemSize(infoPtr);
7313 return oldspacing;
7316 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
7318 INT cx, cy;
7320 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
7322 size->cx = cx;
7323 size->cy = cy;
7325 else
7327 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
7328 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
7332 /***
7333 * DESCRIPTION:
7334 * Sets image lists.
7336 * PARAMETER(S):
7337 * [I] infoPtr : valid pointer to the listview structure
7338 * [I] nType : image list type
7339 * [I] himl : image list handle
7341 * RETURN:
7342 * SUCCESS : old image list
7343 * FAILURE : NULL
7345 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
7347 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7348 INT oldHeight = infoPtr->nItemHeight;
7349 HIMAGELIST himlOld = 0;
7351 TRACE("(nType=%d, himl=%p\n", nType, himl);
7353 switch (nType)
7355 case LVSIL_NORMAL:
7356 himlOld = infoPtr->himlNormal;
7357 infoPtr->himlNormal = himl;
7358 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
7359 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
7360 break;
7362 case LVSIL_SMALL:
7363 himlOld = infoPtr->himlSmall;
7364 infoPtr->himlSmall = himl;
7365 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
7366 break;
7368 case LVSIL_STATE:
7369 himlOld = infoPtr->himlState;
7370 infoPtr->himlState = himl;
7371 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
7372 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
7373 break;
7375 default:
7376 ERR("Unknown icon type=%d\n", nType);
7377 return NULL;
7380 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
7381 if (infoPtr->nItemHeight != oldHeight)
7382 LISTVIEW_UpdateScroll(infoPtr);
7384 return himlOld;
7387 /***
7388 * DESCRIPTION:
7389 * Preallocates memory (does *not* set the actual count of items !)
7391 * PARAMETER(S):
7392 * [I] infoPtr : valid pointer to the listview structure
7393 * [I] nItems : item count (projected number of items to allocate)
7394 * [I] dwFlags : update flags
7396 * RETURN:
7397 * SUCCESS : TRUE
7398 * FAILURE : FALSE
7400 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
7402 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
7404 if (infoPtr->dwStyle & LVS_OWNERDATA)
7406 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7407 INT nOldCount = infoPtr->nItemCount;
7409 if (nItems < nOldCount)
7411 RANGE range = { nItems, nOldCount };
7412 ranges_del(infoPtr->selectionRanges, range);
7413 if (infoPtr->nFocusedItem >= nItems)
7415 infoPtr->nFocusedItem = -1;
7416 SetRectEmpty(&infoPtr->rcFocus);
7420 infoPtr->nItemCount = nItems;
7421 LISTVIEW_UpdateScroll(infoPtr);
7423 /* the flags are valid only in ownerdata report and list modes */
7424 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
7426 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
7427 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
7429 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
7430 LISTVIEW_InvalidateList(infoPtr);
7431 else
7433 INT nFrom, nTo;
7434 POINT Origin;
7435 RECT rcErase;
7437 LISTVIEW_GetOrigin(infoPtr, &Origin);
7438 nFrom = min(nOldCount, nItems);
7439 nTo = max(nOldCount, nItems);
7441 if (uView == LVS_REPORT)
7443 rcErase.left = 0;
7444 rcErase.top = nFrom * infoPtr->nItemHeight;
7445 rcErase.right = infoPtr->nItemWidth;
7446 rcErase.bottom = nTo * infoPtr->nItemHeight;
7447 OffsetRect(&rcErase, Origin.x, Origin.y);
7448 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7449 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7451 else /* LVS_LIST */
7453 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
7455 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
7456 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
7457 rcErase.right = rcErase.left + infoPtr->nItemWidth;
7458 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7459 OffsetRect(&rcErase, Origin.x, Origin.y);
7460 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7461 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7463 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
7464 rcErase.top = 0;
7465 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
7466 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
7467 OffsetRect(&rcErase, Origin.x, Origin.y);
7468 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
7469 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
7473 else
7475 /* According to MSDN for non-LVS_OWNERDATA this is just
7476 * a performance issue. The control allocates its internal
7477 * data structures for the number of items specified. It
7478 * cuts down on the number of memory allocations. Therefore
7479 * we will just issue a WARN here
7481 WARN("for non-ownerdata performance option not implemented.\n");
7484 return TRUE;
7487 /***
7488 * DESCRIPTION:
7489 * Sets the position of an item.
7491 * PARAMETER(S):
7492 * [I] infoPtr : valid pointer to the listview structure
7493 * [I] nItem : item index
7494 * [I] pt : coordinate
7496 * RETURN:
7497 * SUCCESS : TRUE
7498 * FAILURE : FALSE
7500 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7502 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7503 POINT Origin;
7505 TRACE("(nItem=%d, &pt=%s\n", nItem, wine_dbgstr_point(&pt));
7507 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7508 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7510 LISTVIEW_GetOrigin(infoPtr, &Origin);
7512 /* This point value seems to be an undocumented feature.
7513 * The best guess is that it means either at the origin,
7514 * or at true beginning of the list. I will assume the origin. */
7515 if ((pt.x == -1) && (pt.y == -1))
7516 pt = Origin;
7518 if (uView == LVS_ICON)
7520 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7521 pt.y -= ICON_TOP_PADDING;
7523 pt.x -= Origin.x;
7524 pt.y -= Origin.y;
7526 infoPtr->bAutoarrange = FALSE;
7528 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7531 /***
7532 * DESCRIPTION:
7533 * Sets the state of one or many items.
7535 * PARAMETER(S):
7536 * [I] infoPtr : valid pointer to the listview structure
7537 * [I] nItem : item index
7538 * [I] lpLVItem : item or subitem info
7540 * RETURN:
7541 * SUCCESS : TRUE
7542 * FAILURE : FALSE
7544 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7546 BOOL bResult = TRUE;
7547 LVITEMW lvItem;
7549 lvItem.iItem = nItem;
7550 lvItem.iSubItem = 0;
7551 lvItem.mask = LVIF_STATE;
7552 lvItem.state = lpLVItem->state;
7553 lvItem.stateMask = lpLVItem->stateMask;
7554 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7556 if (nItem == -1)
7558 /* apply to all items */
7559 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7560 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7562 else
7563 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7566 * Update selection mark
7568 * Investigation on windows 2k showed that selection mark was updated
7569 * whenever a new selection was made, but if the selected item was
7570 * unselected it was not updated.
7572 * we are probably still not 100% accurate, but this at least sets the
7573 * proper selection mark when it is needed
7576 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7577 (infoPtr->nSelectionMark == -1))
7579 int i;
7580 for (i = 0; i < infoPtr->nItemCount; i++)
7582 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7584 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7586 infoPtr->nSelectionMark = i;
7587 break;
7590 else if (ranges_contain(infoPtr->selectionRanges, i))
7592 infoPtr->nSelectionMark = i;
7593 break;
7598 return bResult;
7601 /***
7602 * DESCRIPTION:
7603 * Sets the text of an item or subitem.
7605 * PARAMETER(S):
7606 * [I] hwnd : window handle
7607 * [I] nItem : item index
7608 * [I] lpLVItem : item or subitem info
7609 * [I] isW : TRUE if input is Unicode
7611 * RETURN:
7612 * SUCCESS : TRUE
7613 * FAILURE : FALSE
7615 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7617 LVITEMW lvItem;
7619 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7621 lvItem.iItem = nItem;
7622 lvItem.iSubItem = lpLVItem->iSubItem;
7623 lvItem.mask = LVIF_TEXT;
7624 lvItem.pszText = lpLVItem->pszText;
7625 lvItem.cchTextMax = lpLVItem->cchTextMax;
7627 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7629 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7632 /***
7633 * DESCRIPTION:
7634 * Set item index that marks the start of a multiple selection.
7636 * PARAMETER(S):
7637 * [I] infoPtr : valid pointer to the listview structure
7638 * [I] nIndex : index
7640 * RETURN:
7641 * Index number or -1 if there is no selection mark.
7643 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7645 INT nOldIndex = infoPtr->nSelectionMark;
7647 TRACE("(nIndex=%d)\n", nIndex);
7649 infoPtr->nSelectionMark = nIndex;
7651 return nOldIndex;
7654 /***
7655 * DESCRIPTION:
7656 * Sets the text background color.
7658 * PARAMETER(S):
7659 * [I] infoPtr : valid pointer to the listview structure
7660 * [I] clrTextBk : text background color
7662 * RETURN:
7663 * SUCCESS : TRUE
7664 * FAILURE : FALSE
7666 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7668 TRACE("(clrTextBk=%x)\n", clrTextBk);
7670 if (infoPtr->clrTextBk != clrTextBk)
7672 infoPtr->clrTextBk = clrTextBk;
7673 LISTVIEW_InvalidateList(infoPtr);
7676 return TRUE;
7679 /***
7680 * DESCRIPTION:
7681 * Sets the text foreground color.
7683 * PARAMETER(S):
7684 * [I] infoPtr : valid pointer to the listview structure
7685 * [I] clrText : text color
7687 * RETURN:
7688 * SUCCESS : TRUE
7689 * FAILURE : FALSE
7691 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7693 TRACE("(clrText=%x)\n", clrText);
7695 if (infoPtr->clrText != clrText)
7697 infoPtr->clrText = clrText;
7698 LISTVIEW_InvalidateList(infoPtr);
7701 return TRUE;
7704 /***
7705 * DESCRIPTION:
7706 * Determines which listview item is located at the specified position.
7708 * PARAMETER(S):
7709 * [I] infoPtr : valid pointer to the listview structure
7710 * [I] hwndNewToolTip : handle to new ToolTip
7712 * RETURN:
7713 * old tool tip
7715 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7717 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7718 infoPtr->hwndToolTip = hwndNewToolTip;
7719 return hwndOldToolTip;
7723 * DESCRIPTION:
7724 * sets the Unicode character format flag for the control
7725 * PARAMETER(S):
7726 * [I] infoPtr :valid pointer to the listview structure
7727 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
7729 * RETURN:
7730 * Old Unicode Format
7732 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL fUnicode)
7734 BOOL rc = infoPtr->notifyFormat;
7735 infoPtr->notifyFormat = (fUnicode)?NFR_UNICODE:NFR_ANSI;
7736 return rc;
7739 /* LISTVIEW_SetWorkAreas */
7741 /***
7742 * DESCRIPTION:
7743 * Callback internally used by LISTVIEW_SortItems()
7745 * PARAMETER(S):
7746 * [I] first : pointer to first ITEM_INFO to compare
7747 * [I] second : pointer to second ITEM_INFO to compare
7748 * [I] lParam : HWND of control
7750 * RETURN:
7751 * if first comes before second : negative
7752 * if first comes after second : positive
7753 * if first and second are equivalent : zero
7755 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7757 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7758 ITEM_INFO* lv_first = DPA_GetPtr( (HDPA)first, 0 );
7759 ITEM_INFO* lv_second = DPA_GetPtr( (HDPA)second, 0 );
7761 /* Forward the call to the client defined callback */
7762 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7765 /***
7766 * DESCRIPTION:
7767 * Sorts the listview items.
7769 * PARAMETER(S):
7770 * [I] infoPtr : valid pointer to the listview structure
7771 * [I] pfnCompare : application-defined value
7772 * [I] lParamSort : pointer to comparison callback
7774 * RETURN:
7775 * SUCCESS : TRUE
7776 * FAILURE : FALSE
7778 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7780 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7781 HDPA hdpaSubItems;
7782 ITEM_INFO *lpItem;
7783 LPVOID selectionMarkItem;
7784 LVITEMW item;
7785 int i;
7787 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7789 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7791 if (!pfnCompare) return FALSE;
7792 if (!infoPtr->hdpaItems) return FALSE;
7794 /* if there are 0 or 1 items, there is no need to sort */
7795 if (infoPtr->nItemCount < 2) return TRUE;
7797 if (infoPtr->nFocusedItem >= 0)
7799 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7800 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7801 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7803 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7804 /* clear the lpItem->state for non-selected ones */
7805 /* remove the selection ranges */
7807 infoPtr->pfnCompare = pfnCompare;
7808 infoPtr->lParamSort = lParamSort;
7809 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7811 /* Adjust selections and indices so that they are the way they should
7812 * be after the sort (otherwise, the list items move around, but
7813 * whatever is at the item's previous original position will be
7814 * selected instead)
7816 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7817 for (i=0; i < infoPtr->nItemCount; i++)
7819 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
7820 lpItem = DPA_GetPtr(hdpaSubItems, 0);
7822 if (lpItem->state & LVIS_SELECTED)
7824 item.state = LVIS_SELECTED;
7825 item.stateMask = LVIS_SELECTED;
7826 LISTVIEW_SetItemState(infoPtr, i, &item);
7828 if (lpItem->state & LVIS_FOCUSED)
7830 infoPtr->nFocusedItem = i;
7831 lpItem->state &= ~LVIS_FOCUSED;
7834 if (selectionMarkItem != NULL)
7835 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7836 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7838 /* refresh the display */
7839 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7840 LISTVIEW_InvalidateList(infoPtr);
7842 return TRUE;
7845 /***
7846 * DESCRIPTION:
7847 * Update theme handle after a theme change.
7849 * PARAMETER(S):
7850 * [I] infoPtr : valid pointer to the listview structure
7852 * RETURN:
7853 * SUCCESS : 0
7854 * FAILURE : something else
7856 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
7858 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
7859 CloseThemeData(theme);
7860 OpenThemeData(infoPtr->hwndSelf, themeClass);
7861 return 0;
7864 /***
7865 * DESCRIPTION:
7866 * Updates an items or rearranges the listview control.
7868 * PARAMETER(S):
7869 * [I] infoPtr : valid pointer to the listview structure
7870 * [I] nItem : item index
7872 * RETURN:
7873 * SUCCESS : TRUE
7874 * FAILURE : FALSE
7876 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7878 TRACE("(nItem=%d)\n", nItem);
7880 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7882 /* rearrange with default alignment style */
7883 if (is_autoarrange(infoPtr))
7884 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7885 else
7886 LISTVIEW_InvalidateItem(infoPtr, nItem);
7888 return TRUE;
7891 /***
7892 * DESCRIPTION:
7893 * Draw the track line at the place defined in the infoPtr structure.
7894 * The line is drawn with a XOR pen so drawing the line for the second time
7895 * in the same place erases the line.
7897 * PARAMETER(S):
7898 * [I] infoPtr : valid pointer to the listview structure
7900 * RETURN:
7901 * SUCCESS : TRUE
7902 * FAILURE : FALSE
7904 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
7906 HPEN hOldPen;
7907 HDC hdc;
7908 INT oldROP;
7910 if (infoPtr->xTrackLine == -1)
7911 return FALSE;
7913 if (!(hdc = GetDC(infoPtr->hwndSelf)))
7914 return FALSE;
7915 hOldPen = SelectObject(hdc, GetStockObject(BLACK_PEN));
7916 oldROP = SetROP2(hdc, R2_XORPEN);
7917 MoveToEx(hdc, infoPtr->xTrackLine, infoPtr->rcList.top, NULL);
7918 LineTo(hdc, infoPtr->xTrackLine, infoPtr->rcList.bottom);
7919 SetROP2(hdc, oldROP);
7920 SelectObject(hdc, hOldPen);
7921 ReleaseDC(infoPtr->hwndSelf, hdc);
7922 return TRUE;
7925 /***
7926 * DESCRIPTION:
7927 * Called when an edit control should be displayed. This function is called after
7928 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
7930 * PARAMETER(S):
7931 * [I] hwnd : Handle to the listview
7932 * [I] uMsg : WM_TIMER (ignored)
7933 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
7934 * [I] dwTimer : The elapsed time (ignored)
7936 * RETURN:
7937 * None.
7939 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
7941 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
7942 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
7944 KillTimer(hwnd, idEvent);
7945 editItem->fEnabled = FALSE;
7946 /* check if the item is still selected */
7947 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
7948 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
7951 /***
7952 * DESCRIPTION:
7953 * Creates the listview control - the WM_NCCREATE phase.
7955 * PARAMETER(S):
7956 * [I] hwnd : window handle
7957 * [I] lpcs : the create parameters
7959 * RETURN:
7960 * Success: TRUE
7961 * Failure: FALSE
7963 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
7965 LISTVIEW_INFO *infoPtr;
7966 LOGFONTW logFont;
7968 TRACE("(lpcs=%p)\n", lpcs);
7970 /* initialize info pointer */
7971 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
7972 if (!infoPtr) return FALSE;
7974 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
7976 infoPtr->hwndSelf = hwnd;
7977 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
7978 /* determine the type of structures to use */
7979 infoPtr->hwndNotify = lpcs->hwndParent;
7980 /* infoPtr->notifyFormat will be filled in WM_CREATE */
7982 /* initialize color information */
7983 infoPtr->clrBk = CLR_NONE;
7984 infoPtr->clrText = CLR_DEFAULT;
7985 infoPtr->clrTextBk = CLR_DEFAULT;
7986 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7988 /* set default values */
7989 infoPtr->nFocusedItem = -1;
7990 infoPtr->nSelectionMark = -1;
7991 infoPtr->nHotItem = -1;
7992 infoPtr->bRedraw = TRUE;
7993 infoPtr->bNoItemMetrics = TRUE;
7994 infoPtr->bDoChangeNotify = TRUE;
7995 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7996 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7997 infoPtr->nEditLabelItem = -1;
7998 infoPtr->dwHoverTime = -1; /* default system hover time */
7999 infoPtr->nMeasureItemHeight = 0;
8000 infoPtr->xTrackLine = -1; /* no track line */
8001 infoPtr->itemEdit.fEnabled = FALSE;
8003 /* get default font (icon title) */
8004 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
8005 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
8006 infoPtr->hFont = infoPtr->hDefaultFont;
8007 LISTVIEW_SaveTextMetrics(infoPtr);
8009 /* allocate memory for the data structure */
8010 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
8011 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
8012 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
8013 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
8014 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
8015 return TRUE;
8017 fail:
8018 DestroyWindow(infoPtr->hwndHeader);
8019 ranges_destroy(infoPtr->selectionRanges);
8020 DPA_Destroy(infoPtr->hdpaItems);
8021 DPA_Destroy(infoPtr->hdpaPosX);
8022 DPA_Destroy(infoPtr->hdpaPosY);
8023 DPA_Destroy(infoPtr->hdpaColumns);
8024 Free(infoPtr);
8025 return FALSE;
8028 /***
8029 * DESCRIPTION:
8030 * Creates the listview control - the WM_CREATE phase. Most of the data is
8031 * already set up in LISTVIEW_NCCreate
8033 * PARAMETER(S):
8034 * [I] hwnd : window handle
8035 * [I] lpcs : the create parameters
8037 * RETURN:
8038 * Success: 0
8039 * Failure: -1
8041 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
8043 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
8044 UINT uView = lpcs->style & LVS_TYPEMASK;
8045 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
8047 TRACE("(lpcs=%p)\n", lpcs);
8049 infoPtr->dwStyle = lpcs->style;
8050 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
8051 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
8053 /* setup creation flags */
8054 dFlags |= (LVS_NOSORTHEADER & lpcs->style) ? 0 : HDS_BUTTONS;
8055 dFlags |= (LVS_NOCOLUMNHEADER & lpcs->style) ? HDS_HIDDEN : 0;
8057 /* create header */
8058 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
8059 0, 0, 0, 0, hwnd, NULL,
8060 lpcs->hInstance, NULL);
8061 if (!infoPtr->hwndHeader) return -1;
8063 /* set header unicode format */
8064 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
8066 /* set header font */
8067 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
8069 /* init item size to avoid division by 0 */
8070 LISTVIEW_UpdateItemSize (infoPtr);
8072 if (uView == LVS_REPORT)
8074 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
8076 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8078 LISTVIEW_UpdateSize(infoPtr);
8079 LISTVIEW_UpdateScroll(infoPtr);
8082 OpenThemeData(hwnd, themeClass);
8084 /* initialize the icon sizes */
8085 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
8086 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
8087 return 0;
8090 /***
8091 * DESCRIPTION:
8092 * Destroys the listview control.
8094 * PARAMETER(S):
8095 * [I] infoPtr : valid pointer to the listview structure
8097 * RETURN:
8098 * Success: 0
8099 * Failure: -1
8101 static LRESULT LISTVIEW_Destroy(const LISTVIEW_INFO *infoPtr)
8103 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
8104 CloseThemeData(theme);
8105 return 0;
8108 /***
8109 * DESCRIPTION:
8110 * Enables the listview control.
8112 * PARAMETER(S):
8113 * [I] infoPtr : valid pointer to the listview structure
8114 * [I] bEnable : specifies whether to enable or disable the window
8116 * RETURN:
8117 * SUCCESS : TRUE
8118 * FAILURE : FALSE
8120 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr, BOOL bEnable)
8122 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
8123 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
8124 return TRUE;
8127 /***
8128 * DESCRIPTION:
8129 * Erases the background of the listview control.
8131 * PARAMETER(S):
8132 * [I] infoPtr : valid pointer to the listview structure
8133 * [I] hdc : device context handle
8135 * RETURN:
8136 * SUCCESS : TRUE
8137 * FAILURE : FALSE
8139 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
8141 RECT rc;
8143 TRACE("(hdc=%p)\n", hdc);
8145 if (!GetClipBox(hdc, &rc)) return FALSE;
8147 /* for double buffered controls we need to do this during refresh */
8148 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
8150 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
8154 /***
8155 * DESCRIPTION:
8156 * Helper function for LISTVIEW_[HV]Scroll *only*.
8157 * Performs vertical/horizontal scrolling by a give amount.
8159 * PARAMETER(S):
8160 * [I] infoPtr : valid pointer to the listview structure
8161 * [I] dx : amount of horizontal scroll
8162 * [I] dy : amount of vertical scroll
8164 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8166 /* now we can scroll the list */
8167 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
8168 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
8169 /* if we have focus, adjust rect */
8170 OffsetRect(&infoPtr->rcFocus, dx, dy);
8171 UpdateWindow(infoPtr->hwndSelf);
8174 /***
8175 * DESCRIPTION:
8176 * Performs vertical scrolling.
8178 * PARAMETER(S):
8179 * [I] infoPtr : valid pointer to the listview structure
8180 * [I] nScrollCode : scroll code
8181 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8182 * [I] hScrollWnd : scrollbar control window handle
8184 * RETURN:
8185 * Zero
8187 * NOTES:
8188 * SB_LINEUP/SB_LINEDOWN:
8189 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
8190 * for LVS_REPORT is 1 line
8191 * for LVS_LIST cannot occur
8194 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8195 INT nScrollDiff, HWND hScrollWnd)
8197 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8198 INT nOldScrollPos, nNewScrollPos;
8199 SCROLLINFO scrollInfo;
8200 BOOL is_an_icon;
8202 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8203 debugscrollcode(nScrollCode), nScrollDiff);
8205 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8207 scrollInfo.cbSize = sizeof(SCROLLINFO);
8208 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8210 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
8212 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
8214 nOldScrollPos = scrollInfo.nPos;
8215 switch (nScrollCode)
8217 case SB_INTERNAL:
8218 break;
8220 case SB_LINEUP:
8221 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
8222 break;
8224 case SB_LINEDOWN:
8225 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
8226 break;
8228 case SB_PAGEUP:
8229 nScrollDiff = -scrollInfo.nPage;
8230 break;
8232 case SB_PAGEDOWN:
8233 nScrollDiff = scrollInfo.nPage;
8234 break;
8236 case SB_THUMBPOSITION:
8237 case SB_THUMBTRACK:
8238 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8239 break;
8241 default:
8242 nScrollDiff = 0;
8245 /* quit right away if pos isn't changing */
8246 if (nScrollDiff == 0) return 0;
8248 /* calculate new position, and handle overflows */
8249 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8250 if (nScrollDiff > 0) {
8251 if (nNewScrollPos < nOldScrollPos ||
8252 nNewScrollPos > scrollInfo.nMax)
8253 nNewScrollPos = scrollInfo.nMax;
8254 } else {
8255 if (nNewScrollPos > nOldScrollPos ||
8256 nNewScrollPos < scrollInfo.nMin)
8257 nNewScrollPos = scrollInfo.nMin;
8260 /* set the new position, and reread in case it changed */
8261 scrollInfo.fMask = SIF_POS;
8262 scrollInfo.nPos = nNewScrollPos;
8263 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
8265 /* carry on only if it really changed */
8266 if (nNewScrollPos == nOldScrollPos) return 0;
8268 /* now adjust to client coordinates */
8269 nScrollDiff = nOldScrollPos - nNewScrollPos;
8270 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
8272 /* and scroll the window */
8273 scroll_list(infoPtr, 0, nScrollDiff);
8275 return 0;
8278 /***
8279 * DESCRIPTION:
8280 * Performs horizontal scrolling.
8282 * PARAMETER(S):
8283 * [I] infoPtr : valid pointer to the listview structure
8284 * [I] nScrollCode : scroll code
8285 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
8286 * [I] hScrollWnd : scrollbar control window handle
8288 * RETURN:
8289 * Zero
8291 * NOTES:
8292 * SB_LINELEFT/SB_LINERIGHT:
8293 * for LVS_ICON, LVS_SMALLICON 1 pixel
8294 * for LVS_REPORT is 1 pixel
8295 * for LVS_LIST is 1 column --> which is a 1 because the
8296 * scroll is based on columns not pixels
8299 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
8300 INT nScrollDiff, HWND hScrollWnd)
8302 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8303 INT nOldScrollPos, nNewScrollPos;
8304 SCROLLINFO scrollInfo;
8306 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
8307 debugscrollcode(nScrollCode), nScrollDiff);
8309 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8311 scrollInfo.cbSize = sizeof(SCROLLINFO);
8312 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
8314 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
8316 nOldScrollPos = scrollInfo.nPos;
8318 switch (nScrollCode)
8320 case SB_INTERNAL:
8321 break;
8323 case SB_LINELEFT:
8324 nScrollDiff = -1;
8325 break;
8327 case SB_LINERIGHT:
8328 nScrollDiff = 1;
8329 break;
8331 case SB_PAGELEFT:
8332 nScrollDiff = -scrollInfo.nPage;
8333 break;
8335 case SB_PAGERIGHT:
8336 nScrollDiff = scrollInfo.nPage;
8337 break;
8339 case SB_THUMBPOSITION:
8340 case SB_THUMBTRACK:
8341 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
8342 break;
8344 default:
8345 nScrollDiff = 0;
8348 /* quit right away if pos isn't changing */
8349 if (nScrollDiff == 0) return 0;
8351 /* calculate new position, and handle overflows */
8352 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
8353 if (nScrollDiff > 0) {
8354 if (nNewScrollPos < nOldScrollPos ||
8355 nNewScrollPos > scrollInfo.nMax)
8356 nNewScrollPos = scrollInfo.nMax;
8357 } else {
8358 if (nNewScrollPos > nOldScrollPos ||
8359 nNewScrollPos < scrollInfo.nMin)
8360 nNewScrollPos = scrollInfo.nMin;
8363 /* set the new position, and reread in case it changed */
8364 scrollInfo.fMask = SIF_POS;
8365 scrollInfo.nPos = nNewScrollPos;
8366 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
8368 /* carry on only if it really changed */
8369 if (nNewScrollPos == nOldScrollPos) return 0;
8371 if(uView == LVS_REPORT)
8372 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
8374 /* now adjust to client coordinates */
8375 nScrollDiff = nOldScrollPos - nNewScrollPos;
8376 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
8378 /* and scroll the window */
8379 scroll_list(infoPtr, nScrollDiff, 0);
8381 return 0;
8384 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
8386 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8387 INT gcWheelDelta = 0;
8388 INT pulScrollLines = 3;
8389 SCROLLINFO scrollInfo;
8391 TRACE("(wheelDelta=%d)\n", wheelDelta);
8393 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
8394 gcWheelDelta -= wheelDelta;
8396 scrollInfo.cbSize = sizeof(SCROLLINFO);
8397 scrollInfo.fMask = SIF_POS;
8399 switch(uView)
8401 case LVS_ICON:
8402 case LVS_SMALLICON:
8404 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
8405 * should be fixed in the future.
8407 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
8408 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
8409 break;
8411 case LVS_REPORT:
8412 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
8414 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
8415 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
8416 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
8418 break;
8420 case LVS_LIST:
8421 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
8422 break;
8424 return 0;
8427 /***
8428 * DESCRIPTION:
8429 * ???
8431 * PARAMETER(S):
8432 * [I] infoPtr : valid pointer to the listview structure
8433 * [I] nVirtualKey : virtual key
8434 * [I] lKeyData : key data
8436 * RETURN:
8437 * Zero
8439 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
8441 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8442 HWND hwndSelf = infoPtr->hwndSelf;
8443 INT nItem = -1;
8444 NMLVKEYDOWN nmKeyDown;
8446 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
8448 /* send LVN_KEYDOWN notification */
8449 nmKeyDown.wVKey = nVirtualKey;
8450 nmKeyDown.flags = 0;
8451 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
8452 if (!IsWindow(hwndSelf))
8453 return 0;
8455 switch (nVirtualKey)
8457 case VK_SPACE:
8458 nItem = infoPtr->nFocusedItem;
8459 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8460 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
8461 break;
8463 case VK_RETURN:
8464 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
8466 if (!notify(infoPtr, NM_RETURN)) return 0;
8467 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
8469 break;
8471 case VK_HOME:
8472 if (infoPtr->nItemCount > 0)
8473 nItem = 0;
8474 break;
8476 case VK_END:
8477 if (infoPtr->nItemCount > 0)
8478 nItem = infoPtr->nItemCount - 1;
8479 break;
8481 case VK_LEFT:
8482 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
8483 break;
8485 case VK_UP:
8486 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
8487 break;
8489 case VK_RIGHT:
8490 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
8491 break;
8493 case VK_DOWN:
8494 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
8495 break;
8497 case VK_PRIOR:
8498 if (uView == LVS_REPORT)
8500 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8501 if (infoPtr->nFocusedItem == topidx)
8502 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
8503 else
8504 nItem = topidx;
8506 else
8507 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
8508 * LISTVIEW_GetCountPerRow(infoPtr);
8509 if(nItem < 0) nItem = 0;
8510 break;
8512 case VK_NEXT:
8513 if (uView == LVS_REPORT)
8515 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
8516 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
8517 if (infoPtr->nFocusedItem == topidx + cnt - 1)
8518 nItem = infoPtr->nFocusedItem + cnt - 1;
8519 else
8520 nItem = topidx + cnt - 1;
8522 else
8523 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
8524 * LISTVIEW_GetCountPerRow(infoPtr);
8525 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
8526 break;
8529 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
8530 LISTVIEW_KeySelection(infoPtr, nItem);
8532 return 0;
8535 /***
8536 * DESCRIPTION:
8537 * Kills the focus.
8539 * PARAMETER(S):
8540 * [I] infoPtr : valid pointer to the listview structure
8542 * RETURN:
8543 * Zero
8545 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
8547 TRACE("()\n");
8549 /* if we did not have the focus, there's nothing to do */
8550 if (!infoPtr->bFocus) return 0;
8552 /* send NM_KILLFOCUS notification */
8553 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
8555 /* if we have a focus rectagle, get rid of it */
8556 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
8558 /* set window focus flag */
8559 infoPtr->bFocus = FALSE;
8561 /* invalidate the selected items before resetting focus flag */
8562 LISTVIEW_InvalidateSelectedItems(infoPtr);
8564 return 0;
8567 /***
8568 * DESCRIPTION:
8569 * Processes double click messages (left mouse button).
8571 * PARAMETER(S):
8572 * [I] infoPtr : valid pointer to the listview structure
8573 * [I] wKey : key flag
8574 * [I] x,y : mouse coordinate
8576 * RETURN:
8577 * Zero
8579 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8581 LVHITTESTINFO htInfo;
8583 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8585 /* Cancel the item edition if any */
8586 if (infoPtr->itemEdit.fEnabled)
8588 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
8589 infoPtr->itemEdit.fEnabled = FALSE;
8592 /* send NM_RELEASEDCAPTURE notification */
8593 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8595 htInfo.pt.x = x;
8596 htInfo.pt.y = y;
8598 /* send NM_DBLCLK notification */
8599 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
8600 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
8602 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
8603 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
8605 return 0;
8608 /***
8609 * DESCRIPTION:
8610 * Processes mouse down messages (left mouse button).
8612 * PARAMETERS:
8613 * infoPtr [I ] valid pointer to the listview structure
8614 * wKey [I ] key flag
8615 * x,y [I ] mouse coordinate
8617 * RETURN:
8618 * Zero
8620 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8622 LVHITTESTINFO lvHitTestInfo;
8623 static BOOL bGroupSelect = TRUE;
8624 POINT pt = { x, y };
8625 INT nItem;
8627 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8629 /* send NM_RELEASEDCAPTURE notification */
8630 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
8632 /* set left button down flag and record the click position */
8633 infoPtr->bLButtonDown = TRUE;
8634 infoPtr->ptClickPos = pt;
8635 infoPtr->bDragging = FALSE;
8637 lvHitTestInfo.pt.x = x;
8638 lvHitTestInfo.pt.y = y;
8640 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8641 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
8642 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8644 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8646 toggle_checkbox_state(infoPtr, nItem);
8647 return 0;
8650 if (infoPtr->dwStyle & LVS_SINGLESEL)
8652 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8653 infoPtr->nEditLabelItem = nItem;
8654 else
8655 LISTVIEW_SetSelection(infoPtr, nItem);
8657 else
8659 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8661 if (bGroupSelect)
8663 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
8664 LISTVIEW_SetItemFocus(infoPtr, nItem);
8665 infoPtr->nSelectionMark = nItem;
8667 else
8669 LVITEMW item;
8671 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8672 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8674 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8675 infoPtr->nSelectionMark = nItem;
8678 else if (wKey & MK_CONTROL)
8680 LVITEMW item;
8682 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8684 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8685 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8686 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8687 infoPtr->nSelectionMark = nItem;
8689 else if (wKey & MK_SHIFT)
8691 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8693 else
8695 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8696 infoPtr->nEditLabelItem = nItem;
8698 /* set selection (clears other pre-existing selections) */
8699 LISTVIEW_SetSelection(infoPtr, nItem);
8703 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
8704 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
8706 else
8708 /* remove all selections */
8709 LISTVIEW_DeselectAll(infoPtr);
8710 ReleaseCapture();
8713 return 0;
8716 /***
8717 * DESCRIPTION:
8718 * Processes mouse up messages (left mouse button).
8720 * PARAMETERS:
8721 * infoPtr [I ] valid pointer to the listview structure
8722 * wKey [I ] key flag
8723 * x,y [I ] mouse coordinate
8725 * RETURN:
8726 * Zero
8728 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
8730 LVHITTESTINFO lvHitTestInfo;
8732 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, x, y);
8734 if (!infoPtr->bLButtonDown) return 0;
8736 lvHitTestInfo.pt.x = x;
8737 lvHitTestInfo.pt.y = y;
8739 /* send NM_CLICK notification */
8740 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8741 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
8743 /* set left button flag */
8744 infoPtr->bLButtonDown = FALSE;
8746 if (infoPtr->bDragging)
8748 infoPtr->bDragging = FALSE;
8749 return 0;
8752 /* if we clicked on a selected item, edit the label */
8753 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8755 /* we want to make sure the user doesn't want to do a double click. So we will
8756 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
8758 infoPtr->itemEdit.fEnabled = TRUE;
8759 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
8760 SetTimer(infoPtr->hwndSelf,
8761 (UINT_PTR)&infoPtr->itemEdit,
8762 GetDoubleClickTime(),
8763 LISTVIEW_DelayedEditItem);
8766 if (!infoPtr->bFocus)
8767 SetFocus(infoPtr->hwndSelf);
8769 return 0;
8772 /***
8773 * DESCRIPTION:
8774 * Destroys the listview control (called after WM_DESTROY).
8776 * PARAMETER(S):
8777 * [I] infoPtr : valid pointer to the listview structure
8779 * RETURN:
8780 * Zero
8782 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8784 TRACE("()\n");
8786 /* delete all items */
8787 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
8789 /* destroy data structure */
8790 DPA_Destroy(infoPtr->hdpaItems);
8791 DPA_Destroy(infoPtr->hdpaPosX);
8792 DPA_Destroy(infoPtr->hdpaPosY);
8793 DPA_Destroy(infoPtr->hdpaColumns);
8794 ranges_destroy(infoPtr->selectionRanges);
8796 /* destroy image lists */
8797 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8799 if (infoPtr->himlNormal)
8800 ImageList_Destroy(infoPtr->himlNormal);
8801 if (infoPtr->himlSmall)
8802 ImageList_Destroy(infoPtr->himlSmall);
8803 if (infoPtr->himlState)
8804 ImageList_Destroy(infoPtr->himlState);
8807 /* destroy font, bkgnd brush */
8808 infoPtr->hFont = 0;
8809 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8810 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8812 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
8814 /* free listview info pointer*/
8815 Free(infoPtr);
8817 return 0;
8820 /***
8821 * DESCRIPTION:
8822 * Handles notifications from header.
8824 * PARAMETER(S):
8825 * [I] infoPtr : valid pointer to the listview structure
8826 * [I] nCtrlId : control identifier
8827 * [I] lpnmh : notification information
8829 * RETURN:
8830 * Zero
8832 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8834 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8835 HWND hwndSelf = infoPtr->hwndSelf;
8837 TRACE("(lpnmh=%p)\n", lpnmh);
8839 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8841 switch (lpnmh->hdr.code)
8843 case HDN_TRACKW:
8844 case HDN_TRACKA:
8846 COLUMN_INFO *lpColumnInfo;
8847 POINT ptOrigin;
8848 INT x;
8850 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8851 break;
8853 /* remove the old line (if any) */
8854 LISTVIEW_DrawTrackLine(infoPtr);
8856 /* compute & draw the new line */
8857 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8858 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
8859 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8860 infoPtr->xTrackLine = x + ptOrigin.x;
8861 LISTVIEW_DrawTrackLine(infoPtr);
8862 break;
8865 case HDN_ENDTRACKA:
8866 case HDN_ENDTRACKW:
8867 /* remove the track line (if any) */
8868 LISTVIEW_DrawTrackLine(infoPtr);
8869 infoPtr->xTrackLine = -1;
8870 break;
8872 case HDN_ENDDRAG:
8873 FIXME("Changing column order not implemented\n");
8874 return TRUE;
8876 case HDN_ITEMCHANGINGW:
8877 case HDN_ITEMCHANGINGA:
8878 return notify_forward_header(infoPtr, lpnmh);
8880 case HDN_ITEMCHANGEDW:
8881 case HDN_ITEMCHANGEDA:
8883 COLUMN_INFO *lpColumnInfo;
8884 INT dx, cxy;
8886 notify_forward_header(infoPtr, lpnmh);
8887 if (!IsWindow(hwndSelf))
8888 break;
8890 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8892 HDITEMW hdi;
8894 hdi.mask = HDI_WIDTH;
8895 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, &hdi)) return 0;
8896 cxy = hdi.cxy;
8898 else
8899 cxy = lpnmh->pitem->cxy;
8901 /* determine how much we change since the last know position */
8902 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8903 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8904 if (dx != 0)
8906 lpColumnInfo->rcHeader.right += dx;
8907 if (lpnmh->iItem + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
8908 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8909 else
8911 /* only needs to update the scrolls */
8912 infoPtr->nItemWidth += dx;
8913 LISTVIEW_UpdateScroll(infoPtr);
8915 LISTVIEW_UpdateItemSize(infoPtr);
8916 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8918 POINT ptOrigin;
8919 RECT rcCol = lpColumnInfo->rcHeader;
8921 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
8922 OffsetRect(&rcCol, ptOrigin.x, 0);
8924 rcCol.top = infoPtr->rcList.top;
8925 rcCol.bottom = infoPtr->rcList.bottom;
8927 /* resizing left-aligned columns leaves most of the left side untouched */
8928 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8930 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
8931 if (dx > 0)
8932 nMaxDirty += dx;
8933 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
8936 /* when shrinking the last column clear the now unused field */
8937 if (lpnmh->iItem == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1 && dx < 0)
8938 rcCol.right -= dx;
8940 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8944 break;
8946 case HDN_ITEMCLICKW:
8947 case HDN_ITEMCLICKA:
8949 /* Handle sorting by Header Column */
8950 NMLISTVIEW nmlv;
8952 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8953 nmlv.iItem = -1;
8954 nmlv.iSubItem = lpnmh->iItem;
8955 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8957 break;
8959 case HDN_DIVIDERDBLCLICKW:
8960 case HDN_DIVIDERDBLCLICKA:
8961 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
8962 break;
8965 return 0;
8968 /***
8969 * DESCRIPTION:
8970 * Paint non-client area of control.
8972 * PARAMETER(S):
8973 * [I] infoPtr : valid pointer to the listview structureof the sender
8974 * [I] region : update region
8976 * RETURN:
8977 * TRUE - frame was painted
8978 * FALSE - call default window proc
8980 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
8982 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
8983 HDC dc;
8984 RECT r;
8985 HRGN cliprgn;
8986 int cxEdge = GetSystemMetrics (SM_CXEDGE),
8987 cyEdge = GetSystemMetrics (SM_CYEDGE);
8989 if (!theme) return FALSE;
8991 GetWindowRect(infoPtr->hwndSelf, &r);
8993 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
8994 r.right - cxEdge, r.bottom - cyEdge);
8995 if (region != (HRGN)1)
8996 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
8997 OffsetRect(&r, -r.left, -r.top);
8999 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
9000 OffsetRect(&r, -r.left, -r.top);
9002 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
9003 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
9004 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
9005 ReleaseDC(infoPtr->hwndSelf, dc);
9007 /* Call default proc to get the scrollbars etc. painted */
9008 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
9010 return TRUE;
9013 /***
9014 * DESCRIPTION:
9015 * Determines the type of structure to use.
9017 * PARAMETER(S):
9018 * [I] infoPtr : valid pointer to the listview structureof the sender
9019 * [I] hwndFrom : listview window handle
9020 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
9022 * RETURN:
9023 * Zero
9025 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
9027 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
9029 if (nCommand == NF_REQUERY)
9030 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9032 return infoPtr->notifyFormat;
9035 /***
9036 * DESCRIPTION:
9037 * Paints/Repaints the listview control.
9039 * PARAMETER(S):
9040 * [I] infoPtr : valid pointer to the listview structure
9041 * [I] hdc : device context handle
9043 * RETURN:
9044 * Zero
9046 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
9048 TRACE("(hdc=%p)\n", hdc);
9050 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
9052 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9054 infoPtr->bNoItemMetrics = FALSE;
9055 LISTVIEW_UpdateItemSize(infoPtr);
9056 if (uView == LVS_ICON || uView == LVS_SMALLICON)
9057 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9058 LISTVIEW_UpdateScroll(infoPtr);
9061 UpdateWindow(infoPtr->hwndHeader);
9063 if (hdc)
9064 LISTVIEW_Refresh(infoPtr, hdc, NULL);
9065 else
9067 PAINTSTRUCT ps;
9069 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
9070 if (!hdc) return 1;
9071 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
9072 EndPaint(infoPtr->hwndSelf, &ps);
9075 return 0;
9079 /***
9080 * DESCRIPTION:
9081 * Paints/Repaints the listview control.
9083 * PARAMETER(S):
9084 * [I] infoPtr : valid pointer to the listview structure
9085 * [I] hdc : device context handle
9086 * [I] options : drawing options
9088 * RETURN:
9089 * Zero
9091 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
9093 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
9095 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
9096 return 0;
9098 if (options & PRF_ERASEBKGND)
9099 LISTVIEW_EraseBkgnd(infoPtr, hdc);
9101 if (options & PRF_CLIENT)
9102 LISTVIEW_Paint(infoPtr, hdc);
9104 return 0;
9108 /***
9109 * DESCRIPTION:
9110 * Processes double click messages (right mouse button).
9112 * PARAMETER(S):
9113 * [I] infoPtr : valid pointer to the listview structure
9114 * [I] wKey : key flag
9115 * [I] x,y : mouse coordinate
9117 * RETURN:
9118 * Zero
9120 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9122 LVHITTESTINFO lvHitTestInfo;
9124 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9126 /* send NM_RELEASEDCAPTURE notification */
9127 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9129 /* send NM_RDBLCLK notification */
9130 lvHitTestInfo.pt.x = x;
9131 lvHitTestInfo.pt.y = y;
9132 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9133 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
9135 return 0;
9138 /***
9139 * DESCRIPTION:
9140 * Processes mouse down messages (right mouse button).
9142 * PARAMETER(S):
9143 * [I] infoPtr : valid pointer to the listview structure
9144 * [I] wKey : key flag
9145 * [I] x,y : mouse coordinate
9147 * RETURN:
9148 * Zero
9150 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9152 LVHITTESTINFO lvHitTestInfo;
9153 INT nItem;
9155 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9157 /* send NM_RELEASEDCAPTURE notification */
9158 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
9160 /* make sure the listview control window has the focus */
9161 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
9163 /* set right button down flag */
9164 infoPtr->bRButtonDown = TRUE;
9166 /* determine the index of the selected item */
9167 lvHitTestInfo.pt.x = x;
9168 lvHitTestInfo.pt.y = y;
9169 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
9171 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
9173 LISTVIEW_SetItemFocus(infoPtr, nItem);
9174 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
9175 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
9176 LISTVIEW_SetSelection(infoPtr, nItem);
9178 else
9180 LISTVIEW_DeselectAll(infoPtr);
9183 return 0;
9186 /***
9187 * DESCRIPTION:
9188 * Processes mouse up messages (right mouse button).
9190 * PARAMETER(S):
9191 * [I] infoPtr : valid pointer to the listview structure
9192 * [I] wKey : key flag
9193 * [I] x,y : mouse coordinate
9195 * RETURN:
9196 * Zero
9198 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
9200 LVHITTESTINFO lvHitTestInfo;
9201 POINT pt;
9203 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, x, y);
9205 if (!infoPtr->bRButtonDown) return 0;
9207 /* set button flag */
9208 infoPtr->bRButtonDown = FALSE;
9210 /* Send NM_RClICK notification */
9211 lvHitTestInfo.pt.x = x;
9212 lvHitTestInfo.pt.y = y;
9213 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
9214 if (!notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo)) return 0;
9216 /* Change to screen coordinate for WM_CONTEXTMENU */
9217 pt = lvHitTestInfo.pt;
9218 ClientToScreen(infoPtr->hwndSelf, &pt);
9220 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
9221 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
9222 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
9224 return 0;
9228 /***
9229 * DESCRIPTION:
9230 * Sets the cursor.
9232 * PARAMETER(S):
9233 * [I] infoPtr : valid pointer to the listview structure
9234 * [I] hwnd : window handle of window containing the cursor
9235 * [I] nHittest : hit-test code
9236 * [I] wMouseMsg : ideintifier of the mouse message
9238 * RETURN:
9239 * TRUE if cursor is set
9240 * FALSE otherwise
9242 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
9244 LVHITTESTINFO lvHitTestInfo;
9246 if(!(LISTVIEW_isHotTracking(infoPtr))) return FALSE;
9248 if(!infoPtr->hHotCursor) return FALSE;
9250 GetCursorPos(&lvHitTestInfo.pt);
9251 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
9253 SetCursor(infoPtr->hHotCursor);
9255 return TRUE;
9258 /***
9259 * DESCRIPTION:
9260 * Sets the focus.
9262 * PARAMETER(S):
9263 * [I] infoPtr : valid pointer to the listview structure
9264 * [I] hwndLoseFocus : handle of previously focused window
9266 * RETURN:
9267 * Zero
9269 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
9271 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
9273 /* if we have the focus already, there's nothing to do */
9274 if (infoPtr->bFocus) return 0;
9276 /* send NM_SETFOCUS notification */
9277 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
9279 /* set window focus flag */
9280 infoPtr->bFocus = TRUE;
9282 /* put the focus rect back on */
9283 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
9285 /* redraw all visible selected items */
9286 LISTVIEW_InvalidateSelectedItems(infoPtr);
9288 return 0;
9291 /***
9292 * DESCRIPTION:
9293 * Sets the font.
9295 * PARAMETER(S):
9296 * [I] infoPtr : valid pointer to the listview structure
9297 * [I] fRedraw : font handle
9298 * [I] fRedraw : redraw flag
9300 * RETURN:
9301 * Zero
9303 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
9305 HFONT oldFont = infoPtr->hFont;
9307 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
9309 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
9310 if (infoPtr->hFont == oldFont) return 0;
9312 LISTVIEW_SaveTextMetrics(infoPtr);
9314 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
9316 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
9317 LISTVIEW_UpdateSize(infoPtr);
9318 LISTVIEW_UpdateScroll(infoPtr);
9321 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
9323 return 0;
9326 /***
9327 * DESCRIPTION:
9328 * Message handling for WM_SETREDRAW.
9329 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
9331 * PARAMETER(S):
9332 * [I] infoPtr : valid pointer to the listview structure
9333 * [I] bRedraw: state of redraw flag
9335 * RETURN:
9336 * DefWinProc return value
9338 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
9340 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
9342 /* we cannot use straight equality here because _any_ non-zero value is TRUE */
9343 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
9345 infoPtr->bRedraw = bRedraw;
9347 if(!bRedraw) return 0;
9349 if (is_autoarrange(infoPtr))
9350 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9351 LISTVIEW_UpdateScroll(infoPtr);
9353 /* despite what the WM_SETREDRAW docs says, apps expect us
9354 * to invalidate the listview here... stupid! */
9355 LISTVIEW_InvalidateList(infoPtr);
9357 return 0;
9360 /***
9361 * DESCRIPTION:
9362 * Resizes the listview control. This function processes WM_SIZE
9363 * messages. At this time, the width and height are not used.
9365 * PARAMETER(S):
9366 * [I] infoPtr : valid pointer to the listview structure
9367 * [I] Width : new width
9368 * [I] Height : new height
9370 * RETURN:
9371 * Zero
9373 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
9375 RECT rcOld = infoPtr->rcList;
9377 TRACE("(width=%d, height=%d)\n", Width, Height);
9379 LISTVIEW_UpdateSize(infoPtr);
9380 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
9382 /* do not bother with display related stuff if we're not redrawing */
9383 if (!is_redrawing(infoPtr)) return 0;
9385 if (is_autoarrange(infoPtr))
9386 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9388 LISTVIEW_UpdateScroll(infoPtr);
9390 /* refresh all only for lists whose height changed significantly */
9391 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
9392 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
9393 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
9394 LISTVIEW_InvalidateList(infoPtr);
9396 return 0;
9399 /***
9400 * DESCRIPTION:
9401 * Sets the size information.
9403 * PARAMETER(S):
9404 * [I] infoPtr : valid pointer to the listview structure
9406 * RETURN:
9407 * None
9409 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
9411 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
9413 TRACE("uView=%d, rcList(old)=%s\n", uView, wine_dbgstr_rect(&infoPtr->rcList));
9415 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
9417 if (uView == LVS_LIST)
9419 /* Apparently the "LIST" style is supposed to have the same
9420 * number of items in a column even if there is no scroll bar.
9421 * Since if a scroll bar already exists then the bottom is already
9422 * reduced, only reduce if the scroll bar does not currently exist.
9423 * The "2" is there to mimic the native control. I think it may be
9424 * related to either padding or edges. (GLA 7/2002)
9426 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
9427 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
9428 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
9430 else if (uView == LVS_REPORT)
9432 HDLAYOUT hl;
9433 WINDOWPOS wp;
9435 hl.prc = &infoPtr->rcList;
9436 hl.pwpos = &wp;
9437 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9438 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
9439 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy,
9440 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9441 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9442 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
9444 infoPtr->rcList.top = max(wp.cy, 0);
9445 infoPtr->rcList.top += (infoPtr->dwLvExStyle & LVS_EX_GRIDLINES) ? 2 : 0;
9448 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
9451 /***
9452 * DESCRIPTION:
9453 * Processes WM_STYLECHANGED messages.
9455 * PARAMETER(S):
9456 * [I] infoPtr : valid pointer to the listview structure
9457 * [I] wStyleType : window style type (normal or extended)
9458 * [I] lpss : window style information
9460 * RETURN:
9461 * Zero
9463 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
9464 const STYLESTRUCT *lpss)
9466 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
9467 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
9468 UINT style;
9470 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
9471 wStyleType, lpss->styleOld, lpss->styleNew);
9473 if (wStyleType != GWL_STYLE) return 0;
9475 /* FIXME: if LVS_NOSORTHEADER changed, update header */
9476 /* what if LVS_OWNERDATA changed? */
9477 /* or LVS_SINGLESEL */
9478 /* or LVS_SORT{AS,DES}CENDING */
9480 infoPtr->dwStyle = lpss->styleNew;
9482 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
9483 ((lpss->styleNew & WS_HSCROLL) == 0))
9484 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
9486 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
9487 ((lpss->styleNew & WS_VSCROLL) == 0))
9488 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
9490 if (uNewView != uOldView)
9492 SIZE oldIconSize = infoPtr->iconSize;
9493 HIMAGELIST himl;
9495 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9496 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9498 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9499 SetRectEmpty(&infoPtr->rcFocus);
9501 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9502 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
9504 if (uNewView == LVS_ICON)
9506 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
9508 TRACE("icon old size=(%d,%d), new size=(%d,%d)\n",
9509 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
9510 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
9513 else if (uNewView == LVS_REPORT)
9515 HDLAYOUT hl;
9516 WINDOWPOS wp;
9518 hl.prc = &infoPtr->rcList;
9519 hl.pwpos = &wp;
9520 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
9521 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9522 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
9523 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9526 LISTVIEW_UpdateItemSize(infoPtr);
9529 if (uNewView == LVS_REPORT)
9531 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
9533 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
9535 /* Turn off the header control */
9536 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
9537 TRACE("Hide header control, was 0x%08x\n", style);
9538 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
9539 } else {
9540 /* Turn on the header control */
9541 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
9543 TRACE("Show header control, was 0x%08x\n", style);
9544 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
9550 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
9551 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
9552 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9554 /* update the size of the client area */
9555 LISTVIEW_UpdateSize(infoPtr);
9557 /* add scrollbars if needed */
9558 LISTVIEW_UpdateScroll(infoPtr);
9560 /* invalidate client area + erase background */
9561 LISTVIEW_InvalidateList(infoPtr);
9563 return 0;
9566 /***
9567 * DESCRIPTION:
9568 * Window procedure of the listview control.
9571 static LRESULT WINAPI
9572 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9574 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9576 TRACE("(uMsg=%x wParam=%lx lParam=%lx)\n", uMsg, wParam, lParam);
9578 if (!infoPtr && (uMsg != WM_NCCREATE))
9579 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9581 switch (uMsg)
9583 case LVM_APPROXIMATEVIEWRECT:
9584 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
9585 LOWORD(lParam), HIWORD(lParam));
9586 case LVM_ARRANGE:
9587 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
9589 /* case LVM_CANCELEDITLABEL: */
9591 case LVM_CREATEDRAGIMAGE:
9592 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
9594 case LVM_DELETEALLITEMS:
9595 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
9597 case LVM_DELETECOLUMN:
9598 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
9600 case LVM_DELETEITEM:
9601 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
9603 case LVM_EDITLABELW:
9604 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
9606 case LVM_EDITLABELA:
9607 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
9609 /* case LVM_ENABLEGROUPVIEW: */
9611 case LVM_ENSUREVISIBLE:
9612 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
9614 case LVM_FINDITEMW:
9615 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
9617 case LVM_FINDITEMA:
9618 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
9620 case LVM_GETBKCOLOR:
9621 return infoPtr->clrBk;
9623 /* case LVM_GETBKIMAGE: */
9625 case LVM_GETCALLBACKMASK:
9626 return infoPtr->uCallbackMask;
9628 case LVM_GETCOLUMNA:
9629 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9631 case LVM_GETCOLUMNW:
9632 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9634 case LVM_GETCOLUMNORDERARRAY:
9635 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9637 case LVM_GETCOLUMNWIDTH:
9638 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
9640 case LVM_GETCOUNTPERPAGE:
9641 return LISTVIEW_GetCountPerPage(infoPtr);
9643 case LVM_GETEDITCONTROL:
9644 return (LRESULT)infoPtr->hwndEdit;
9646 case LVM_GETEXTENDEDLISTVIEWSTYLE:
9647 return infoPtr->dwLvExStyle;
9649 /* case LVM_GETGROUPINFO: */
9651 /* case LVM_GETGROUPMETRICS: */
9653 case LVM_GETHEADER:
9654 return (LRESULT)infoPtr->hwndHeader;
9656 case LVM_GETHOTCURSOR:
9657 return (LRESULT)infoPtr->hHotCursor;
9659 case LVM_GETHOTITEM:
9660 return infoPtr->nHotItem;
9662 case LVM_GETHOVERTIME:
9663 return infoPtr->dwHoverTime;
9665 case LVM_GETIMAGELIST:
9666 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
9668 /* case LVM_GETINSERTMARK: */
9670 /* case LVM_GETINSERTMARKCOLOR: */
9672 /* case LVM_GETINSERTMARKRECT: */
9674 case LVM_GETISEARCHSTRINGA:
9675 case LVM_GETISEARCHSTRINGW:
9676 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
9677 return FALSE;
9679 case LVM_GETITEMA:
9680 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
9682 case LVM_GETITEMW:
9683 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
9685 case LVM_GETITEMCOUNT:
9686 return infoPtr->nItemCount;
9688 case LVM_GETITEMPOSITION:
9689 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
9691 case LVM_GETITEMRECT:
9692 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
9694 case LVM_GETITEMSPACING:
9695 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
9697 case LVM_GETITEMSTATE:
9698 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
9700 case LVM_GETITEMTEXTA:
9701 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9703 case LVM_GETITEMTEXTW:
9704 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9706 case LVM_GETNEXTITEM:
9707 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
9709 case LVM_GETNUMBEROFWORKAREAS:
9710 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
9711 return 1;
9713 case LVM_GETORIGIN:
9714 if (!lParam) return FALSE;
9715 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT ||
9716 (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST) return FALSE;
9717 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
9718 return TRUE;
9720 /* case LVM_GETOUTLINECOLOR: */
9722 /* case LVM_GETSELECTEDCOLUMN: */
9724 case LVM_GETSELECTEDCOUNT:
9725 return LISTVIEW_GetSelectedCount(infoPtr);
9727 case LVM_GETSELECTIONMARK:
9728 return infoPtr->nSelectionMark;
9730 case LVM_GETSTRINGWIDTHA:
9731 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
9733 case LVM_GETSTRINGWIDTHW:
9734 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
9736 case LVM_GETSUBITEMRECT:
9737 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
9739 case LVM_GETTEXTBKCOLOR:
9740 return infoPtr->clrTextBk;
9742 case LVM_GETTEXTCOLOR:
9743 return infoPtr->clrText;
9745 /* case LVM_GETTILEINFO: */
9747 /* case LVM_GETTILEVIEWINFO: */
9749 case LVM_GETTOOLTIPS:
9750 if( !infoPtr->hwndToolTip )
9751 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
9752 return (LRESULT)infoPtr->hwndToolTip;
9754 case LVM_GETTOPINDEX:
9755 return LISTVIEW_GetTopIndex(infoPtr);
9757 case LVM_GETUNICODEFORMAT:
9758 return (infoPtr->notifyFormat == NFR_UNICODE);
9760 /* case LVM_GETVIEW: */
9762 case LVM_GETVIEWRECT:
9763 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
9765 case LVM_GETWORKAREAS:
9766 FIXME("LVM_GETWORKAREAS: unimplemented\n");
9767 return FALSE;
9769 /* case LVM_HASGROUP: */
9771 case LVM_HITTEST:
9772 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
9774 case LVM_INSERTCOLUMNA:
9775 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9777 case LVM_INSERTCOLUMNW:
9778 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9780 /* case LVM_INSERTGROUP: */
9782 /* case LVM_INSERTGROUPSORTED: */
9784 case LVM_INSERTITEMA:
9785 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9787 case LVM_INSERTITEMW:
9788 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9790 /* case LVM_INSERTMARKHITTEST: */
9792 /* case LVM_ISGROUPVIEWENABLED: */
9794 /* case LVM_MAPIDTOINDEX: */
9796 /* case LVM_MAPINDEXTOID: */
9798 /* case LVM_MOVEGROUP: */
9800 /* case LVM_MOVEITEMTOGROUP: */
9802 case LVM_REDRAWITEMS:
9803 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9805 /* case LVM_REMOVEALLGROUPS: */
9807 /* case LVM_REMOVEGROUP: */
9809 case LVM_SCROLL:
9810 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9812 case LVM_SETBKCOLOR:
9813 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9815 /* case LVM_SETBKIMAGE: */
9817 case LVM_SETCALLBACKMASK:
9818 infoPtr->uCallbackMask = (UINT)wParam;
9819 return TRUE;
9821 case LVM_SETCOLUMNA:
9822 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9824 case LVM_SETCOLUMNW:
9825 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9827 case LVM_SETCOLUMNORDERARRAY:
9828 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9830 case LVM_SETCOLUMNWIDTH:
9831 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9833 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9834 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9836 /* case LVM_SETGROUPINFO: */
9838 /* case LVM_SETGROUPMETRICS: */
9840 case LVM_SETHOTCURSOR:
9841 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9843 case LVM_SETHOTITEM:
9844 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9846 case LVM_SETHOVERTIME:
9847 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9849 case LVM_SETICONSPACING:
9850 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9852 case LVM_SETIMAGELIST:
9853 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9855 /* case LVM_SETINFOTIP: */
9857 /* case LVM_SETINSERTMARK: */
9859 /* case LVM_SETINSERTMARKCOLOR: */
9861 case LVM_SETITEMA:
9862 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9864 case LVM_SETITEMW:
9865 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9867 case LVM_SETITEMCOUNT:
9868 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9870 case LVM_SETITEMPOSITION:
9872 POINT pt;
9873 pt.x = (short)LOWORD(lParam);
9874 pt.y = (short)HIWORD(lParam);
9875 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9878 case LVM_SETITEMPOSITION32:
9879 if (lParam == 0) return FALSE;
9880 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9882 case LVM_SETITEMSTATE:
9883 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9885 case LVM_SETITEMTEXTA:
9886 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9888 case LVM_SETITEMTEXTW:
9889 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9891 /* case LVM_SETOUTLINECOLOR: */
9893 /* case LVM_SETSELECTEDCOLUMN: */
9895 case LVM_SETSELECTIONMARK:
9896 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9898 case LVM_SETTEXTBKCOLOR:
9899 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9901 case LVM_SETTEXTCOLOR:
9902 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9904 /* case LVM_SETTILEINFO: */
9906 /* case LVM_SETTILEVIEWINFO: */
9908 /* case LVM_SETTILEWIDTH: */
9910 case LVM_SETTOOLTIPS:
9911 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9913 case LVM_SETUNICODEFORMAT:
9914 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
9916 /* case LVM_SETVIEW: */
9918 /* case LVM_SETWORKAREAS: */
9920 /* case LVM_SORTGROUPS: */
9922 case LVM_SORTITEMS:
9923 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9925 /* LVM_SORTITEMSEX: */
9927 case LVM_SUBITEMHITTEST:
9928 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9930 case LVM_UPDATE:
9931 return LISTVIEW_Update(infoPtr, (INT)wParam);
9933 case WM_CHAR:
9934 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9936 case WM_COMMAND:
9937 return LISTVIEW_Command(infoPtr, wParam, lParam);
9939 case WM_NCCREATE:
9940 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
9942 case WM_CREATE:
9943 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9945 case WM_DESTROY:
9946 return LISTVIEW_Destroy(infoPtr);
9948 case WM_ENABLE:
9949 return LISTVIEW_Enable(infoPtr, (BOOL)wParam);
9951 case WM_ERASEBKGND:
9952 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9954 case WM_GETDLGCODE:
9955 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9957 case WM_GETFONT:
9958 return (LRESULT)infoPtr->hFont;
9960 case WM_HSCROLL:
9961 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9963 case WM_KEYDOWN:
9964 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9966 case WM_KILLFOCUS:
9967 return LISTVIEW_KillFocus(infoPtr);
9969 case WM_LBUTTONDBLCLK:
9970 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9972 case WM_LBUTTONDOWN:
9973 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9975 case WM_LBUTTONUP:
9976 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9978 case WM_MOUSEMOVE:
9979 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9981 case WM_MOUSEHOVER:
9982 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
9984 case WM_NCDESTROY:
9985 return LISTVIEW_NCDestroy(infoPtr);
9987 case WM_NCPAINT:
9988 if (LISTVIEW_NCPaint(infoPtr, (HRGN)wParam))
9989 return 0;
9990 goto fwd_msg;
9992 case WM_NOTIFY:
9993 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9994 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9995 else return 0;
9997 case WM_NOTIFYFORMAT:
9998 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
10000 case WM_PRINTCLIENT:
10001 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
10003 case WM_PAINT:
10004 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
10006 case WM_RBUTTONDBLCLK:
10007 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10009 case WM_RBUTTONDOWN:
10010 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10012 case WM_RBUTTONUP:
10013 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
10015 case WM_SETCURSOR:
10016 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
10017 return TRUE;
10018 goto fwd_msg;
10020 case WM_SETFOCUS:
10021 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
10023 case WM_SETFONT:
10024 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
10026 case WM_SETREDRAW:
10027 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
10029 case WM_SIZE:
10030 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
10032 case WM_STYLECHANGED:
10033 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
10035 case WM_SYSCOLORCHANGE:
10036 COMCTL32_RefreshSysColors();
10037 return 0;
10039 /* case WM_TIMER: */
10040 case WM_THEMECHANGED:
10041 return LISTVIEW_ThemeChanged(infoPtr);
10043 case WM_VSCROLL:
10044 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
10046 case WM_MOUSEWHEEL:
10047 if (wParam & (MK_SHIFT | MK_CONTROL))
10048 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10049 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
10051 case WM_WINDOWPOSCHANGED:
10052 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
10054 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
10055 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
10056 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
10058 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
10060 MEASUREITEMSTRUCT mis;
10061 mis.CtlType = ODT_LISTVIEW;
10062 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
10063 mis.itemID = -1;
10064 mis.itemWidth = 0;
10065 mis.itemData = 0;
10066 mis.itemHeight= infoPtr->nItemHeight;
10067 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
10068 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
10069 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
10072 LISTVIEW_UpdateSize(infoPtr);
10073 LISTVIEW_UpdateScroll(infoPtr);
10075 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10077 /* case WM_WININICHANGE: */
10079 default:
10080 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
10081 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
10083 fwd_msg:
10084 /* call default window procedure */
10085 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
10090 /***
10091 * DESCRIPTION:
10092 * Registers the window class.
10094 * PARAMETER(S):
10095 * None
10097 * RETURN:
10098 * None
10100 void LISTVIEW_Register(void)
10102 WNDCLASSW wndClass;
10104 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
10105 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
10106 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
10107 wndClass.cbClsExtra = 0;
10108 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
10109 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
10110 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
10111 wndClass.lpszClassName = WC_LISTVIEWW;
10112 RegisterClassW(&wndClass);
10115 /***
10116 * DESCRIPTION:
10117 * Unregisters the window class.
10119 * PARAMETER(S):
10120 * None
10122 * RETURN:
10123 * None
10125 void LISTVIEW_Unregister(void)
10127 UnregisterClassW(WC_LISTVIEWW, NULL);
10130 /***
10131 * DESCRIPTION:
10132 * Handle any WM_COMMAND messages
10134 * PARAMETER(S):
10135 * [I] infoPtr : valid pointer to the listview structure
10136 * [I] wParam : the first message parameter
10137 * [I] lParam : the second message parameter
10139 * RETURN:
10140 * Zero.
10142 static LRESULT LISTVIEW_Command(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10144 switch (HIWORD(wParam))
10146 case EN_UPDATE:
10149 * Adjust the edit window size
10151 WCHAR buffer[1024];
10152 HDC hdc = GetDC(infoPtr->hwndEdit);
10153 HFONT hFont, hOldFont = 0;
10154 RECT rect;
10155 SIZE sz;
10157 if (!infoPtr->hwndEdit || !hdc) return 0;
10158 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
10159 GetWindowRect(infoPtr->hwndEdit, &rect);
10161 /* Select font to get the right dimension of the string */
10162 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
10163 if(hFont != 0)
10165 hOldFont = SelectObject(hdc, hFont);
10168 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
10170 TEXTMETRICW textMetric;
10172 /* Add Extra spacing for the next character */
10173 GetTextMetricsW(hdc, &textMetric);
10174 sz.cx += (textMetric.tmMaxCharWidth * 2);
10176 SetWindowPos (
10177 infoPtr->hwndEdit,
10178 HWND_TOP,
10181 sz.cx,
10182 rect.bottom - rect.top,
10183 SWP_DRAWFRAME|SWP_NOMOVE);
10185 if(hFont != 0)
10186 SelectObject(hdc, hOldFont);
10188 ReleaseDC(infoPtr->hwndEdit, hdc);
10190 break;
10193 default:
10194 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
10197 return 0;
10201 /***
10202 * DESCRIPTION:
10203 * Subclassed edit control windproc function
10205 * PARAMETER(S):
10206 * [I] hwnd : the edit window handle
10207 * [I] uMsg : the message that is to be processed
10208 * [I] wParam : first message parameter
10209 * [I] lParam : second message parameter
10210 * [I] isW : TRUE if input is Unicode
10212 * RETURN:
10213 * Zero.
10215 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
10217 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
10218 BOOL cancel = FALSE;
10220 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
10221 hwnd, uMsg, wParam, lParam, isW);
10223 switch (uMsg)
10225 case WM_GETDLGCODE:
10226 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
10228 case WM_KILLFOCUS:
10229 break;
10231 case WM_DESTROY:
10233 WNDPROC editProc = infoPtr->EditWndProc;
10234 infoPtr->EditWndProc = 0;
10235 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
10236 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
10239 case WM_KEYDOWN:
10240 if (VK_ESCAPE == (INT)wParam)
10242 cancel = TRUE;
10243 break;
10245 else if (VK_RETURN == (INT)wParam)
10246 break;
10248 default:
10249 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
10252 /* kill the edit */
10253 if (infoPtr->hwndEdit)
10255 LPWSTR buffer = NULL;
10257 infoPtr->hwndEdit = 0;
10258 if (!cancel)
10260 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
10262 if (len)
10264 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
10266 if (isW) GetWindowTextW(hwnd, buffer, len+1);
10267 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
10271 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
10273 Free(buffer);
10276 SendMessageW(hwnd, WM_CLOSE, 0, 0);
10277 return 0;
10280 /***
10281 * DESCRIPTION:
10282 * Subclassed edit control Unicode windproc function
10284 * PARAMETER(S):
10285 * [I] hwnd : the edit window handle
10286 * [I] uMsg : the message that is to be processed
10287 * [I] wParam : first message parameter
10288 * [I] lParam : second message parameter
10290 * RETURN:
10292 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10294 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
10297 /***
10298 * DESCRIPTION:
10299 * Subclassed edit control ANSI windproc function
10301 * PARAMETER(S):
10302 * [I] hwnd : the edit window handle
10303 * [I] uMsg : the message that is to be processed
10304 * [I] wParam : first message parameter
10305 * [I] lParam : second message parameter
10307 * RETURN:
10309 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
10311 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
10314 /***
10315 * DESCRIPTION:
10316 * Creates a subclassed edit control
10318 * PARAMETER(S):
10319 * [I] infoPtr : valid pointer to the listview structure
10320 * [I] text : initial text for the edit
10321 * [I] style : the window style
10322 * [I] isW : TRUE if input is Unicode
10324 * RETURN:
10326 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
10327 INT x, INT y, INT width, INT height, BOOL isW)
10329 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
10330 HWND hedit;
10331 SIZE sz;
10332 HDC hdc;
10333 HDC hOldFont=0;
10334 TEXTMETRICW textMetric;
10335 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
10337 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
10339 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER;
10340 hdc = GetDC(infoPtr->hwndSelf);
10342 /* Select the font to get appropriate metric dimensions */
10343 if(infoPtr->hFont != 0)
10344 hOldFont = SelectObject(hdc, infoPtr->hFont);
10346 /*Get String Length in pixels */
10347 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
10349 /*Add Extra spacing for the next character */
10350 GetTextMetricsW(hdc, &textMetric);
10351 sz.cx += (textMetric.tmMaxCharWidth * 2);
10353 if(infoPtr->hFont != 0)
10354 SelectObject(hdc, hOldFont);
10356 ReleaseDC(infoPtr->hwndSelf, hdc);
10357 if (isW)
10358 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10359 else
10360 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
10362 if (!hedit) return 0;
10364 infoPtr->EditWndProc = (WNDPROC)
10365 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
10366 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
10368 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
10370 return hedit;