include: Use the hard-float calling convention for Windows APIs on ARM
[wine.git] / dlls / comctl32 / listview.c
blob35877976d82155512d70208991fb76a374fa6ac4
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
9 * Copyright 2009-2015 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
11 * Copyright 2012-2013 Daniel Jelinski
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 * NOTES
29 * This code was audited for completeness against the documented features
30 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
32 * Unless otherwise noted, we believe this code to be complete, as per
33 * the specification mentioned above.
34 * If you discover missing features, or bugs, please note them below.
36 * TODO:
38 * Default Message Processing
39 * -- WM_CREATE: create the icon and small icon image lists at this point only if
40 * the LVS_SHAREIMAGELISTS style is not specified.
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_StyleChanged doesn't handle some changes too well
59 * Speedups
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode.
67 * This would allow framing items (iterator_frameditems),
68 * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
70 * Flags
71 * -- LVIF_COLUMNS
72 * -- LVIF_GROUPID
74 * States
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVIS_DROPHILITED
78 * Styles
79 * -- LVS_NOLABELWRAP
80 * -- LVS_NOSCROLL (see Q137520)
81 * -- LVS_ALIGNTOP
83 * Extended Styles
84 * -- LVS_EX_BORDERSELECT
85 * -- LVS_EX_FLATSB
86 * -- LVS_EX_INFOTIP
87 * -- LVS_EX_LABELTIP
88 * -- LVS_EX_MULTIWORKAREAS
89 * -- LVS_EX_REGIONAL
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
95 * Notifications:
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
97 * -- LVN_GETINFOTIP
98 * -- LVN_HOTTRACK
99 * -- LVN_SETDISPINFO
101 * Messages:
102 * -- LVM_ENABLEGROUPVIEW
103 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
104 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
105 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
106 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
107 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
108 * -- LVM_GETINSERTMARKRECT
109 * -- LVM_GETNUMBEROFWORKAREAS
110 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
111 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
112 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
113 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
114 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
115 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
116 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
117 * -- LVM_INSERTGROUPSORTED
118 * -- LVM_INSERTMARKHITTEST
119 * -- LVM_ISGROUPVIEWENABLED
120 * -- LVM_MOVEGROUP
121 * -- LVM_MOVEITEMTOGROUP
122 * -- LVM_SETINFOTIP
123 * -- LVM_SETTILEWIDTH
124 * -- LVM_SORTGROUPS
126 * Macros:
127 * -- ListView_GetHoverTime, ListView_SetHoverTime
128 * -- ListView_GetISearchString
129 * -- ListView_GetNumberOfWorkAreas
130 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
132 * Functions:
133 * -- LVGroupComparE
136 #include "config.h"
137 #include "wine/port.h"
139 #include <assert.h>
140 #include <ctype.h>
141 #include <string.h>
142 #include <stdlib.h>
143 #include <stdarg.h>
144 #include <stdio.h>
146 #include "windef.h"
147 #include "winbase.h"
148 #include "winnt.h"
149 #include "wingdi.h"
150 #include "winuser.h"
151 #include "winnls.h"
152 #include "commctrl.h"
153 #include "comctl32.h"
154 #include "uxtheme.h"
156 #include "wine/debug.h"
157 #include "wine/unicode.h"
159 WINE_DEFAULT_DEBUG_CHANNEL(listview);
161 typedef struct tagCOLUMN_INFO
163 RECT rcHeader; /* tracks the header's rectangle */
164 INT fmt; /* same as LVCOLUMN.fmt */
165 INT cxMin;
166 } COLUMN_INFO;
168 typedef struct tagITEMHDR
170 LPWSTR pszText;
171 INT iImage;
172 } ITEMHDR, *LPITEMHDR;
174 typedef struct tagSUBITEM_INFO
176 ITEMHDR hdr;
177 INT iSubItem;
178 } SUBITEM_INFO;
180 typedef struct tagITEM_ID ITEM_ID;
182 typedef struct tagITEM_INFO
184 ITEMHDR hdr;
185 UINT state;
186 LPARAM lParam;
187 INT iIndent;
188 ITEM_ID *id;
189 } ITEM_INFO;
191 struct tagITEM_ID
193 UINT id; /* item id */
194 HDPA item; /* link to item data */
197 typedef struct tagRANGE
199 INT lower;
200 INT upper;
201 } RANGE;
203 typedef struct tagRANGES
205 HDPA hdpa;
206 } *RANGES;
208 typedef struct tagITERATOR
210 INT nItem;
211 INT nSpecial;
212 RANGE range;
213 RANGES ranges;
214 INT index;
215 } ITERATOR;
217 typedef struct tagDELAYED_ITEM_EDIT
219 BOOL fEnabled;
220 INT iItem;
221 } DELAYED_ITEM_EDIT;
223 typedef struct tagLISTVIEW_INFO
225 /* control window */
226 HWND hwndSelf;
227 RECT rcList; /* This rectangle is really the window
228 * client rectangle possibly reduced by the
229 * horizontal scroll bar and/or header - see
230 * LISTVIEW_UpdateSize. This rectangle offset
231 * by the LISTVIEW_GetOrigin value is in
232 * client coordinates */
234 /* notification window */
235 SHORT notifyFormat;
236 HWND hwndNotify;
237 BOOL bDoChangeNotify; /* send change notification messages? */
238 UINT uCallbackMask;
240 /* tooltips */
241 HWND hwndToolTip;
243 /* items */
244 INT nItemCount; /* the number of items in the list */
245 HDPA hdpaItems; /* array ITEM_INFO pointers */
246 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
247 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
248 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
249 RANGES selectionRanges;
250 INT nSelectionMark; /* item to start next multiselection from */
251 INT nHotItem;
253 /* columns */
254 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
255 BOOL colRectsDirty; /* trigger column rectangles requery from header */
257 /* item metrics */
258 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
259 INT nItemHeight;
260 INT nItemWidth;
262 /* sorting */
263 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
264 LPARAM lParamSort;
266 /* style */
267 DWORD dwStyle; /* the cached window GWL_STYLE */
268 DWORD dwLvExStyle; /* extended listview style */
269 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
271 /* edit item */
272 HWND hwndEdit;
273 WNDPROC EditWndProc;
274 INT nEditLabelItem;
275 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
277 /* icons */
278 HIMAGELIST himlNormal;
279 HIMAGELIST himlSmall;
280 HIMAGELIST himlState;
281 SIZE iconSize;
282 BOOL autoSpacing;
283 SIZE iconSpacing;
284 SIZE iconStateSize;
285 POINT currIconPos; /* this is the position next icon will be placed */
287 /* header */
288 HWND hwndHeader;
289 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
291 /* marquee selection */
292 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
293 BOOL bScrolling;
294 RECT marqueeRect; /* absolute coordinates of marquee selection */
295 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
296 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
298 /* focus drawing */
299 BOOL bFocus; /* control has focus */
300 INT nFocusedItem;
301 RECT rcFocus; /* focus bounds */
303 /* colors */
304 HBRUSH hBkBrush;
305 COLORREF clrBk;
306 COLORREF clrText;
307 COLORREF clrTextBk;
309 /* font */
310 HFONT hDefaultFont;
311 HFONT hFont;
312 INT ntmHeight; /* Some cached metrics of the font used */
313 INT ntmMaxCharWidth; /* by the listview to draw items */
314 INT nEllipsisWidth;
316 /* mouse operation */
317 BOOL bLButtonDown;
318 BOOL bDragging;
319 POINT ptClickPos; /* point where the user clicked */
320 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
321 DWORD dwHoverTime;
322 HCURSOR hHotCursor;
323 INT cWheelRemainder;
325 /* keyboard operation */
326 DWORD lastKeyPressTimestamp;
327 WPARAM charCode;
328 INT nSearchParamLength;
329 WCHAR szSearchParam[ MAX_PATH ];
331 /* painting */
332 BOOL bIsDrawing; /* Drawing in progress */
333 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
334 BOOL redraw; /* WM_SETREDRAW switch */
336 /* misc */
337 DWORD iVersion; /* CCM_[G,S]ETVERSION */
338 } LISTVIEW_INFO;
341 * constants
343 /* How many we debug buffer to allocate */
344 #define DEBUG_BUFFERS 20
345 /* The size of a single debug buffer */
346 #define DEBUG_BUFFER_SIZE 256
348 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
349 #define SB_INTERNAL -1
351 /* maximum size of a label */
352 #define DISP_TEXT_SIZE 260
354 /* padding for items in list and small icon display modes */
355 #define WIDTH_PADDING 12
357 /* padding for items in list, report and small icon display modes */
358 #define HEIGHT_PADDING 1
360 /* offset of items in report display mode */
361 #define REPORT_MARGINX 2
363 /* padding for icon in large icon display mode
364 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
365 * that HITTEST will see.
366 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
367 * ICON_TOP_PADDING - sum of the two above.
368 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
369 * LABEL_HOR_PADDING - between text and sides of box
370 * LABEL_VERT_PADDING - between bottom of text and end of box
372 * ICON_LR_PADDING - additional width above icon size.
373 * ICON_LR_HALF - half of the above value
375 #define ICON_TOP_PADDING_NOTHITABLE 2
376 #define ICON_TOP_PADDING_HITABLE 2
377 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
378 #define ICON_BOTTOM_PADDING 4
379 #define LABEL_HOR_PADDING 5
380 #define LABEL_VERT_PADDING 7
381 #define ICON_LR_PADDING 16
382 #define ICON_LR_HALF (ICON_LR_PADDING/2)
384 /* default label width for items in list and small icon display modes */
385 #define DEFAULT_LABEL_WIDTH 40
386 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
387 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
389 /* default column width for items in list display mode */
390 #define DEFAULT_COLUMN_WIDTH 128
392 /* Size of "line" scroll for V & H scrolls */
393 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
395 /* Padding between image and label */
396 #define IMAGE_PADDING 2
398 /* Padding behind the label */
399 #define TRAILING_LABEL_PADDING 12
400 #define TRAILING_HEADER_PADDING 11
402 /* Border for the icon caption */
403 #define CAPTION_BORDER 2
405 /* Standard DrawText flags */
406 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
407 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
408 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
410 /* Image index from state */
411 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
413 /* The time in milliseconds to reset the search in the list */
414 #define KEY_DELAY 450
416 /* Dump the LISTVIEW_INFO structure to the debug channel */
417 #define LISTVIEW_DUMP(iP) do { \
418 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
419 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
420 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
421 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
422 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
423 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
424 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
425 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
426 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
427 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
428 } while(0)
430 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
433 * forward declarations
435 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
436 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
437 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
438 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
439 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
440 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
441 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
442 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
443 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
444 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
445 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
446 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
447 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
448 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
449 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
450 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
451 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
452 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
453 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
454 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
456 /******** Text handling functions *************************************/
458 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
459 * text string. The string may be ANSI or Unicode, in which case
460 * the boolean isW tells us the type of the string.
462 * The name of the function tell what type of strings it expects:
463 * W: Unicode, T: ANSI/Unicode - function of isW
466 static inline BOOL is_text(LPCWSTR text)
468 return text != NULL && text != LPSTR_TEXTCALLBACKW;
471 static inline int textlenT(LPCWSTR text, BOOL isW)
473 return !is_text(text) ? 0 :
474 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
477 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
479 if (isDestW)
480 if (isSrcW) lstrcpynW(dest, src, max);
481 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
482 else
483 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
484 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
487 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
489 LPWSTR wstr = (LPWSTR)text;
491 if (!isW && is_text(text))
493 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
494 wstr = Alloc(len * sizeof(WCHAR));
495 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
497 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
498 return wstr;
501 static inline void textfreeT(LPWSTR wstr, BOOL isW)
503 if (!isW && is_text(wstr)) Free (wstr);
507 * dest is a pointer to a Unicode string
508 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
510 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
512 BOOL bResult = TRUE;
514 if (src == LPSTR_TEXTCALLBACKW)
516 if (is_text(*dest)) Free(*dest);
517 *dest = LPSTR_TEXTCALLBACKW;
519 else
521 LPWSTR pszText = textdupTtoW(src, isW);
522 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
523 bResult = Str_SetPtrW(dest, pszText);
524 textfreeT(pszText, isW);
526 return bResult;
530 * compares a Unicode to a Unicode/ANSI text string
532 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
534 if (!aw) return bt ? -1 : 0;
535 if (!bt) return 1;
536 if (aw == LPSTR_TEXTCALLBACKW)
537 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
538 if (bt != LPSTR_TEXTCALLBACKW)
540 LPWSTR bw = textdupTtoW(bt, isW);
541 int r = bw ? lstrcmpW(aw, bw) : 1;
542 textfreeT(bw, isW);
543 return r;
546 return 1;
549 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
551 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
552 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
555 /******** Debugging functions *****************************************/
557 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
559 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
560 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
563 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
565 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
566 n = min(textlenT(text, isW), n);
567 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
570 static char* debug_getbuf(void)
572 static int index = 0;
573 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
574 return buffers[index++ % DEBUG_BUFFERS];
577 static inline const char* debugrange(const RANGE *lprng)
579 if (!lprng) return "(null)";
580 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
583 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
585 char* buf = debug_getbuf(), *text = buf;
586 int len, size = DEBUG_BUFFER_SIZE;
588 if (pScrollInfo == NULL) return "(null)";
589 len = snprintf(buf, size, "{cbSize=%u, ", pScrollInfo->cbSize);
590 if (len == -1) goto end;
591 buf += len; size -= len;
592 if (pScrollInfo->fMask & SIF_RANGE)
593 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
594 else len = 0;
595 if (len == -1) goto end;
596 buf += len; size -= len;
597 if (pScrollInfo->fMask & SIF_PAGE)
598 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
599 else len = 0;
600 if (len == -1) goto end;
601 buf += len; size -= len;
602 if (pScrollInfo->fMask & SIF_POS)
603 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
604 else len = 0;
605 if (len == -1) goto end;
606 buf += len; size -= len;
607 if (pScrollInfo->fMask & SIF_TRACKPOS)
608 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
609 else len = 0;
610 if (len == -1) goto end;
611 buf += len;
612 goto undo;
613 end:
614 buf = text + strlen(text);
615 undo:
616 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
617 return text;
620 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
622 if (!plvnm) return "(null)";
623 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
624 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
625 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
626 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
629 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
631 char* buf = debug_getbuf(), *text = buf;
632 int len, size = DEBUG_BUFFER_SIZE;
634 if (lpLVItem == NULL) return "(null)";
635 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
636 if (len == -1) goto end;
637 buf += len; size -= len;
638 if (lpLVItem->mask & LVIF_STATE)
639 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
640 else len = 0;
641 if (len == -1) goto end;
642 buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_TEXT)
644 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
645 else len = 0;
646 if (len == -1) goto end;
647 buf += len; size -= len;
648 if (lpLVItem->mask & LVIF_IMAGE)
649 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
650 else len = 0;
651 if (len == -1) goto end;
652 buf += len; size -= len;
653 if (lpLVItem->mask & LVIF_PARAM)
654 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
655 else len = 0;
656 if (len == -1) goto end;
657 buf += len; size -= len;
658 if (lpLVItem->mask & LVIF_INDENT)
659 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
660 else len = 0;
661 if (len == -1) goto end;
662 buf += len;
663 goto undo;
664 end:
665 buf = text + strlen(text);
666 undo:
667 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
668 return text;
671 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
673 char* buf = debug_getbuf(), *text = buf;
674 int len, size = DEBUG_BUFFER_SIZE;
676 if (lpColumn == NULL) return "(null)";
677 len = snprintf(buf, size, "{");
678 if (len == -1) goto end;
679 buf += len; size -= len;
680 if (lpColumn->mask & LVCF_SUBITEM)
681 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
682 else len = 0;
683 if (len == -1) goto end;
684 buf += len; size -= len;
685 if (lpColumn->mask & LVCF_FMT)
686 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
687 else len = 0;
688 if (len == -1) goto end;
689 buf += len; size -= len;
690 if (lpColumn->mask & LVCF_WIDTH)
691 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
692 else len = 0;
693 if (len == -1) goto end;
694 buf += len; size -= len;
695 if (lpColumn->mask & LVCF_TEXT)
696 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
697 else len = 0;
698 if (len == -1) goto end;
699 buf += len; size -= len;
700 if (lpColumn->mask & LVCF_IMAGE)
701 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
702 else len = 0;
703 if (len == -1) goto end;
704 buf += len; size -= len;
705 if (lpColumn->mask & LVCF_ORDER)
706 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
707 else len = 0;
708 if (len == -1) goto end;
709 buf += len;
710 goto undo;
711 end:
712 buf = text + strlen(text);
713 undo:
714 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
715 return text;
718 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
720 if (!lpht) return "(null)";
722 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
723 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
726 /* Return the corresponding text for a given scroll value */
727 static inline LPCSTR debugscrollcode(int nScrollCode)
729 switch(nScrollCode)
731 case SB_LINELEFT: return "SB_LINELEFT";
732 case SB_LINERIGHT: return "SB_LINERIGHT";
733 case SB_PAGELEFT: return "SB_PAGELEFT";
734 case SB_PAGERIGHT: return "SB_PAGERIGHT";
735 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
736 case SB_THUMBTRACK: return "SB_THUMBTRACK";
737 case SB_ENDSCROLL: return "SB_ENDSCROLL";
738 case SB_INTERNAL: return "SB_INTERNAL";
739 default: return "unknown";
744 /******** Notification functions ************************************/
746 static int get_ansi_notification(UINT unicodeNotificationCode)
748 switch (unicodeNotificationCode)
750 case LVN_BEGINLABELEDITA:
751 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
752 case LVN_ENDLABELEDITA:
753 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
754 case LVN_GETDISPINFOA:
755 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
756 case LVN_SETDISPINFOA:
757 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
758 case LVN_ODFINDITEMA:
759 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
760 case LVN_GETINFOTIPA:
761 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
762 /* header forwards */
763 case HDN_TRACKA:
764 case HDN_TRACKW: return HDN_TRACKA;
765 case HDN_ENDTRACKA:
766 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
767 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
768 case HDN_ENDDRAG: return HDN_ENDDRAG;
769 case HDN_ITEMCHANGINGA:
770 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
771 case HDN_ITEMCHANGEDA:
772 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
773 case HDN_ITEMCLICKA:
774 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
775 case HDN_DIVIDERDBLCLICKA:
776 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
777 default: break;
779 FIXME("unknown notification %x\n", unicodeNotificationCode);
780 return unicodeNotificationCode;
783 /* forwards header notifications to listview parent */
784 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
786 LPCWSTR text = NULL, filter = NULL;
787 LRESULT ret;
788 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
790 /* on unicode format exit earlier */
791 if (infoPtr->notifyFormat == NFR_UNICODE)
792 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
793 (LPARAM)lpnmh);
795 /* header always supplies unicode notifications,
796 all we have to do is to convert strings to ANSI */
797 if (lpnmh->pitem)
799 /* convert item text */
800 if (lpnmh->pitem->mask & HDI_TEXT)
802 text = (LPCWSTR)lpnmh->pitem->pszText;
803 lpnmh->pitem->pszText = NULL;
804 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
806 /* convert filter text */
807 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
808 lpnmh->pitem->pvFilter)
810 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
811 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = NULL;
812 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
815 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
817 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
818 (LPARAM)lpnmh);
820 /* cleanup */
821 if(text)
823 Free(lpnmh->pitem->pszText);
824 lpnmh->pitem->pszText = (LPSTR)text;
826 if(filter)
828 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
829 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
832 return ret;
835 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
837 LRESULT result;
839 TRACE("(code=%d)\n", code);
841 pnmh->hwndFrom = infoPtr->hwndSelf;
842 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
843 pnmh->code = code;
844 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
846 TRACE(" <= %ld\n", result);
848 return result;
851 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
853 NMHDR nmh;
854 HWND hwnd = infoPtr->hwndSelf;
855 notify_hdr(infoPtr, code, &nmh);
856 return IsWindow(hwnd);
859 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
861 NMITEMACTIVATE nmia;
862 LVITEMW item;
864 nmia.uNewState = 0;
865 nmia.uOldState = 0;
866 nmia.uChanged = 0;
867 nmia.uKeyFlags = 0;
869 item.mask = LVIF_PARAM|LVIF_STATE;
870 item.iItem = htInfo->iItem;
871 item.iSubItem = 0;
872 item.stateMask = (UINT)-1;
873 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
874 nmia.lParam = item.lParam;
875 nmia.uOldState = item.state;
876 nmia.uNewState = item.state | LVIS_ACTIVATING;
877 nmia.uChanged = LVIF_STATE;
880 nmia.iItem = htInfo->iItem;
881 nmia.iSubItem = htInfo->iSubItem;
882 nmia.ptAction = htInfo->pt;
884 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
885 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
886 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
888 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
891 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
893 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
894 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
897 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
898 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
900 NMITEMACTIVATE nmia;
901 LVITEMW item;
902 HWND hwnd = infoPtr->hwndSelf;
903 LRESULT ret;
905 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
906 ZeroMemory(&nmia, sizeof(nmia));
907 nmia.iItem = lvht->iItem;
908 nmia.iSubItem = lvht->iSubItem;
909 nmia.ptAction = lvht->pt;
910 item.mask = LVIF_PARAM;
911 item.iItem = lvht->iItem;
912 item.iSubItem = 0;
913 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
914 ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
915 return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
918 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
920 NMLISTVIEW nmlv;
921 LVITEMW item;
922 HWND hwnd = infoPtr->hwndSelf;
924 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
925 nmlv.iItem = nItem;
926 item.mask = LVIF_PARAM;
927 item.iItem = nItem;
928 item.iSubItem = 0;
929 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
930 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
931 return IsWindow(hwnd);
935 Send notification. depends on dispinfoW having same
936 structure as dispinfoA.
937 infoPtr : listview struct
938 code : *Unicode* notification code
939 pdi : dispinfo structure (can be unicode or ansi)
940 isW : TRUE if dispinfo is Unicode
942 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
944 INT length = 0, ret_length;
945 LPWSTR buffer = NULL, ret_text;
946 BOOL return_ansi = FALSE;
947 BOOL return_unicode = FALSE;
948 BOOL ret;
950 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
952 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
953 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
956 ret_length = pdi->item.cchTextMax;
957 ret_text = pdi->item.pszText;
959 if (return_unicode || return_ansi)
961 if (code != LVN_GETDISPINFOW)
963 length = return_ansi ?
964 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
965 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
967 else
969 length = pdi->item.cchTextMax;
970 *pdi->item.pszText = 0; /* make sure we don't process garbage */
973 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
974 if (!buffer) return FALSE;
976 if (return_ansi)
977 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
978 buffer, length);
979 else
980 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
981 length, NULL, NULL);
983 pdi->item.pszText = buffer;
984 pdi->item.cchTextMax = length;
987 if (infoPtr->notifyFormat == NFR_ANSI)
988 code = get_ansi_notification(code);
990 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
991 ret = notify_hdr(infoPtr, code, &pdi->hdr);
992 TRACE(" resulting code=%d\n", pdi->hdr.code);
994 if (return_ansi || return_unicode)
996 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
998 strcpy((char*)ret_text, (char*)pdi->item.pszText);
1000 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
1002 strcpyW(ret_text, pdi->item.pszText);
1004 else if (return_ansi) /* note : pointer can be changed by app ! */
1006 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
1007 ret_length, NULL, NULL);
1009 else
1010 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
1011 ret_text, ret_length);
1013 pdi->item.pszText = ret_text; /* restores our buffer */
1014 pdi->item.cchTextMax = ret_length;
1016 Free(buffer);
1017 return ret;
1020 /* if dipsinfo holder changed notification code then convert */
1021 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1023 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1025 buffer = Alloc(length * sizeof(CHAR));
1026 if (!buffer) return FALSE;
1028 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1029 ret_length, NULL, NULL);
1031 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1032 Free(buffer);
1035 return ret;
1038 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1039 const RECT *rcBounds, const LVITEMW *lplvItem)
1041 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1042 lpnmlvcd->nmcd.hdc = hdc;
1043 lpnmlvcd->nmcd.rc = *rcBounds;
1044 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1045 lpnmlvcd->clrText = infoPtr->clrText;
1046 if (!lplvItem) return;
1047 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1048 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1049 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1050 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1051 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1052 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1055 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1057 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1058 DWORD result;
1060 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1061 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1062 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1063 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1064 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1065 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1066 return result;
1069 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1071 COLORREF backcolor, textcolor;
1073 /* apparently, for selected items, we have to override the returned values */
1074 if (!SubItem)
1076 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1078 if (infoPtr->bFocus)
1080 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1081 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1083 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1085 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1086 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1091 backcolor = lpnmlvcd->clrTextBk;
1092 textcolor = lpnmlvcd->clrText;
1094 if (backcolor == CLR_DEFAULT)
1095 backcolor = comctl32_color.clrWindow;
1096 if (textcolor == CLR_DEFAULT)
1097 textcolor = comctl32_color.clrWindowText;
1099 /* Set the text attributes */
1100 if (backcolor != CLR_NONE)
1102 SetBkMode(hdc, OPAQUE);
1103 SetBkColor(hdc, backcolor);
1105 else
1106 SetBkMode(hdc, TRANSPARENT);
1107 SetTextColor(hdc, textcolor);
1110 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1112 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1115 /* returns TRUE when repaint needed, FALSE otherwise */
1116 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1118 MEASUREITEMSTRUCT mis;
1119 mis.CtlType = ODT_LISTVIEW;
1120 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1121 mis.itemID = -1;
1122 mis.itemWidth = 0;
1123 mis.itemData = 0;
1124 mis.itemHeight= infoPtr->nItemHeight;
1125 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1126 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1128 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1129 return TRUE;
1131 return FALSE;
1134 /******** Item iterator functions **********************************/
1136 static RANGES ranges_create(int count);
1137 static void ranges_destroy(RANGES ranges);
1138 static BOOL ranges_add(RANGES ranges, RANGE range);
1139 static BOOL ranges_del(RANGES ranges, RANGE range);
1140 static void ranges_dump(RANGES ranges);
1142 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1144 RANGE range = { nItem, nItem + 1 };
1146 return ranges_add(ranges, range);
1149 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1151 RANGE range = { nItem, nItem + 1 };
1153 return ranges_del(ranges, range);
1156 /***
1157 * ITERATOR DOCUMENTATION
1159 * The iterator functions allow for easy, and convenient iteration
1160 * over items of interest in the list. Typically, you create an
1161 * iterator, use it, and destroy it, as such:
1162 * ITERATOR i;
1164 * iterator_xxxitems(&i, ...);
1165 * while (iterator_{prev,next}(&i)
1167 * //code which uses i.nItem
1169 * iterator_destroy(&i);
1171 * where xxx is either: framed, or visible.
1172 * Note that it is important that the code destroys the iterator
1173 * after it's done with it, as the creation of the iterator may
1174 * allocate memory, which thus needs to be freed.
1176 * You can iterate both forwards, and backwards through the list,
1177 * by using iterator_next or iterator_prev respectively.
1179 * Lower numbered items are draw on top of higher number items in
1180 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1181 * items may overlap). So, to test items, you should use
1182 * iterator_next
1183 * which lists the items top to bottom (in Z-order).
1184 * For drawing items, you should use
1185 * iterator_prev
1186 * which lists the items bottom to top (in Z-order).
1187 * If you keep iterating over the items after the end-of-items
1188 * marker (-1) is returned, the iterator will start from the
1189 * beginning. Typically, you don't need to test for -1,
1190 * because iterator_{next,prev} will return TRUE if more items
1191 * are to be iterated over, or FALSE otherwise.
1193 * Note: the iterator is defined to be bidirectional. That is,
1194 * any number of prev followed by any number of next, or
1195 * five versa, should leave the iterator at the same item:
1196 * prev * n, next * n = next * n, prev * n
1198 * The iterator has a notion of an out-of-order, special item,
1199 * which sits at the start of the list. This is used in
1200 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1201 * which needs to be first, as it may overlap other items.
1203 * The code is a bit messy because we have:
1204 * - a special item to deal with
1205 * - simple range, or composite range
1206 * - empty range.
1207 * If you find bugs, or want to add features, please make sure you
1208 * always check/modify *both* iterator_prev, and iterator_next.
1211 /****
1212 * This function iterates through the items in increasing order,
1213 * but prefixed by the special item, then -1. That is:
1214 * special, 1, 2, 3, ..., n, -1.
1215 * Each item is listed only once.
1217 static inline BOOL iterator_next(ITERATOR* i)
1219 if (i->nItem == -1)
1221 i->nItem = i->nSpecial;
1222 if (i->nItem != -1) return TRUE;
1224 if (i->nItem == i->nSpecial)
1226 if (i->ranges) i->index = 0;
1227 goto pickarange;
1230 i->nItem++;
1231 testitem:
1232 if (i->nItem == i->nSpecial) i->nItem++;
1233 if (i->nItem < i->range.upper) return TRUE;
1235 pickarange:
1236 if (i->ranges)
1238 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1239 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1240 else goto end;
1242 else if (i->nItem >= i->range.upper) goto end;
1244 i->nItem = i->range.lower;
1245 if (i->nItem >= 0) goto testitem;
1246 end:
1247 i->nItem = -1;
1248 return FALSE;
1251 /****
1252 * This function iterates through the items in decreasing order,
1253 * followed by the special item, then -1. That is:
1254 * n, n-1, ..., 3, 2, 1, special, -1.
1255 * Each item is listed only once.
1257 static inline BOOL iterator_prev(ITERATOR* i)
1259 BOOL start = FALSE;
1261 if (i->nItem == -1)
1263 start = TRUE;
1264 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1265 goto pickarange;
1267 if (i->nItem == i->nSpecial)
1269 i->nItem = -1;
1270 return FALSE;
1273 testitem:
1274 i->nItem--;
1275 if (i->nItem == i->nSpecial) i->nItem--;
1276 if (i->nItem >= i->range.lower) return TRUE;
1278 pickarange:
1279 if (i->ranges)
1281 if (i->index > 0)
1282 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1283 else goto end;
1285 else if (!start && i->nItem < i->range.lower) goto end;
1287 i->nItem = i->range.upper;
1288 if (i->nItem > 0) goto testitem;
1289 end:
1290 return (i->nItem = i->nSpecial) != -1;
1293 static RANGE iterator_range(const ITERATOR *i)
1295 RANGE range;
1297 if (!i->ranges) return i->range;
1299 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1301 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1302 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1304 else range.lower = range.upper = 0;
1306 return range;
1309 /***
1310 * Releases resources associated with this iterator.
1312 static inline void iterator_destroy(const ITERATOR *i)
1314 ranges_destroy(i->ranges);
1317 /***
1318 * Create an empty iterator.
1320 static inline BOOL iterator_empty(ITERATOR* i)
1322 ZeroMemory(i, sizeof(*i));
1323 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1324 return TRUE;
1327 /***
1328 * Create an iterator over a range.
1330 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1332 iterator_empty(i);
1333 i->range = range;
1334 return TRUE;
1337 /***
1338 * Create an iterator over a bunch of ranges.
1339 * Please note that the iterator will take ownership of the ranges,
1340 * and will free them upon destruction.
1342 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1344 iterator_empty(i);
1345 i->ranges = ranges;
1346 return TRUE;
1349 /***
1350 * Creates an iterator over the items which intersect frame.
1351 * Uses absolute coordinates rather than compensating for the current offset.
1353 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1355 RECT rcItem, rcTemp;
1357 /* in case we fail, we want to return an empty iterator */
1358 if (!iterator_empty(i)) return FALSE;
1360 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1362 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1364 INT nItem;
1366 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1368 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1369 if (IntersectRect(&rcTemp, &rcItem, frame))
1370 i->nSpecial = infoPtr->nFocusedItem;
1372 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1373 /* to do better here, we need to have PosX, and PosY sorted */
1374 TRACE("building icon ranges:\n");
1375 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1377 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1378 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1379 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1380 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1381 if (IntersectRect(&rcTemp, &rcItem, frame))
1382 ranges_additem(i->ranges, nItem);
1384 return TRUE;
1386 else if (infoPtr->uView == LV_VIEW_DETAILS)
1388 RANGE range;
1390 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1391 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1393 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1394 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1395 if (range.upper <= range.lower) return TRUE;
1396 if (!iterator_rangeitems(i, range)) return FALSE;
1397 TRACE(" report=%s\n", debugrange(&i->range));
1399 else
1401 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1402 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1403 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1404 INT nFirstCol;
1405 INT nLastCol;
1406 INT lower;
1407 RANGE item_range;
1408 INT nCol;
1410 if (infoPtr->nItemWidth)
1412 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1413 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1415 else
1417 nFirstCol = max(frame->left, 0);
1418 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1421 lower = nFirstCol * nPerCol + nFirstRow;
1423 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1424 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1426 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1428 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1429 TRACE("building list ranges:\n");
1430 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1432 item_range.lower = nCol * nPerCol + nFirstRow;
1433 if(item_range.lower >= infoPtr->nItemCount) break;
1434 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1435 TRACE(" list=%s\n", debugrange(&item_range));
1436 ranges_add(i->ranges, item_range);
1440 return TRUE;
1443 /***
1444 * Creates an iterator over the items which intersect lprc.
1446 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1448 RECT frame = *lprc;
1449 POINT Origin;
1451 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1453 LISTVIEW_GetOrigin(infoPtr, &Origin);
1454 OffsetRect(&frame, -Origin.x, -Origin.y);
1456 return iterator_frameditems_absolute(i, infoPtr, &frame);
1459 /***
1460 * Creates an iterator over the items which intersect the visible region of hdc.
1462 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1464 POINT Origin, Position;
1465 RECT rcItem, rcClip;
1466 INT rgntype;
1468 rgntype = GetClipBox(hdc, &rcClip);
1469 if (rgntype == NULLREGION) return iterator_empty(i);
1470 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1471 if (rgntype == SIMPLEREGION) return TRUE;
1473 /* first deal with the special item */
1474 if (i->nSpecial != -1)
1476 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1477 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1480 /* if we can't deal with the region, we'll just go with the simple range */
1481 LISTVIEW_GetOrigin(infoPtr, &Origin);
1482 TRACE("building visible range:\n");
1483 if (!i->ranges && i->range.lower < i->range.upper)
1485 if (!(i->ranges = ranges_create(50))) return TRUE;
1486 if (!ranges_add(i->ranges, i->range))
1488 ranges_destroy(i->ranges);
1489 i->ranges = 0;
1490 return TRUE;
1494 /* now delete the invisible items from the list */
1495 while(iterator_next(i))
1497 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1498 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1499 rcItem.top = Position.y + Origin.y;
1500 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1501 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1502 if (!RectVisible(hdc, &rcItem))
1503 ranges_delitem(i->ranges, i->nItem);
1505 /* the iterator should restart on the next iterator_next */
1506 TRACE("done\n");
1508 return TRUE;
1511 /* Remove common elements from two iterators */
1512 /* Passed iterators have to point on the first elements */
1513 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1515 if(!iter1->ranges || !iter2->ranges) {
1516 int lower, upper;
1518 if(iter1->ranges || iter2->ranges ||
1519 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1520 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1521 ERR("result is not a one range iterator\n");
1522 return FALSE;
1525 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1526 return TRUE;
1528 lower = iter1->range.lower;
1529 upper = iter1->range.upper;
1531 if(lower < iter2->range.lower)
1532 iter1->range.upper = iter2->range.lower;
1533 else if(upper > iter2->range.upper)
1534 iter1->range.lower = iter2->range.upper;
1535 else
1536 iter1->range.lower = iter1->range.upper = -1;
1538 if(iter2->range.lower < lower)
1539 iter2->range.upper = lower;
1540 else if(iter2->range.upper > upper)
1541 iter2->range.lower = upper;
1542 else
1543 iter2->range.lower = iter2->range.upper = -1;
1545 return TRUE;
1548 iterator_next(iter1);
1549 iterator_next(iter2);
1551 while(1) {
1552 if(iter1->nItem==-1 || iter2->nItem==-1)
1553 break;
1555 if(iter1->nItem == iter2->nItem) {
1556 int delete = iter1->nItem;
1558 iterator_prev(iter1);
1559 iterator_prev(iter2);
1560 ranges_delitem(iter1->ranges, delete);
1561 ranges_delitem(iter2->ranges, delete);
1562 iterator_next(iter1);
1563 iterator_next(iter2);
1564 } else if(iter1->nItem > iter2->nItem)
1565 iterator_next(iter2);
1566 else
1567 iterator_next(iter1);
1570 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1571 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1572 return TRUE;
1575 /******** Misc helper functions ************************************/
1577 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1578 WPARAM wParam, LPARAM lParam, BOOL isW)
1580 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1581 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1584 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1586 return (infoPtr->dwStyle & LVS_AUTOARRANGE) &&
1587 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1590 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1592 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1593 if(state == 1 || state == 2)
1595 LVITEMW lvitem;
1596 state ^= 3;
1597 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1598 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1599 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1603 /* this should be called after window style got updated,
1604 it used to reset view state to match current window style */
1605 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1607 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1609 case LVS_ICON:
1610 infoPtr->uView = LV_VIEW_ICON;
1611 break;
1612 case LVS_REPORT:
1613 infoPtr->uView = LV_VIEW_DETAILS;
1614 break;
1615 case LVS_SMALLICON:
1616 infoPtr->uView = LV_VIEW_SMALLICON;
1617 break;
1618 case LVS_LIST:
1619 infoPtr->uView = LV_VIEW_LIST;
1623 /* computes next item id value */
1624 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1626 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1628 if (count > 0)
1630 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1631 return lpID->id + 1;
1633 return 0;
1636 /******** Internal API functions ************************************/
1638 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1640 static COLUMN_INFO mainItem;
1642 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1643 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1645 /* update cached column rectangles */
1646 if (infoPtr->colRectsDirty)
1648 COLUMN_INFO *info;
1649 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1650 INT i;
1652 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1653 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1654 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1656 Ptr->colRectsDirty = FALSE;
1659 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1662 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1664 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1665 HINSTANCE hInst;
1667 if (infoPtr->hwndHeader) return 0;
1669 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1671 /* setup creation flags */
1672 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1673 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1675 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1677 /* create header */
1678 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1679 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1680 if (!infoPtr->hwndHeader) return -1;
1682 /* set header unicode format */
1683 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1685 /* set header font */
1686 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1688 /* set header image list */
1689 if (infoPtr->himlSmall)
1690 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1692 LISTVIEW_UpdateSize(infoPtr);
1694 return 0;
1697 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1699 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1702 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1704 return (infoPtr->uView == LV_VIEW_DETAILS ||
1705 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1706 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1709 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1711 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1714 /* used to handle collapse main item column case */
1715 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1717 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1718 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1721 /* Listview invalidation functions: use _only_ these functions to invalidate */
1723 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1725 return infoPtr->redraw;
1728 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1730 if(!is_redrawing(infoPtr)) return;
1731 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1732 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1735 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1737 RECT rcBox;
1739 if(!is_redrawing(infoPtr)) return;
1740 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1741 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1744 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1746 POINT Origin, Position;
1747 RECT rcBox;
1749 if(!is_redrawing(infoPtr)) return;
1750 assert (infoPtr->uView == LV_VIEW_DETAILS);
1751 LISTVIEW_GetOrigin(infoPtr, &Origin);
1752 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1753 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1754 rcBox.top = 0;
1755 rcBox.bottom = infoPtr->nItemHeight;
1756 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1757 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1760 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1762 LISTVIEW_InvalidateRect(infoPtr, NULL);
1765 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1767 RECT rcCol;
1769 if(!is_redrawing(infoPtr)) return;
1770 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1771 rcCol.top = infoPtr->rcList.top;
1772 rcCol.bottom = infoPtr->rcList.bottom;
1773 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1776 /***
1777 * DESCRIPTION:
1778 * Retrieves the number of items that can fit vertically in the client area.
1780 * PARAMETER(S):
1781 * [I] infoPtr : valid pointer to the listview structure
1783 * RETURN:
1784 * Number of items per row.
1786 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1788 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1790 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1793 /***
1794 * DESCRIPTION:
1795 * Retrieves the number of items that can fit horizontally in the client
1796 * area.
1798 * PARAMETER(S):
1799 * [I] infoPtr : valid pointer to the listview structure
1801 * RETURN:
1802 * Number of items per column.
1804 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1806 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1808 return max(nListHeight / infoPtr->nItemHeight, 1);
1812 /*************************************************************************
1813 * LISTVIEW_ProcessLetterKeys
1815 * Processes keyboard messages generated by pressing the letter keys
1816 * on the keyboard.
1817 * What this does is perform a case insensitive search from the
1818 * current position with the following quirks:
1819 * - If two chars or more are pressed in quick succession we search
1820 * for the corresponding string (e.g. 'abc').
1821 * - If there is a delay we wipe away the current search string and
1822 * restart with just that char.
1823 * - If the user keeps pressing the same character, whether slowly or
1824 * fast, so that the search string is entirely composed of this
1825 * character ('aaaaa' for instance), then we search for first item
1826 * that starting with that character.
1827 * - If the user types the above character in quick succession, then
1828 * we must also search for the corresponding string ('aaaaa'), and
1829 * go to that string if there is a match.
1831 * PARAMETERS
1832 * [I] hwnd : handle to the window
1833 * [I] charCode : the character code, the actual character
1834 * [I] keyData : key data
1836 * RETURNS
1838 * Zero.
1840 * BUGS
1842 * - The current implementation has a list of characters it will
1843 * accept and it ignores everything else. In particular it will
1844 * ignore accentuated characters which seems to match what
1845 * Windows does. But I'm not sure it makes sense to follow
1846 * Windows there.
1847 * - We don't sound a beep when the search fails.
1849 * SEE ALSO
1851 * TREEVIEW_ProcessLetterKeys
1853 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1855 WCHAR buffer[MAX_PATH];
1856 DWORD prevTime;
1857 LVITEMW item;
1858 int startidx;
1859 INT nItem;
1860 INT diff;
1862 /* simple parameter checking */
1863 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1865 /* only allow the valid WM_CHARs through */
1866 if (!isalnumW(charCode) &&
1867 charCode != '.' && charCode != '`' && charCode != '!' &&
1868 charCode != '@' && charCode != '#' && charCode != '$' &&
1869 charCode != '%' && charCode != '^' && charCode != '&' &&
1870 charCode != '*' && charCode != '(' && charCode != ')' &&
1871 charCode != '-' && charCode != '_' && charCode != '+' &&
1872 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1873 charCode != '}' && charCode != '[' && charCode != '{' &&
1874 charCode != '/' && charCode != '?' && charCode != '>' &&
1875 charCode != '<' && charCode != ',' && charCode != '~')
1876 return 0;
1878 /* update the search parameters */
1879 prevTime = infoPtr->lastKeyPressTimestamp;
1880 infoPtr->lastKeyPressTimestamp = GetTickCount();
1881 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1883 if (diff >= 0 && diff < KEY_DELAY)
1885 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1886 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1888 if (infoPtr->charCode != charCode)
1889 infoPtr->charCode = charCode = 0;
1891 else
1893 infoPtr->charCode = charCode;
1894 infoPtr->szSearchParam[0] = charCode;
1895 infoPtr->nSearchParamLength = 1;
1898 /* should start from next after focused item, so next item that matches
1899 will be selected, if there isn't any and focused matches it will be selected
1900 on second search stage from beginning of the list */
1901 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1903 /* with some accumulated search data available start with current focus, otherwise
1904 it's excluded from search */
1905 startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
1906 if (startidx == infoPtr->nItemCount) startidx = 0;
1908 else
1909 startidx = 0;
1911 /* let application handle this for virtual listview */
1912 if (infoPtr->dwStyle & LVS_OWNERDATA)
1914 NMLVFINDITEMW nmlv;
1916 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1917 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1918 nmlv.lvfi.psz = infoPtr->szSearchParam;
1919 nmlv.iStart = startidx;
1921 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1923 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1925 else
1927 int i = startidx, endidx;
1929 /* and search from the current position */
1930 nItem = -1;
1931 endidx = infoPtr->nItemCount;
1933 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1934 while (1)
1936 /* start from first item if not found with >= startidx */
1937 if (i == infoPtr->nItemCount && startidx > 0)
1939 endidx = startidx;
1940 startidx = 0;
1943 for (i = startidx; i < endidx; i++)
1945 /* retrieve text */
1946 item.mask = LVIF_TEXT;
1947 item.iItem = i;
1948 item.iSubItem = 0;
1949 item.pszText = buffer;
1950 item.cchTextMax = MAX_PATH;
1951 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1953 if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
1955 nItem = i;
1956 break;
1958 /* this is used to find first char match when search string is not available yet,
1959 otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1960 already waiting for user to complete a string */
1961 else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
1963 /* this would work but we must keep looking for a longer match */
1964 nItem = i;
1968 if ( nItem != -1 || /* found something */
1969 endidx != infoPtr->nItemCount || /* second search done */
1970 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1971 break;
1975 if (nItem != -1)
1976 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1978 return 0;
1981 /*************************************************************************
1982 * LISTVIEW_UpdateHeaderSize [Internal]
1984 * Function to resize the header control
1986 * PARAMS
1987 * [I] hwnd : handle to a window
1988 * [I] nNewScrollPos : scroll pos to set
1990 * RETURNS
1991 * None.
1993 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1995 RECT winRect;
1996 POINT point[2];
1998 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
2000 if (!infoPtr->hwndHeader) return;
2002 GetWindowRect(infoPtr->hwndHeader, &winRect);
2003 point[0].x = winRect.left;
2004 point[0].y = winRect.top;
2005 point[1].x = winRect.right;
2006 point[1].y = winRect.bottom;
2008 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
2009 point[0].x = -nNewScrollPos;
2010 point[1].x += nNewScrollPos;
2012 SetWindowPos(infoPtr->hwndHeader,0,
2013 point[0].x,point[0].y,point[1].x,point[1].y,
2014 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
2015 SWP_NOZORDER | SWP_NOACTIVATE);
2018 static INT LISTVIEW_UpdateHScroll(LISTVIEW_INFO *infoPtr)
2020 SCROLLINFO horzInfo;
2021 INT dx;
2023 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2024 horzInfo.cbSize = sizeof(SCROLLINFO);
2025 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2027 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2028 if (infoPtr->uView == LV_VIEW_LIST)
2030 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2031 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2033 /* scroll by at least one column per page */
2034 if(horzInfo.nPage < infoPtr->nItemWidth)
2035 horzInfo.nPage = infoPtr->nItemWidth;
2037 if (infoPtr->nItemWidth)
2038 horzInfo.nPage /= infoPtr->nItemWidth;
2040 else if (infoPtr->uView == LV_VIEW_DETAILS)
2042 horzInfo.nMax = infoPtr->nItemWidth;
2044 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2046 RECT rcView;
2048 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2051 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2053 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2055 RECT rcHeader;
2056 INT index;
2058 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2059 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2061 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2062 horzInfo.nMax = rcHeader.right;
2063 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2067 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2068 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2069 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2070 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2071 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2073 /* Update the Header Control */
2074 if (infoPtr->hwndHeader)
2076 horzInfo.fMask = SIF_POS;
2077 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2078 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2081 LISTVIEW_UpdateSize(infoPtr);
2082 return dx;
2085 static INT LISTVIEW_UpdateVScroll(LISTVIEW_INFO *infoPtr)
2087 SCROLLINFO vertInfo;
2088 INT dy;
2090 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2091 vertInfo.cbSize = sizeof(SCROLLINFO);
2092 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2094 if (infoPtr->uView == LV_VIEW_DETAILS)
2096 vertInfo.nMax = infoPtr->nItemCount;
2098 /* scroll by at least one page */
2099 if(vertInfo.nPage < infoPtr->nItemHeight)
2100 vertInfo.nPage = infoPtr->nItemHeight;
2102 if (infoPtr->nItemHeight > 0)
2103 vertInfo.nPage /= infoPtr->nItemHeight;
2105 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2107 RECT rcView;
2109 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2112 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2113 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2114 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2115 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2116 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2118 LISTVIEW_UpdateSize(infoPtr);
2119 return dy;
2122 /***
2123 * DESCRIPTION:
2124 * Update the scrollbars. This function should be called whenever
2125 * the content, size or view changes.
2127 * PARAMETER(S):
2128 * [I] infoPtr : valid pointer to the listview structure
2130 * RETURN:
2131 * None
2133 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
2135 INT dx, dy, pass;
2137 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2139 /* Setting the horizontal scroll can change the listview size
2140 * (and potentially everything else) so we need to recompute
2141 * everything again for the vertical scroll and vice-versa
2143 for (dx = 0, dy = 0, pass = 0; pass <= 1; pass++)
2145 dx += LISTVIEW_UpdateHScroll(infoPtr);
2146 dy += LISTVIEW_UpdateVScroll(infoPtr);
2149 /* Change of the range may have changed the scroll pos. If so move the content */
2150 if (dx != 0 || dy != 0)
2152 RECT listRect;
2153 listRect = infoPtr->rcList;
2154 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2155 SW_ERASE | SW_INVALIDATE);
2160 /***
2161 * DESCRIPTION:
2162 * Shows/hides the focus rectangle.
2164 * PARAMETER(S):
2165 * [I] infoPtr : valid pointer to the listview structure
2166 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2168 * RETURN:
2169 * None
2171 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2173 HDC hdc;
2175 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2177 if (infoPtr->nFocusedItem < 0) return;
2179 /* we need some gymnastics in ICON mode to handle large items */
2180 if (infoPtr->uView == LV_VIEW_ICON)
2182 RECT rcBox;
2184 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2185 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2187 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2188 return;
2192 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2194 /* for some reason, owner draw should work only in report mode */
2195 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2197 DRAWITEMSTRUCT dis;
2198 LVITEMW item;
2200 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2201 HFONT hOldFont = SelectObject(hdc, hFont);
2203 item.iItem = infoPtr->nFocusedItem;
2204 item.iSubItem = 0;
2205 item.mask = LVIF_PARAM;
2206 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2208 ZeroMemory(&dis, sizeof(dis));
2209 dis.CtlType = ODT_LISTVIEW;
2210 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2211 dis.itemID = item.iItem;
2212 dis.itemAction = ODA_FOCUS;
2213 if (fShow) dis.itemState |= ODS_FOCUS;
2214 dis.hwndItem = infoPtr->hwndSelf;
2215 dis.hDC = hdc;
2216 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2217 dis.itemData = item.lParam;
2219 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2221 SelectObject(hdc, hOldFont);
2223 else
2224 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nFocusedItem);
2226 done:
2227 ReleaseDC(infoPtr->hwndSelf, hdc);
2230 /***
2231 * Invalidates all visible selected items.
2233 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2235 ITERATOR i;
2237 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2238 while(iterator_next(&i))
2240 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2241 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2243 iterator_destroy(&i);
2247 /***
2248 * DESCRIPTION: [INTERNAL]
2249 * Computes an item's (left,top) corner, relative to rcView.
2250 * That is, the position has NOT been made relative to the Origin.
2251 * This is deliberate, to avoid computing the Origin over, and
2252 * over again, when this function is called in a loop. Instead,
2253 * one can factor the computation of the Origin before the loop,
2254 * and offset the value returned by this function, on every iteration.
2256 * PARAMETER(S):
2257 * [I] infoPtr : valid pointer to the listview structure
2258 * [I] nItem : item number
2259 * [O] lpptOrig : item top, left corner
2261 * RETURN:
2262 * None.
2264 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2266 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2268 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2270 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2271 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2273 else if (infoPtr->uView == LV_VIEW_LIST)
2275 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2276 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2277 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2279 else /* LV_VIEW_DETAILS */
2281 lpptPosition->x = REPORT_MARGINX;
2282 /* item is always at zero indexed column */
2283 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2284 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2285 lpptPosition->y = nItem * infoPtr->nItemHeight;
2289 /***
2290 * DESCRIPTION: [INTERNAL]
2291 * Compute the rectangles of an item. This is to localize all
2292 * the computations in one place. If you are not interested in some
2293 * of these values, simply pass in a NULL -- the function is smart
2294 * enough to compute only what's necessary. The function computes
2295 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2296 * one, the BOX rectangle. This rectangle is very cheap to compute,
2297 * and is guaranteed to contain all the other rectangles. Computing
2298 * the ICON rect is also cheap, but all the others are potentially
2299 * expensive. This gives an easy and effective optimization when
2300 * searching (like point inclusion, or rectangle intersection):
2301 * first test against the BOX, and if TRUE, test against the desired
2302 * rectangle.
2303 * If the function does not have all the necessary information
2304 * to computed the requested rectangles, will crash with a
2305 * failed assertion. This is done so we catch all programming
2306 * errors, given that the function is called only from our code.
2308 * We have the following 'special' meanings for a few fields:
2309 * * If LVIS_FOCUSED is set, we assume the item has the focus
2310 * This is important in ICON mode, where it might get a larger
2311 * then usual rectangle
2313 * Please note that subitem support works only in REPORT mode.
2315 * PARAMETER(S):
2316 * [I] infoPtr : valid pointer to the listview structure
2317 * [I] lpLVItem : item to compute the measures for
2318 * [O] lprcBox : ptr to Box rectangle
2319 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2320 * [0] lprcSelectBox : ptr to select box rectangle
2321 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2322 * [O] lprcIcon : ptr to Icon rectangle
2323 * Same as LVM_GETITEMRECT with LVIR_ICON
2324 * [O] lprcStateIcon: ptr to State Icon rectangle
2325 * [O] lprcLabel : ptr to Label rectangle
2326 * Same as LVM_GETITEMRECT with LVIR_LABEL
2328 * RETURN:
2329 * None.
2331 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2332 LPRECT lprcBox, LPRECT lprcSelectBox,
2333 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2335 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2336 RECT Box, SelectBox, Icon, Label;
2337 COLUMN_INFO *lpColumnInfo = NULL;
2338 SIZE labelSize = { 0, 0 };
2340 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2342 /* Be smart and try to figure out the minimum we have to do */
2343 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2344 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2346 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2347 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2349 if (lprcSelectBox) doSelectBox = TRUE;
2350 if (lprcLabel) doLabel = TRUE;
2351 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2352 if (doSelectBox)
2354 doIcon = TRUE;
2355 doLabel = TRUE;
2358 /************************************************************/
2359 /* compute the box rectangle (it should be cheap to do) */
2360 /************************************************************/
2361 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2362 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2364 if (lpLVItem->iSubItem)
2366 Box = lpColumnInfo->rcHeader;
2368 else
2370 Box.left = 0;
2371 Box.right = infoPtr->nItemWidth;
2373 Box.top = 0;
2374 Box.bottom = infoPtr->nItemHeight;
2376 /******************************************************************/
2377 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2378 /******************************************************************/
2379 if (doIcon)
2381 LONG state_width = 0;
2383 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2384 state_width = infoPtr->iconStateSize.cx;
2386 if (infoPtr->uView == LV_VIEW_ICON)
2388 Icon.left = Box.left + state_width;
2389 if (infoPtr->himlNormal)
2390 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2391 Icon.top = Box.top + ICON_TOP_PADDING;
2392 Icon.right = Icon.left;
2393 Icon.bottom = Icon.top;
2394 if (infoPtr->himlNormal)
2396 Icon.right += infoPtr->iconSize.cx;
2397 Icon.bottom += infoPtr->iconSize.cy;
2400 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2402 Icon.left = Box.left + state_width;
2404 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2406 /* we need the indent in report mode */
2407 assert(lpLVItem->mask & LVIF_INDENT);
2408 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2411 Icon.top = Box.top;
2412 Icon.right = Icon.left;
2413 if (infoPtr->himlSmall &&
2414 (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2415 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2416 Icon.right += infoPtr->iconSize.cx;
2417 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2419 if(lprcIcon) *lprcIcon = Icon;
2420 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2422 /* TODO: is this correct? */
2423 if (lprcStateIcon)
2425 lprcStateIcon->left = Icon.left - state_width;
2426 lprcStateIcon->right = Icon.left;
2427 lprcStateIcon->top = Icon.top;
2428 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2429 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2432 else Icon.right = 0;
2434 /************************************************************/
2435 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2436 /************************************************************/
2437 if (doLabel)
2439 /* calculate how far to the right can the label stretch */
2440 Label.right = Box.right;
2441 if (infoPtr->uView == LV_VIEW_DETAILS)
2443 if (lpLVItem->iSubItem == 0)
2445 /* we need a zero based rect here */
2446 Label = lpColumnInfo->rcHeader;
2447 OffsetRect(&Label, -Label.left, 0);
2451 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2453 labelSize.cx = infoPtr->nItemWidth;
2454 labelSize.cy = infoPtr->nItemHeight;
2455 goto calc_label;
2458 /* we need the text in non owner draw mode */
2459 assert(lpLVItem->mask & LVIF_TEXT);
2460 if (is_text(lpLVItem->pszText))
2462 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2463 HDC hdc = GetDC(infoPtr->hwndSelf);
2464 HFONT hOldFont = SelectObject(hdc, hFont);
2465 UINT uFormat;
2466 RECT rcText;
2468 /* compute rough rectangle where the label will go */
2469 SetRectEmpty(&rcText);
2470 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2471 rcText.bottom = infoPtr->nItemHeight;
2472 if (infoPtr->uView == LV_VIEW_ICON)
2473 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2475 /* now figure out the flags */
2476 if (infoPtr->uView == LV_VIEW_ICON)
2477 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2478 else
2479 uFormat = LV_SL_DT_FLAGS;
2481 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2483 if (rcText.right != rcText.left)
2484 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2486 labelSize.cy = rcText.bottom - rcText.top;
2488 SelectObject(hdc, hOldFont);
2489 ReleaseDC(infoPtr->hwndSelf, hdc);
2492 calc_label:
2493 if (infoPtr->uView == LV_VIEW_ICON)
2495 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2496 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2497 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2498 Label.right = Label.left + labelSize.cx;
2499 Label.bottom = Label.top + infoPtr->nItemHeight;
2500 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2502 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2503 labelSize.cy /= infoPtr->ntmHeight;
2504 labelSize.cy = max(labelSize.cy, 1);
2505 labelSize.cy *= infoPtr->ntmHeight;
2507 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2509 else if (infoPtr->uView == LV_VIEW_DETAILS)
2511 Label.left = Icon.right;
2512 Label.top = Box.top;
2513 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2514 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2515 Label.bottom = Label.top + infoPtr->nItemHeight;
2517 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2519 Label.left = Icon.right;
2520 Label.top = Box.top;
2521 Label.right = min(Label.left + labelSize.cx, Label.right);
2522 Label.bottom = Label.top + infoPtr->nItemHeight;
2525 if (lprcLabel) *lprcLabel = Label;
2526 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2529 /************************************************************/
2530 /* compute SELECT bounding box */
2531 /************************************************************/
2532 if (doSelectBox)
2534 if (infoPtr->uView == LV_VIEW_DETAILS)
2536 SelectBox.left = Icon.left;
2537 SelectBox.top = Box.top;
2538 SelectBox.bottom = Box.bottom;
2540 if (labelSize.cx)
2541 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2542 else
2543 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2545 else
2547 UnionRect(&SelectBox, &Icon, &Label);
2549 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2550 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2553 /* Fix the Box if necessary */
2554 if (lprcBox)
2556 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2557 else *lprcBox = Box;
2559 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2562 /***
2563 * DESCRIPTION: [INTERNAL]
2565 * PARAMETER(S):
2566 * [I] infoPtr : valid pointer to the listview structure
2567 * [I] nItem : item number
2568 * [O] lprcBox : ptr to Box rectangle
2570 * RETURN:
2571 * None.
2573 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2575 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2576 POINT Position, Origin;
2577 LVITEMW lvItem;
2579 LISTVIEW_GetOrigin(infoPtr, &Origin);
2580 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2582 /* Be smart and try to figure out the minimum we have to do */
2583 lvItem.mask = 0;
2584 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2585 lvItem.mask |= LVIF_TEXT;
2586 lvItem.iItem = nItem;
2587 lvItem.iSubItem = 0;
2588 lvItem.pszText = szDispText;
2589 lvItem.cchTextMax = DISP_TEXT_SIZE;
2590 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2591 if (infoPtr->uView == LV_VIEW_ICON)
2593 lvItem.mask |= LVIF_STATE;
2594 lvItem.stateMask = LVIS_FOCUSED;
2595 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2597 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2599 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2600 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2602 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2604 else
2605 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2608 /* LISTVIEW_MapIdToIndex helper */
2609 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2611 ITEM_ID *id1 = (ITEM_ID*)p1;
2612 ITEM_ID *id2 = (ITEM_ID*)p2;
2614 if (id1->id == id2->id) return 0;
2616 return (id1->id < id2->id) ? -1 : 1;
2619 /***
2620 * DESCRIPTION:
2621 * Returns the item index for id specified.
2623 * PARAMETER(S):
2624 * [I] infoPtr : valid pointer to the listview structure
2625 * [I] iID : item id to get index for
2627 * RETURN:
2628 * Item index, or -1 on failure.
2630 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2632 ITEM_ID ID;
2633 INT index;
2635 TRACE("iID=%d\n", iID);
2637 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2638 if (infoPtr->nItemCount == 0) return -1;
2640 ID.id = iID;
2641 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2643 if (index != -1)
2645 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2646 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2649 return -1;
2652 /***
2653 * DESCRIPTION:
2654 * Returns the item id for index given.
2656 * PARAMETER(S):
2657 * [I] infoPtr : valid pointer to the listview structure
2658 * [I] iItem : item index to get id for
2660 * RETURN:
2661 * Item id.
2663 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2665 ITEM_INFO *lpItem;
2666 HDPA hdpaSubItems;
2668 TRACE("iItem=%d\n", iItem);
2670 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2671 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2673 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2674 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2676 return lpItem->id->id;
2679 /***
2680 * DESCRIPTION:
2681 * Returns the current icon position, and advances it along the top.
2682 * The returned position is not offset by Origin.
2684 * PARAMETER(S):
2685 * [I] infoPtr : valid pointer to the listview structure
2686 * [O] lpPos : will get the current icon position
2688 * RETURN:
2689 * None
2691 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2693 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2695 *lpPos = infoPtr->currIconPos;
2697 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2698 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2700 infoPtr->currIconPos.x = 0;
2701 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2705 /***
2706 * DESCRIPTION:
2707 * Returns the current icon position, and advances it down the left edge.
2708 * The returned position is not offset by Origin.
2710 * PARAMETER(S):
2711 * [I] infoPtr : valid pointer to the listview structure
2712 * [O] lpPos : will get the current icon position
2714 * RETURN:
2715 * None
2717 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2719 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2721 *lpPos = infoPtr->currIconPos;
2723 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2724 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2726 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2727 infoPtr->currIconPos.y = 0;
2731 /***
2732 * DESCRIPTION:
2733 * Moves an icon to the specified position.
2734 * It takes care of invalidating the item, etc.
2736 * PARAMETER(S):
2737 * [I] infoPtr : valid pointer to the listview structure
2738 * [I] nItem : the item to move
2739 * [I] lpPos : the new icon position
2740 * [I] isNew : flags the item as being new
2742 * RETURN:
2743 * Success: TRUE
2744 * Failure: FALSE
2746 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2748 POINT old;
2750 if (!isNew)
2752 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2753 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2755 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2756 LISTVIEW_InvalidateItem(infoPtr, nItem);
2759 /* Allocating a POINTER for every item is too resource intensive,
2760 * so we'll keep the (x,y) in different arrays */
2761 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2762 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2764 LISTVIEW_InvalidateItem(infoPtr, nItem);
2766 return TRUE;
2769 /***
2770 * DESCRIPTION:
2771 * Arranges listview items in icon display mode.
2773 * PARAMETER(S):
2774 * [I] infoPtr : valid pointer to the listview structure
2775 * [I] nAlignCode : alignment code
2777 * RETURN:
2778 * SUCCESS : TRUE
2779 * FAILURE : FALSE
2781 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2783 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2784 POINT pos;
2785 INT i;
2787 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2789 TRACE("nAlignCode=%d\n", nAlignCode);
2791 if (nAlignCode == LVA_DEFAULT)
2793 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2794 else nAlignCode = LVA_ALIGNTOP;
2797 switch (nAlignCode)
2799 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2800 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2801 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2802 default: return FALSE;
2805 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2806 for (i = 0; i < infoPtr->nItemCount; i++)
2808 next_pos(infoPtr, &pos);
2809 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2812 return TRUE;
2815 /***
2816 * DESCRIPTION:
2817 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2818 * For LVS_REPORT always returns empty rectangle.
2820 * PARAMETER(S):
2821 * [I] infoPtr : valid pointer to the listview structure
2822 * [O] lprcView : bounding rectangle
2824 * RETURN:
2825 * SUCCESS : TRUE
2826 * FAILURE : FALSE
2828 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2830 INT i, x, y;
2832 SetRectEmpty(lprcView);
2834 switch (infoPtr->uView)
2836 case LV_VIEW_ICON:
2837 case LV_VIEW_SMALLICON:
2838 for (i = 0; i < infoPtr->nItemCount; i++)
2840 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2841 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2842 lprcView->right = max(lprcView->right, x);
2843 lprcView->bottom = max(lprcView->bottom, y);
2845 if (infoPtr->nItemCount > 0)
2847 lprcView->right += infoPtr->nItemWidth;
2848 lprcView->bottom += infoPtr->nItemHeight;
2850 break;
2852 case LV_VIEW_LIST:
2853 y = LISTVIEW_GetCountPerColumn(infoPtr);
2854 x = infoPtr->nItemCount / y;
2855 if (infoPtr->nItemCount % y) x++;
2856 lprcView->right = x * infoPtr->nItemWidth;
2857 lprcView->bottom = y * infoPtr->nItemHeight;
2858 break;
2862 /***
2863 * DESCRIPTION:
2864 * Retrieves the bounding rectangle of all the items.
2866 * PARAMETER(S):
2867 * [I] infoPtr : valid pointer to the listview structure
2868 * [O] lprcView : bounding rectangle
2870 * RETURN:
2871 * SUCCESS : TRUE
2872 * FAILURE : FALSE
2874 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2876 POINT ptOrigin;
2878 TRACE("(lprcView=%p)\n", lprcView);
2880 if (!lprcView) return FALSE;
2882 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2884 if (infoPtr->uView != LV_VIEW_DETAILS)
2886 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2887 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2890 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2892 return TRUE;
2895 /***
2896 * DESCRIPTION:
2897 * Retrieves the subitem pointer associated with the subitem index.
2899 * PARAMETER(S):
2900 * [I] hdpaSubItems : DPA handle for a specific item
2901 * [I] nSubItem : index of subitem
2903 * RETURN:
2904 * SUCCESS : subitem pointer
2905 * FAILURE : NULL
2907 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2909 SUBITEM_INFO *lpSubItem;
2910 INT i;
2912 /* we should binary search here if need be */
2913 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2915 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2916 if (lpSubItem->iSubItem == nSubItem)
2917 return lpSubItem;
2920 return NULL;
2924 /***
2925 * DESCRIPTION:
2926 * Calculates the desired item width.
2928 * PARAMETER(S):
2929 * [I] infoPtr : valid pointer to the listview structure
2931 * RETURN:
2932 * The desired item width.
2934 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2936 INT nItemWidth = 0;
2938 TRACE("uView=%d\n", infoPtr->uView);
2940 if (infoPtr->uView == LV_VIEW_ICON)
2941 nItemWidth = infoPtr->iconSpacing.cx;
2942 else if (infoPtr->uView == LV_VIEW_DETAILS)
2944 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2946 RECT rcHeader;
2947 INT index;
2949 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2950 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2952 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2953 nItemWidth = rcHeader.right;
2956 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2958 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2959 LVITEMW lvItem;
2960 INT i;
2962 lvItem.mask = LVIF_TEXT;
2963 lvItem.iSubItem = 0;
2965 for (i = 0; i < infoPtr->nItemCount; i++)
2967 lvItem.iItem = i;
2968 lvItem.pszText = szDispText;
2969 lvItem.cchTextMax = DISP_TEXT_SIZE;
2970 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2971 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2972 nItemWidth);
2975 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2976 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2978 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2981 return nItemWidth;
2984 /***
2985 * DESCRIPTION:
2986 * Calculates the desired item height.
2988 * PARAMETER(S):
2989 * [I] infoPtr : valid pointer to the listview structure
2991 * RETURN:
2992 * The desired item height.
2994 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2996 INT nItemHeight;
2998 TRACE("uView=%d\n", infoPtr->uView);
3000 if (infoPtr->uView == LV_VIEW_ICON)
3001 nItemHeight = infoPtr->iconSpacing.cy;
3002 else
3004 nItemHeight = infoPtr->ntmHeight;
3005 if (infoPtr->himlState)
3006 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
3007 if (infoPtr->himlSmall)
3008 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
3009 nItemHeight += HEIGHT_PADDING;
3010 if (infoPtr->nMeasureItemHeight > 0)
3011 nItemHeight = infoPtr->nMeasureItemHeight;
3014 return max(nItemHeight, 1);
3017 /***
3018 * DESCRIPTION:
3019 * Updates the width, and height of an item.
3021 * PARAMETER(S):
3022 * [I] infoPtr : valid pointer to the listview structure
3024 * RETURN:
3025 * None.
3027 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
3029 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
3030 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
3034 /***
3035 * DESCRIPTION:
3036 * Retrieves and saves important text metrics info for the current
3037 * Listview font.
3039 * PARAMETER(S):
3040 * [I] infoPtr : valid pointer to the listview structure
3043 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3045 HDC hdc = GetDC(infoPtr->hwndSelf);
3046 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3047 HFONT hOldFont = SelectObject(hdc, hFont);
3048 TEXTMETRICW tm;
3049 SIZE sz;
3051 if (GetTextMetricsW(hdc, &tm))
3053 infoPtr->ntmHeight = tm.tmHeight;
3054 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3057 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3058 infoPtr->nEllipsisWidth = sz.cx;
3060 SelectObject(hdc, hOldFont);
3061 ReleaseDC(infoPtr->hwndSelf, hdc);
3063 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3066 /***
3067 * DESCRIPTION:
3068 * A compare function for ranges
3070 * PARAMETER(S)
3071 * [I] range1 : pointer to range 1;
3072 * [I] range2 : pointer to range 2;
3073 * [I] flags : flags
3075 * RETURNS:
3076 * > 0 : if range 1 > range 2
3077 * < 0 : if range 2 > range 1
3078 * = 0 : if range intersects range 2
3080 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3082 INT cmp;
3084 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3085 cmp = -1;
3086 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3087 cmp = 1;
3088 else
3089 cmp = 0;
3091 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3093 return cmp;
3096 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3098 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3100 INT i;
3101 RANGE *prev, *curr;
3103 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3104 assert (ranges);
3105 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3106 ranges_dump(ranges);
3107 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3109 prev = DPA_GetPtr(ranges->hdpa, 0);
3110 assert (prev->lower >= 0 && prev->lower < prev->upper);
3111 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3113 curr = DPA_GetPtr(ranges->hdpa, i);
3114 assert (prev->upper <= curr->lower);
3115 assert (curr->lower < curr->upper);
3116 prev = curr;
3119 TRACE("--- Done checking---\n");
3122 static RANGES ranges_create(int count)
3124 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3125 if (!ranges) return NULL;
3126 ranges->hdpa = DPA_Create(count);
3127 if (ranges->hdpa) return ranges;
3128 Free(ranges);
3129 return NULL;
3132 static void ranges_clear(RANGES ranges)
3134 INT i;
3136 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3137 Free(DPA_GetPtr(ranges->hdpa, i));
3138 DPA_DeleteAllPtrs(ranges->hdpa);
3142 static void ranges_destroy(RANGES ranges)
3144 if (!ranges) return;
3145 ranges_clear(ranges);
3146 DPA_Destroy(ranges->hdpa);
3147 Free(ranges);
3150 static RANGES ranges_clone(RANGES ranges)
3152 RANGES clone;
3153 INT i;
3155 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3157 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3159 RANGE *newrng = Alloc(sizeof(RANGE));
3160 if (!newrng) goto fail;
3161 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3162 if (!DPA_SetPtr(clone->hdpa, i, newrng))
3164 Free(newrng);
3165 goto fail;
3168 return clone;
3170 fail:
3171 TRACE ("clone failed\n");
3172 ranges_destroy(clone);
3173 return NULL;
3176 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3178 INT i;
3180 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3181 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3183 return ranges;
3186 static void ranges_dump(RANGES ranges)
3188 INT i;
3190 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3191 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3194 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3196 RANGE srchrng = { nItem, nItem + 1 };
3198 TRACE("(nItem=%d)\n", nItem);
3199 ranges_check(ranges, "before contain");
3200 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3203 static INT ranges_itemcount(RANGES ranges)
3205 INT i, count = 0;
3207 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3209 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3210 count += sel->upper - sel->lower;
3213 return count;
3216 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3218 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3219 INT index;
3221 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3222 if (index == -1) return TRUE;
3224 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3226 chkrng = DPA_GetPtr(ranges->hdpa, index);
3227 if (chkrng->lower >= nItem)
3228 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3229 if (chkrng->upper > nItem)
3230 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3232 return TRUE;
3235 static BOOL ranges_add(RANGES ranges, RANGE range)
3237 RANGE srchrgn;
3238 INT index;
3240 TRACE("(%s)\n", debugrange(&range));
3241 ranges_check(ranges, "before add");
3243 /* try find overlapping regions first */
3244 srchrgn.lower = range.lower - 1;
3245 srchrgn.upper = range.upper + 1;
3246 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3248 if (index == -1)
3250 RANGE *newrgn;
3252 TRACE("Adding new range\n");
3254 /* create the brand new range to insert */
3255 newrgn = Alloc(sizeof(RANGE));
3256 if(!newrgn) goto fail;
3257 *newrgn = range;
3259 /* figure out where to insert it */
3260 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3261 TRACE("index=%d\n", index);
3262 if (index == -1) index = 0;
3264 /* and get it over with */
3265 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3267 Free(newrgn);
3268 goto fail;
3271 else
3273 RANGE *chkrgn, *mrgrgn;
3274 INT fromindex, mergeindex;
3276 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3277 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3279 chkrgn->lower = min(range.lower, chkrgn->lower);
3280 chkrgn->upper = max(range.upper, chkrgn->upper);
3282 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3284 /* merge now common ranges */
3285 fromindex = 0;
3286 srchrgn.lower = chkrgn->lower - 1;
3287 srchrgn.upper = chkrgn->upper + 1;
3291 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3292 if (mergeindex == -1) break;
3293 if (mergeindex == index)
3295 fromindex = index + 1;
3296 continue;
3299 TRACE("Merge with index %i\n", mergeindex);
3301 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3302 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3303 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3304 Free(mrgrgn);
3305 DPA_DeletePtr(ranges->hdpa, mergeindex);
3306 if (mergeindex < index) index --;
3307 } while(1);
3310 ranges_check(ranges, "after add");
3311 return TRUE;
3313 fail:
3314 ranges_check(ranges, "failed add");
3315 return FALSE;
3318 static BOOL ranges_del(RANGES ranges, RANGE range)
3320 RANGE *chkrgn;
3321 INT index;
3323 TRACE("(%s)\n", debugrange(&range));
3324 ranges_check(ranges, "before del");
3326 /* we don't use DPAS_SORTED here, since we need *
3327 * to find the first overlapping range */
3328 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3329 while(index != -1)
3331 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3333 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3335 /* case 1: Same range */
3336 if ( (chkrgn->upper == range.upper) &&
3337 (chkrgn->lower == range.lower) )
3339 DPA_DeletePtr(ranges->hdpa, index);
3340 Free(chkrgn);
3341 break;
3343 /* case 2: engulf */
3344 else if ( (chkrgn->upper <= range.upper) &&
3345 (chkrgn->lower >= range.lower) )
3347 DPA_DeletePtr(ranges->hdpa, index);
3348 Free(chkrgn);
3350 /* case 3: overlap upper */
3351 else if ( (chkrgn->upper <= range.upper) &&
3352 (chkrgn->lower < range.lower) )
3354 chkrgn->upper = range.lower;
3356 /* case 4: overlap lower */
3357 else if ( (chkrgn->upper > range.upper) &&
3358 (chkrgn->lower >= range.lower) )
3360 chkrgn->lower = range.upper;
3361 break;
3363 /* case 5: fully internal */
3364 else
3366 RANGE *newrgn;
3368 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3369 newrgn->lower = chkrgn->lower;
3370 newrgn->upper = range.lower;
3371 chkrgn->lower = range.upper;
3372 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3374 Free(newrgn);
3375 goto fail;
3377 break;
3380 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3383 ranges_check(ranges, "after del");
3384 return TRUE;
3386 fail:
3387 ranges_check(ranges, "failed del");
3388 return FALSE;
3391 /***
3392 * DESCRIPTION:
3393 * Removes all selection ranges
3395 * Parameters(s):
3396 * [I] infoPtr : valid pointer to the listview structure
3397 * [I] toSkip : item range to skip removing the selection
3399 * RETURNS:
3400 * SUCCESS : TRUE
3401 * FAILURE : FALSE
3403 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3405 LVITEMW lvItem;
3406 ITERATOR i;
3407 RANGES clone;
3409 TRACE("()\n");
3411 lvItem.state = 0;
3412 lvItem.stateMask = LVIS_SELECTED;
3414 /* need to clone the DPA because callbacks can change it */
3415 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3416 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3417 while(iterator_next(&i))
3418 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3419 /* note that the iterator destructor will free the cloned range */
3420 iterator_destroy(&i);
3422 return TRUE;
3425 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3427 RANGES toSkip;
3429 if (!(toSkip = ranges_create(1))) return FALSE;
3430 if (nItem != -1) ranges_additem(toSkip, nItem);
3431 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3432 ranges_destroy(toSkip);
3433 return TRUE;
3436 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3438 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3441 /***
3442 * DESCRIPTION:
3443 * Retrieves the number of items that are marked as selected.
3445 * PARAMETER(S):
3446 * [I] infoPtr : valid pointer to the listview structure
3448 * RETURN:
3449 * Number of items selected.
3451 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3453 INT nSelectedCount = 0;
3455 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3457 INT i;
3458 for (i = 0; i < infoPtr->nItemCount; i++)
3460 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3461 nSelectedCount++;
3464 else
3465 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3467 TRACE("nSelectedCount=%d\n", nSelectedCount);
3468 return nSelectedCount;
3471 /***
3472 * DESCRIPTION:
3473 * Manages the item focus.
3475 * PARAMETER(S):
3476 * [I] infoPtr : valid pointer to the listview structure
3477 * [I] nItem : item index
3479 * RETURN:
3480 * TRUE : focused item changed
3481 * FALSE : focused item has NOT changed
3483 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3485 INT oldFocus = infoPtr->nFocusedItem;
3486 LVITEMW lvItem;
3488 if (nItem == infoPtr->nFocusedItem) return FALSE;
3490 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3491 lvItem.stateMask = LVIS_FOCUSED;
3492 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3494 return oldFocus != infoPtr->nFocusedItem;
3497 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3499 if (nShiftItem < nItem) return nShiftItem;
3501 if (nShiftItem > nItem) return nShiftItem + direction;
3503 if (direction > 0) return nShiftItem + direction;
3505 return min(nShiftItem, infoPtr->nItemCount - 1);
3508 /* This function updates focus index.
3510 Parameters:
3511 focus : current focus index
3512 item : index of item to be added/removed
3513 direction : add/remove flag
3515 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3517 BOOL old_change = infoPtr->bDoChangeNotify;
3519 infoPtr->bDoChangeNotify = FALSE;
3520 focus = shift_item(infoPtr, focus, item, direction);
3521 if (focus != infoPtr->nFocusedItem)
3522 LISTVIEW_SetItemFocus(infoPtr, focus);
3523 infoPtr->bDoChangeNotify = old_change;
3527 * DESCRIPTION:
3528 * Updates the various indices after an item has been inserted or deleted.
3530 * PARAMETER(S):
3531 * [I] infoPtr : valid pointer to the listview structure
3532 * [I] nItem : item index
3533 * [I] direction : Direction of shift, +1 or -1.
3535 * RETURN:
3536 * None
3538 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3540 TRACE("Shifting %i, %i steps\n", nItem, direction);
3542 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3543 assert(abs(direction) == 1);
3544 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3546 /* But we are not supposed to modify nHotItem! */
3550 * DESCRIPTION:
3551 * Adds a block of selections.
3553 * PARAMETER(S):
3554 * [I] infoPtr : valid pointer to the listview structure
3555 * [I] nItem : item index
3557 * RETURN:
3558 * Whether the window is still valid.
3560 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3562 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3563 INT nLast = max(infoPtr->nSelectionMark, nItem);
3564 HWND hwndSelf = infoPtr->hwndSelf;
3565 NMLVODSTATECHANGE nmlv;
3566 LVITEMW item;
3567 BOOL bOldChange;
3568 INT i;
3570 /* Temporarily disable change notification
3571 * If the control is LVS_OWNERDATA, we need to send
3572 * only one LVN_ODSTATECHANGED notification.
3573 * See MSDN documentation for LVN_ITEMCHANGED.
3575 bOldChange = infoPtr->bDoChangeNotify;
3576 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3578 if (nFirst == -1) nFirst = nItem;
3580 item.state = LVIS_SELECTED;
3581 item.stateMask = LVIS_SELECTED;
3583 for (i = nFirst; i <= nLast; i++)
3584 LISTVIEW_SetItemState(infoPtr,i,&item);
3586 ZeroMemory(&nmlv, sizeof(nmlv));
3587 nmlv.iFrom = nFirst;
3588 nmlv.iTo = nLast;
3589 nmlv.uOldState = 0;
3590 nmlv.uNewState = item.state;
3592 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3593 if (!IsWindow(hwndSelf))
3594 return FALSE;
3595 infoPtr->bDoChangeNotify = bOldChange;
3596 return TRUE;
3600 /***
3601 * DESCRIPTION:
3602 * Sets a single group selection.
3604 * PARAMETER(S):
3605 * [I] infoPtr : valid pointer to the listview structure
3606 * [I] nItem : item index
3608 * RETURN:
3609 * None
3611 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3613 RANGES selection;
3614 LVITEMW item;
3615 ITERATOR i;
3616 BOOL bOldChange;
3618 if (!(selection = ranges_create(100))) return;
3620 item.state = LVIS_SELECTED;
3621 item.stateMask = LVIS_SELECTED;
3623 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3625 if (infoPtr->nSelectionMark == -1)
3627 infoPtr->nSelectionMark = nItem;
3628 ranges_additem(selection, nItem);
3630 else
3632 RANGE sel;
3634 sel.lower = min(infoPtr->nSelectionMark, nItem);
3635 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3636 ranges_add(selection, sel);
3639 else
3641 RECT rcItem, rcSel, rcSelMark;
3642 POINT ptItem;
3644 rcItem.left = LVIR_BOUNDS;
3645 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3646 ranges_destroy (selection);
3647 return;
3649 rcSelMark.left = LVIR_BOUNDS;
3650 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3651 ranges_destroy (selection);
3652 return;
3654 UnionRect(&rcSel, &rcItem, &rcSelMark);
3655 iterator_frameditems(&i, infoPtr, &rcSel);
3656 while(iterator_next(&i))
3658 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3659 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3661 iterator_destroy(&i);
3664 /* disable per item notifications on LVS_OWNERDATA style
3665 FIXME: single LVN_ODSTATECHANGED should be used */
3666 bOldChange = infoPtr->bDoChangeNotify;
3667 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3669 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3672 iterator_rangesitems(&i, selection);
3673 while(iterator_next(&i))
3674 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3675 /* this will also destroy the selection */
3676 iterator_destroy(&i);
3678 infoPtr->bDoChangeNotify = bOldChange;
3680 LISTVIEW_SetItemFocus(infoPtr, nItem);
3683 /***
3684 * DESCRIPTION:
3685 * Sets a single selection.
3687 * PARAMETER(S):
3688 * [I] infoPtr : valid pointer to the listview structure
3689 * [I] nItem : item index
3691 * RETURN:
3692 * None
3694 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3696 LVITEMW lvItem;
3698 TRACE("nItem=%d\n", nItem);
3700 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3702 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3703 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3704 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3706 infoPtr->nSelectionMark = nItem;
3709 /***
3710 * DESCRIPTION:
3711 * Set selection(s) with keyboard.
3713 * PARAMETER(S):
3714 * [I] infoPtr : valid pointer to the listview structure
3715 * [I] nItem : item index
3716 * [I] space : VK_SPACE code sent
3718 * RETURN:
3719 * SUCCESS : TRUE (needs to be repainted)
3720 * FAILURE : FALSE (nothing has changed)
3722 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3724 /* FIXME: pass in the state */
3725 WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3726 WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3727 BOOL bResult = FALSE;
3729 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3730 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3732 bResult = TRUE;
3734 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3735 LISTVIEW_SetSelection(infoPtr, nItem);
3736 else
3738 if (wShift)
3739 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3740 else if (wCtrl)
3742 LVITEMW lvItem;
3743 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3744 lvItem.stateMask = LVIS_SELECTED;
3745 if (space)
3747 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3748 if (lvItem.state & LVIS_SELECTED)
3749 infoPtr->nSelectionMark = nItem;
3751 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3754 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3757 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3758 return bResult;
3761 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3763 LVHITTESTINFO lvHitTestInfo;
3765 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3766 lvHitTestInfo.pt.x = pt.x;
3767 lvHitTestInfo.pt.y = pt.y;
3769 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3771 lpLVItem->mask = LVIF_PARAM;
3772 lpLVItem->iItem = lvHitTestInfo.iItem;
3773 lpLVItem->iSubItem = 0;
3775 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3778 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3780 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3781 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3782 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3785 /***
3786 * DESCRIPTION:
3787 * Called when the mouse is being actively tracked and has hovered for a specified
3788 * amount of time
3790 * PARAMETER(S):
3791 * [I] infoPtr : valid pointer to the listview structure
3792 * [I] fwKeys : key indicator
3793 * [I] x,y : mouse position
3795 * RETURN:
3796 * 0 if the message was processed, non-zero if there was an error
3798 * INFO:
3799 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3800 * over the item for a certain period of time.
3803 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3805 NMHDR hdr;
3807 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3809 if (LISTVIEW_IsHotTracking(infoPtr))
3811 LVITEMW item;
3812 POINT pt;
3814 pt.x = x;
3815 pt.y = y;
3817 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3818 LISTVIEW_SetSelection(infoPtr, item.iItem);
3820 SetFocus(infoPtr->hwndSelf);
3823 return 0;
3826 #define SCROLL_LEFT 0x1
3827 #define SCROLL_RIGHT 0x2
3828 #define SCROLL_UP 0x4
3829 #define SCROLL_DOWN 0x8
3831 /***
3832 * DESCRIPTION:
3833 * Utility routine to draw and highlight items within a marquee selection rectangle.
3835 * PARAMETER(S):
3836 * [I] infoPtr : valid pointer to the listview structure
3837 * [I] coords_orig : original co-ordinates of the cursor
3838 * [I] coords_offs : offsetted coordinates of the cursor
3839 * [I] offset : offset amount
3840 * [I] scroll : Bitmask of which directions we should scroll, if at all
3842 * RETURN:
3843 * None.
3845 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3846 const POINT *coords_offs, const POINT *offset,
3847 INT scroll)
3849 BOOL controlDown = FALSE;
3850 LVITEMW item;
3851 ITERATOR old_elems, new_elems;
3852 RECT rect;
3854 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3856 rect.left = infoPtr->marqueeOrigin.x;
3857 rect.right = coords_offs->x;
3859 else
3861 rect.left = coords_offs->x;
3862 rect.right = infoPtr->marqueeOrigin.x;
3865 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3867 rect.top = infoPtr->marqueeOrigin.y;
3868 rect.bottom = coords_offs->y;
3870 else
3872 rect.top = coords_offs->y;
3873 rect.bottom = infoPtr->marqueeOrigin.y;
3876 /* Cancel out the old marquee rectangle and draw the new one */
3877 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3879 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3880 the cursor is further away */
3882 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3883 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3885 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3886 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3888 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3889 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3891 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3892 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3894 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3896 infoPtr->marqueeRect = rect;
3897 infoPtr->marqueeDrawRect = rect;
3898 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3900 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3901 iterator_remove_common_items(&old_elems, &new_elems);
3903 /* Iterate over no longer selected items */
3904 while (iterator_next(&old_elems))
3906 if (old_elems.nItem > -1)
3908 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3909 item.state = 0;
3910 else
3911 item.state = LVIS_SELECTED;
3913 item.stateMask = LVIS_SELECTED;
3915 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3918 iterator_destroy(&old_elems);
3921 /* Iterate over newly selected items */
3922 if (GetKeyState(VK_CONTROL) & 0x8000)
3923 controlDown = TRUE;
3925 while (iterator_next(&new_elems))
3927 if (new_elems.nItem > -1)
3929 /* If CTRL is pressed, invert. If not, always select the item. */
3930 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3931 item.state = 0;
3932 else
3933 item.state = LVIS_SELECTED;
3935 item.stateMask = LVIS_SELECTED;
3937 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3940 iterator_destroy(&new_elems);
3942 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3945 /***
3946 * DESCRIPTION:
3947 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3948 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3950 * PARAMETER(S):
3951 * [I] hwnd : Handle to the listview
3952 * [I] uMsg : WM_TIMER (ignored)
3953 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3954 * [I] dwTimer : The elapsed time (ignored)
3956 * RETURN:
3957 * None.
3959 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3961 LISTVIEW_INFO *infoPtr;
3962 SCROLLINFO scrollInfo;
3963 POINT coords_orig;
3964 POINT coords_offs;
3965 POINT offset;
3966 INT scroll = 0;
3968 infoPtr = (LISTVIEW_INFO *) idEvent;
3970 if (!infoPtr)
3971 return;
3973 /* Get the current cursor position and convert to client coordinates */
3974 GetCursorPos(&coords_orig);
3975 ScreenToClient(hWnd, &coords_orig);
3977 /* Ensure coordinates are within client bounds */
3978 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3979 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3981 /* Get offset */
3982 LISTVIEW_GetOrigin(infoPtr, &offset);
3984 /* Offset coordinates by the appropriate amount */
3985 coords_offs.x -= offset.x;
3986 coords_offs.y -= offset.y;
3988 scrollInfo.cbSize = sizeof(SCROLLINFO);
3989 scrollInfo.fMask = SIF_ALL;
3991 /* Work out in which directions we can scroll */
3992 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3994 if (scrollInfo.nPos != scrollInfo.nMin)
3995 scroll |= SCROLL_UP;
3997 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3998 scroll |= SCROLL_DOWN;
4001 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4003 if (scrollInfo.nPos != scrollInfo.nMin)
4004 scroll |= SCROLL_LEFT;
4006 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
4007 scroll |= SCROLL_RIGHT;
4010 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
4011 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
4012 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
4013 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
4015 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
4019 /***
4020 * DESCRIPTION:
4021 * Called whenever WM_MOUSEMOVE is received.
4023 * PARAMETER(S):
4024 * [I] infoPtr : valid pointer to the listview structure
4025 * [I] fwKeys : key indicator
4026 * [I] x,y : mouse position
4028 * RETURN:
4029 * 0 if the message is processed, non-zero if there was an error
4031 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
4033 LVHITTESTINFO ht;
4034 RECT rect;
4035 POINT pt;
4037 if (!(fwKeys & MK_LBUTTON))
4038 infoPtr->bLButtonDown = FALSE;
4040 if (infoPtr->bLButtonDown)
4042 rect.left = rect.right = infoPtr->ptClickPos.x;
4043 rect.top = rect.bottom = infoPtr->ptClickPos.y;
4045 InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
4047 if (infoPtr->bMarqueeSelect)
4049 POINT coords_orig;
4050 POINT coords_offs;
4051 POINT offset;
4053 coords_orig.x = x;
4054 coords_orig.y = y;
4056 /* Get offset */
4057 LISTVIEW_GetOrigin(infoPtr, &offset);
4059 /* Ensure coordinates are within client bounds */
4060 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4061 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4063 /* Offset coordinates by the appropriate amount */
4064 coords_offs.x -= offset.x;
4065 coords_offs.y -= offset.y;
4067 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4068 move the mouse again */
4070 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4071 (y >= infoPtr->rcList.bottom))
4073 if (!infoPtr->bScrolling)
4075 infoPtr->bScrolling = TRUE;
4076 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4079 else
4081 infoPtr->bScrolling = FALSE;
4082 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4085 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4086 return 0;
4089 pt.x = x;
4090 pt.y = y;
4092 ht.pt = pt;
4093 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4095 /* reset item marker */
4096 if (infoPtr->nLButtonDownItem != ht.iItem)
4097 infoPtr->nLButtonDownItem = -1;
4099 if (!PtInRect(&rect, pt))
4101 /* this path covers the following:
4102 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4103 2. change focus with keys
4104 3. move mouse over item from step 1 selects it and moves focus on it */
4105 if (infoPtr->nLButtonDownItem != -1 &&
4106 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4108 LVITEMW lvItem;
4110 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4111 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4113 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4114 infoPtr->nLButtonDownItem = -1;
4117 if (!infoPtr->bDragging)
4119 ht.pt = infoPtr->ptClickPos;
4120 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4122 /* If the click is outside the range of an item, begin a
4123 highlight. If not, begin an item drag. */
4124 if (ht.iItem == -1)
4126 NMHDR hdr;
4128 /* If we're allowing multiple selections, send notification.
4129 If return value is non-zero, cancel. */
4130 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4132 /* Store the absolute coordinates of the click */
4133 POINT offset;
4134 LISTVIEW_GetOrigin(infoPtr, &offset);
4136 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4137 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4139 /* Begin selection and capture mouse */
4140 infoPtr->bMarqueeSelect = TRUE;
4141 SetCapture(infoPtr->hwndSelf);
4144 else
4146 NMLISTVIEW nmlv;
4148 ZeroMemory(&nmlv, sizeof(nmlv));
4149 nmlv.iItem = ht.iItem;
4150 nmlv.ptAction = infoPtr->ptClickPos;
4152 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4153 infoPtr->bDragging = TRUE;
4157 return 0;
4161 /* see if we are supposed to be tracking mouse hovering */
4162 if (LISTVIEW_IsHotTracking(infoPtr)) {
4163 TRACKMOUSEEVENT trackinfo;
4164 DWORD flags;
4166 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4167 trackinfo.dwFlags = TME_QUERY;
4169 /* see if we are already tracking this hwnd */
4170 _TrackMouseEvent(&trackinfo);
4172 flags = TME_LEAVE;
4173 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4174 flags |= TME_HOVER;
4176 if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4177 trackinfo.dwFlags = flags;
4178 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4179 trackinfo.hwndTrack = infoPtr->hwndSelf;
4181 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4182 _TrackMouseEvent(&trackinfo);
4186 return 0;
4190 /***
4191 * Tests whether the item is assignable to a list with style lStyle
4193 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4195 if ( (lpLVItem->mask & LVIF_TEXT) &&
4196 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4197 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4199 return TRUE;
4203 /***
4204 * DESCRIPTION:
4205 * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4207 * PARAMETER(S):
4208 * [I] infoPtr : valid pointer to the listview structure
4209 * [I] lpLVItem : valid pointer to new item attributes
4210 * [I] isNew : the item being set is being inserted
4211 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4212 * [O] bChanged : will be set to TRUE if the item really changed
4214 * RETURN:
4215 * SUCCESS : TRUE
4216 * FAILURE : FALSE
4218 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4220 ITEM_INFO *lpItem;
4221 NMLISTVIEW nmlv;
4222 UINT uChanged = 0;
4223 LVITEMW item;
4224 /* stateMask is ignored for LVM_INSERTITEM */
4225 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4227 TRACE("()\n");
4229 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4231 if (lpLVItem->mask == 0) return TRUE;
4233 if (infoPtr->dwStyle & LVS_OWNERDATA)
4235 /* a virtual listview only stores selection and focus */
4236 if (lpLVItem->mask & ~LVIF_STATE)
4237 return FALSE;
4238 lpItem = NULL;
4240 else
4242 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4243 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4244 assert (lpItem);
4247 /* we need to get the lParam and state of the item */
4248 item.iItem = lpLVItem->iItem;
4249 item.iSubItem = lpLVItem->iSubItem;
4250 item.mask = LVIF_STATE | LVIF_PARAM;
4251 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4253 item.state = 0;
4254 item.lParam = 0;
4255 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4257 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4258 /* determine what fields will change */
4259 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4260 uChanged |= LVIF_STATE;
4262 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4263 uChanged |= LVIF_IMAGE;
4265 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4266 uChanged |= LVIF_PARAM;
4268 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4269 uChanged |= LVIF_INDENT;
4271 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4272 uChanged |= LVIF_TEXT;
4274 TRACE("change mask=0x%x\n", uChanged);
4276 memset(&nmlv, 0, sizeof(NMLISTVIEW));
4277 nmlv.iItem = lpLVItem->iItem;
4278 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4279 nmlv.uOldState = item.state;
4280 nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4281 nmlv.lParam = item.lParam;
4283 /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4284 and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4285 are enabled. Even nothing really changed we still need to send this,
4286 in this case uChanged mask is just set to passed item mask. */
4287 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4289 HWND hwndSelf = infoPtr->hwndSelf;
4291 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4292 return FALSE;
4293 if (!IsWindow(hwndSelf))
4294 return FALSE;
4297 /* When item is inserted we need to shift existing focus index if new item has lower index. */
4298 if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4299 /* this means we won't hit a focus change path later */
4300 ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4302 if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4303 infoPtr->nFocusedItem++;
4306 if (!uChanged) return TRUE;
4307 *bChanged = TRUE;
4309 /* copy information */
4310 if (lpLVItem->mask & LVIF_TEXT)
4311 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4313 if (lpLVItem->mask & LVIF_IMAGE)
4314 lpItem->hdr.iImage = lpLVItem->iImage;
4316 if (lpLVItem->mask & LVIF_PARAM)
4317 lpItem->lParam = lpLVItem->lParam;
4319 if (lpLVItem->mask & LVIF_INDENT)
4320 lpItem->iIndent = lpLVItem->iIndent;
4322 if (uChanged & LVIF_STATE)
4324 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4326 lpItem->state &= ~stateMask;
4327 lpItem->state |= (lpLVItem->state & stateMask);
4329 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4331 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4332 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4334 else if (stateMask & LVIS_SELECTED)
4336 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4338 /* If we are asked to change focus, and we manage it, do it.
4339 It's important to have all new item data stored at this point,
4340 because changing existing focus could result in a redrawing operation,
4341 which in turn could ask for disp data, application should see all data
4342 for inserted item when processing LVN_GETDISPINFO.
4344 The way this works application will see nested item change notifications -
4345 changed item notifications interrupted by ones from item losing focus. */
4346 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4348 if (lpLVItem->state & LVIS_FOCUSED)
4350 /* update selection mark */
4351 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4352 infoPtr->nSelectionMark = lpLVItem->iItem;
4354 if (infoPtr->nFocusedItem != -1)
4356 /* remove current focus */
4357 item.mask = LVIF_STATE;
4358 item.state = 0;
4359 item.stateMask = LVIS_FOCUSED;
4361 /* recurse with redrawing an item */
4362 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4365 infoPtr->nFocusedItem = lpLVItem->iItem;
4366 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4368 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4370 infoPtr->nFocusedItem = -1;
4375 /* if we're inserting the item, we're done */
4376 if (isNew) return TRUE;
4378 /* send LVN_ITEMCHANGED notification */
4379 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4380 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4382 return TRUE;
4385 /***
4386 * DESCRIPTION:
4387 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4389 * PARAMETER(S):
4390 * [I] infoPtr : valid pointer to the listview structure
4391 * [I] lpLVItem : valid pointer to new subitem attributes
4392 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4393 * [O] bChanged : will be set to TRUE if the item really changed
4395 * RETURN:
4396 * SUCCESS : TRUE
4397 * FAILURE : FALSE
4399 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4401 HDPA hdpaSubItems;
4402 SUBITEM_INFO *lpSubItem;
4404 /* we do not support subitems for virtual listviews */
4405 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4407 /* set subitem only if column is present */
4408 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4410 /* First do some sanity checks */
4411 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4412 particularly useful. We currently do not actually do anything with
4413 the flag on subitems.
4415 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4416 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4418 /* get the subitem structure, and create it if not there */
4419 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4420 assert (hdpaSubItems);
4422 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4423 if (!lpSubItem)
4425 SUBITEM_INFO *tmpSubItem;
4426 INT i;
4428 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4429 if (!lpSubItem) return FALSE;
4430 /* we could binary search here, if need be...*/
4431 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4433 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4434 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4436 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4438 Free(lpSubItem);
4439 return FALSE;
4441 lpSubItem->iSubItem = lpLVItem->iSubItem;
4442 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4443 *bChanged = TRUE;
4446 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4448 lpSubItem->hdr.iImage = lpLVItem->iImage;
4449 *bChanged = TRUE;
4452 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4454 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4455 *bChanged = TRUE;
4458 return TRUE;
4461 /***
4462 * DESCRIPTION:
4463 * Sets item attributes.
4465 * PARAMETER(S):
4466 * [I] infoPtr : valid pointer to the listview structure
4467 * [I] lpLVItem : new item attributes
4468 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4470 * RETURN:
4471 * SUCCESS : TRUE
4472 * FAILURE : FALSE
4474 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4476 HWND hwndSelf = infoPtr->hwndSelf;
4477 LPWSTR pszText = NULL;
4478 BOOL bResult, bChanged = FALSE;
4479 RECT oldItemArea;
4481 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4483 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4484 return FALSE;
4486 /* Store old item area */
4487 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4489 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4490 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4492 pszText = lpLVItem->pszText;
4493 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4496 /* actually set the fields */
4497 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4499 if (lpLVItem->iSubItem)
4500 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4501 else
4502 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4503 if (!IsWindow(hwndSelf))
4504 return FALSE;
4506 /* redraw item, if necessary */
4507 if (bChanged && !infoPtr->bIsDrawing)
4509 /* this little optimization eliminates some nasty flicker */
4510 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4511 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4512 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4513 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4514 else
4516 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4517 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4520 /* restore text */
4521 if (pszText)
4523 textfreeT(lpLVItem->pszText, isW);
4524 lpLVItem->pszText = pszText;
4527 return bResult;
4530 /***
4531 * DESCRIPTION:
4532 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4534 * PARAMETER(S):
4535 * [I] infoPtr : valid pointer to the listview structure
4537 * RETURN:
4538 * item index
4540 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4542 INT nItem = 0;
4543 SCROLLINFO scrollInfo;
4545 scrollInfo.cbSize = sizeof(SCROLLINFO);
4546 scrollInfo.fMask = SIF_POS;
4548 if (infoPtr->uView == LV_VIEW_LIST)
4550 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4551 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4553 else if (infoPtr->uView == LV_VIEW_DETAILS)
4555 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4556 nItem = scrollInfo.nPos;
4558 else
4560 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4561 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4564 TRACE("nItem=%d\n", nItem);
4566 return nItem;
4570 /***
4571 * DESCRIPTION:
4572 * Erases the background of the given rectangle
4574 * PARAMETER(S):
4575 * [I] infoPtr : valid pointer to the listview structure
4576 * [I] hdc : device context handle
4577 * [I] lprcBox : clipping rectangle
4579 * RETURN:
4580 * Success: TRUE
4581 * Failure: FALSE
4583 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4585 if (!infoPtr->hBkBrush) return FALSE;
4587 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4589 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4592 /* Draw main item or subitem */
4593 static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const NMLVCUSTOMDRAW *nmlvcd, const POINT *pos)
4595 RECT rcSelect, rcLabel, rcBox, rcStateIcon, rcIcon;
4596 const RECT *background;
4597 HIMAGELIST himl;
4598 UINT format;
4599 RECT *focus;
4601 /* now check if we need to update the focus rectangle */
4602 focus = infoPtr->bFocus && (item->state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4603 if (!focus) item->state &= ~LVIS_FOCUSED;
4605 LISTVIEW_GetItemMetrics(infoPtr, item, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4606 OffsetRect(&rcBox, pos->x, pos->y);
4607 OffsetRect(&rcSelect, pos->x, pos->y);
4608 OffsetRect(&rcIcon, pos->x, pos->y);
4609 OffsetRect(&rcStateIcon, pos->x, pos->y);
4610 OffsetRect(&rcLabel, pos->x, pos->y);
4611 TRACE("%d: box=%s, select=%s, icon=%s. label=%s\n", item->iSubItem,
4612 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4613 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4615 /* FIXME: temporary hack */
4616 rcSelect.left = rcLabel.left;
4618 if (infoPtr->uView == LV_VIEW_DETAILS && item->iSubItem == 0)
4620 if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4621 OffsetRect(&rcSelect, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4622 OffsetRect(&rcIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4623 OffsetRect(&rcStateIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4624 OffsetRect(&rcLabel, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4627 /* in icon mode, the label rect is really what we want to draw the
4628 * background for */
4629 /* in detail mode, we want to paint background for label rect when
4630 * item is not selected or listview has full row select; otherwise paint
4631 * background for text only */
4632 if ( infoPtr->uView == LV_VIEW_ICON ||
4633 (infoPtr->uView == LV_VIEW_DETAILS && (!(item->state & LVIS_SELECTED) ||
4634 (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
4635 background = &rcLabel;
4636 else
4637 background = &rcSelect;
4639 if (nmlvcd->clrTextBk != CLR_NONE)
4640 ExtTextOutW(nmlvcd->nmcd.hdc, background->left, background->top, ETO_OPAQUE, background, NULL, 0, NULL);
4642 if (item->state & LVIS_FOCUSED)
4644 if (infoPtr->uView == LV_VIEW_DETAILS)
4646 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4648 /* we have to update left focus bound too if item isn't in leftmost column
4649 and reduce right box bound */
4650 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4652 INT leftmost;
4654 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4656 INT Originx = pos->x - LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left;
4657 INT rightmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4658 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4660 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, rightmost)->rcHeader.right + Originx;
4661 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4664 rcSelect.right = rcBox.right;
4666 infoPtr->rcFocus = rcSelect;
4668 else
4669 infoPtr->rcFocus = rcLabel;
4672 /* state icons */
4673 if (infoPtr->himlState && STATEIMAGEINDEX(item->state) && (item->iSubItem == 0))
4675 UINT stateimage = STATEIMAGEINDEX(item->state);
4676 if (stateimage)
4678 TRACE("stateimage=%d\n", stateimage);
4679 ImageList_Draw(infoPtr->himlState, stateimage-1, nmlvcd->nmcd.hdc, rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4683 /* item icons */
4684 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4685 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon))
4687 UINT style;
4689 TRACE("iImage=%d\n", item->iImage);
4691 if (item->state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4692 style = ILD_SELECTED;
4693 else
4694 style = ILD_NORMAL;
4696 ImageList_DrawEx(himl, item->iImage, nmlvcd->nmcd.hdc, rcIcon.left, rcIcon.top,
4697 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4698 item->state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4699 style | (item->state & LVIS_OVERLAYMASK));
4702 /* Don't bother painting item being edited */
4703 if (infoPtr->hwndEdit && item->iItem == infoPtr->nEditLabelItem && item->iSubItem == 0) return;
4705 /* figure out the text drawing flags */
4706 format = (infoPtr->uView == LV_VIEW_ICON ? (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4707 if (infoPtr->uView == LV_VIEW_ICON)
4708 format = (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4709 else if (item->iSubItem)
4711 switch (LISTVIEW_GetColumnInfo(infoPtr, item->iSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4713 case LVCFMT_RIGHT: format |= DT_RIGHT; break;
4714 case LVCFMT_CENTER: format |= DT_CENTER; break;
4715 default: format |= DT_LEFT;
4718 if (!(format & (DT_RIGHT | DT_CENTER)))
4720 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4721 else rcLabel.left += LABEL_HOR_PADDING;
4723 else if (format & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4725 /* for GRIDLINES reduce the bottom so the text formats correctly */
4726 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4727 rcLabel.bottom--;
4729 DrawTextW(nmlvcd->nmcd.hdc, item->pszText, -1, &rcLabel, format);
4732 /***
4733 * DESCRIPTION:
4734 * Draws an item.
4736 * PARAMETER(S):
4737 * [I] infoPtr : valid pointer to the listview structure
4738 * [I] hdc : device context handle
4739 * [I] nItem : item index
4740 * [I] nSubItem : subitem index
4741 * [I] pos : item position in client coordinates
4742 * [I] cdmode : custom draw mode
4744 * RETURN:
4745 * Success: TRUE
4746 * Failure: FALSE
4748 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERATOR *subitems, POINT pos, DWORD cdmode)
4750 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4751 static WCHAR callbackW[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4752 DWORD cdsubitemmode = CDRF_DODEFAULT;
4753 RECT *focus, rcBox;
4754 NMLVCUSTOMDRAW nmlvcd;
4755 LVITEMW lvItem;
4757 TRACE("(hdc=%p, nItem=%d, subitems=%p, pos=%s)\n", hdc, nItem, subitems, wine_dbgstr_point(&pos));
4759 /* get information needed for drawing the item */
4760 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
4761 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4762 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4763 lvItem.iItem = nItem;
4764 lvItem.iSubItem = 0;
4765 lvItem.state = 0;
4766 lvItem.lParam = 0;
4767 lvItem.cchTextMax = DISP_TEXT_SIZE;
4768 lvItem.pszText = szDispText;
4769 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4770 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4771 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4773 /* now check if we need to update the focus rectangle */
4774 focus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4775 if (!focus) lvItem.state &= ~LVIS_FOCUSED;
4777 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, NULL, NULL, NULL);
4778 OffsetRect(&rcBox, pos.x, pos.y);
4780 /* Full custom draw stage sequence looks like this:
4782 LV_VIEW_DETAILS:
4784 - CDDS_ITEMPREPAINT
4785 - CDDS_ITEMPREPAINT|CDDS_SUBITEM | => sent n times, where n is number of subitems,
4786 CDDS_ITEMPOSTPAINT|CDDS_SUBITEM | including item itself
4787 - CDDS_ITEMPOSTPAINT
4789 other styles:
4791 - CDDS_ITEMPREPAINT
4792 - CDDS_ITEMPOSTPAINT
4795 /* fill in the custom draw structure */
4796 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4797 if (cdmode & CDRF_NOTIFYITEMDRAW)
4798 cdsubitemmode = notify_customdraw(infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
4799 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4801 if (subitems)
4803 while (iterator_next(subitems))
4805 DWORD subitemstage = CDRF_DODEFAULT;
4807 /* We need to query for each subitem, item's data (subitem == 0) is already here at this point */
4808 if (subitems->nItem)
4810 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_INDENT;
4811 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4812 lvItem.iItem = nItem;
4813 lvItem.iSubItem = subitems->nItem;
4814 lvItem.state = 0;
4815 lvItem.lParam = 0;
4816 lvItem.cchTextMax = DISP_TEXT_SIZE;
4817 lvItem.pszText = szDispText;
4818 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4819 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4820 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4821 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4822 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4824 /* update custom draw data */
4825 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &nmlvcd.nmcd.rc, NULL, NULL, NULL, NULL);
4826 OffsetRect(&nmlvcd.nmcd.rc, pos.x, pos.y);
4827 nmlvcd.iSubItem = subitems->nItem;
4830 if (cdsubitemmode & CDRF_NOTIFYSUBITEMDRAW)
4831 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4832 else
4834 nmlvcd.clrTextBk = infoPtr->clrTextBk;
4835 nmlvcd.clrText = infoPtr->clrText;
4838 if (subitems->nItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4839 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4840 else if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4841 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4843 if (!(subitemstage & CDRF_SKIPDEFAULT))
4844 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4846 if (subitemstage & CDRF_NOTIFYPOSTPAINT)
4847 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPOSTPAINT, &nmlvcd);
4850 else
4852 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4853 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4856 postpaint:
4857 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4859 nmlvcd.iSubItem = 0;
4860 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
4863 return TRUE;
4866 /***
4867 * DESCRIPTION:
4868 * Draws listview items when in owner draw mode.
4870 * PARAMETER(S):
4871 * [I] infoPtr : valid pointer to the listview structure
4872 * [I] hdc : device context handle
4874 * RETURN:
4875 * None
4877 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4879 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4880 DWORD cditemmode = CDRF_DODEFAULT;
4881 NMLVCUSTOMDRAW nmlvcd;
4882 POINT Origin, Position;
4883 DRAWITEMSTRUCT dis;
4884 LVITEMW item;
4886 TRACE("()\n");
4888 ZeroMemory(&dis, sizeof(dis));
4890 /* Get scroll info once before loop */
4891 LISTVIEW_GetOrigin(infoPtr, &Origin);
4893 /* iterate through the invalidated rows */
4894 while(iterator_next(i))
4896 item.iItem = i->nItem;
4897 item.iSubItem = 0;
4898 item.mask = LVIF_PARAM | LVIF_STATE;
4899 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4900 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4902 dis.CtlType = ODT_LISTVIEW;
4903 dis.CtlID = uID;
4904 dis.itemID = item.iItem;
4905 dis.itemAction = ODA_DRAWENTIRE;
4906 dis.itemState = 0;
4907 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4908 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4909 dis.hwndItem = infoPtr->hwndSelf;
4910 dis.hDC = hdc;
4911 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4912 dis.rcItem.left = Position.x + Origin.x;
4913 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4914 dis.rcItem.top = Position.y + Origin.y;
4915 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4916 dis.itemData = item.lParam;
4918 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4921 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4922 * structure for the rest. of the paint cycle
4924 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4925 if (cdmode & CDRF_NOTIFYITEMDRAW)
4926 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4928 if (!(cditemmode & CDRF_SKIPDEFAULT))
4930 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4931 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4934 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4935 notify_postpaint(infoPtr, &nmlvcd);
4939 /***
4940 * DESCRIPTION:
4941 * Draws listview items when in report display mode.
4943 * PARAMETER(S):
4944 * [I] infoPtr : valid pointer to the listview structure
4945 * [I] hdc : device context handle
4946 * [I] cdmode : custom draw mode
4948 * RETURN:
4949 * None
4951 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4953 INT rgntype;
4954 RECT rcClip, rcItem;
4955 POINT Origin;
4956 RANGES colRanges;
4957 INT col;
4958 ITERATOR j;
4960 TRACE("()\n");
4962 /* figure out what to draw */
4963 rgntype = GetClipBox(hdc, &rcClip);
4964 if (rgntype == NULLREGION) return;
4966 /* Get scroll info once before loop */
4967 LISTVIEW_GetOrigin(infoPtr, &Origin);
4969 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4971 /* narrow down the columns we need to paint */
4972 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4974 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4976 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4977 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4978 ranges_additem(colRanges, index);
4980 iterator_rangesitems(&j, colRanges);
4982 /* in full row select, we _have_ to draw the main item */
4983 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4984 j.nSpecial = 0;
4986 /* iterate through the invalidated rows */
4987 while(iterator_next(i))
4989 RANGES subitems;
4990 POINT Position;
4991 ITERATOR k;
4993 SelectObject(hdc, infoPtr->hFont);
4994 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4995 Position.x = Origin.x;
4996 Position.y += Origin.y;
4998 subitems = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5000 /* iterate through the invalidated columns */
5001 while(iterator_next(&j))
5003 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5005 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
5007 rcItem.top = 0;
5008 rcItem.bottom = infoPtr->nItemHeight;
5009 OffsetRect(&rcItem, Origin.x, Position.y);
5010 if (!RectVisible(hdc, &rcItem)) continue;
5013 ranges_additem(subitems, j.nItem);
5016 iterator_rangesitems(&k, subitems);
5017 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, &k, Position, cdmode);
5018 iterator_destroy(&k);
5020 iterator_destroy(&j);
5023 /***
5024 * DESCRIPTION:
5025 * Draws the gridlines if necessary when in report display mode.
5027 * PARAMETER(S):
5028 * [I] infoPtr : valid pointer to the listview structure
5029 * [I] hdc : device context handle
5031 * RETURN:
5032 * None
5034 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
5036 INT rgntype;
5037 INT y, itemheight;
5038 INT col, index;
5039 HPEN hPen, hOldPen;
5040 RECT rcClip, rcItem = {0};
5041 POINT Origin;
5042 RANGES colRanges;
5043 ITERATOR j;
5044 BOOL rmost = FALSE;
5046 TRACE("()\n");
5048 /* figure out what to draw */
5049 rgntype = GetClipBox(hdc, &rcClip);
5050 if (rgntype == NULLREGION) return;
5052 /* Get scroll info once before loop */
5053 LISTVIEW_GetOrigin(infoPtr, &Origin);
5055 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5057 /* narrow down the columns we need to paint */
5058 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
5060 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
5062 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5063 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
5064 ranges_additem(colRanges, index);
5067 /* is right most vertical line visible? */
5068 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
5070 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5071 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5072 rmost = (rcItem.right + Origin.x < rcClip.right);
5075 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
5077 hOldPen = SelectObject ( hdc, hPen );
5079 /* draw the vertical lines for the columns */
5080 iterator_rangesitems(&j, colRanges);
5081 while(iterator_next(&j))
5083 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5084 if (rcItem.left == 0) continue; /* skip leftmost column */
5085 rcItem.left += Origin.x;
5086 rcItem.right += Origin.x;
5087 rcItem.top = infoPtr->rcList.top;
5088 rcItem.bottom = infoPtr->rcList.bottom;
5089 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
5090 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5091 LineTo (hdc, rcItem.left, rcItem.bottom);
5093 iterator_destroy(&j);
5094 /* draw rightmost grid line if visible */
5095 if (rmost)
5097 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
5098 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5099 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5101 rcItem.right += Origin.x;
5103 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
5104 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
5107 /* draw the horizontal lines for the rows */
5108 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
5109 rcItem.left = infoPtr->rcList.left;
5110 rcItem.right = infoPtr->rcList.right;
5111 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
5113 rcItem.bottom = rcItem.top = y;
5114 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
5115 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5116 LineTo (hdc, rcItem.right, rcItem.top);
5119 SelectObject( hdc, hOldPen );
5120 DeleteObject( hPen );
5122 else
5123 ranges_destroy(colRanges);
5126 /***
5127 * DESCRIPTION:
5128 * Draws listview items when in list display mode.
5130 * PARAMETER(S):
5131 * [I] infoPtr : valid pointer to the listview structure
5132 * [I] hdc : device context handle
5133 * [I] cdmode : custom draw mode
5135 * RETURN:
5136 * None
5138 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
5140 POINT Origin, Position;
5142 /* Get scroll info once before loop */
5143 LISTVIEW_GetOrigin(infoPtr, &Origin);
5145 while(iterator_prev(i))
5147 SelectObject(hdc, infoPtr->hFont);
5148 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
5149 Position.x += Origin.x;
5150 Position.y += Origin.y;
5152 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, NULL, Position, cdmode);
5157 /***
5158 * DESCRIPTION:
5159 * Draws listview items.
5161 * PARAMETER(S):
5162 * [I] infoPtr : valid pointer to the listview structure
5163 * [I] hdc : device context handle
5164 * [I] prcErase : rect to be erased before refresh (may be NULL)
5166 * RETURN:
5167 * NoneX
5169 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5171 COLORREF oldTextColor = 0, oldBkColor = 0;
5172 NMLVCUSTOMDRAW nmlvcd;
5173 HFONT hOldFont = 0;
5174 DWORD cdmode;
5175 INT oldBkMode = 0;
5176 RECT rcClient;
5177 ITERATOR i;
5178 HDC hdcOrig = hdc;
5179 HBITMAP hbmp = NULL;
5180 RANGE range;
5182 LISTVIEW_DUMP(infoPtr);
5184 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5185 TRACE("double buffering\n");
5187 hdc = CreateCompatibleDC(hdcOrig);
5188 if (!hdc) {
5189 ERR("Failed to create DC for backbuffer\n");
5190 return;
5192 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5193 infoPtr->rcList.bottom);
5194 if (!hbmp) {
5195 ERR("Failed to create bitmap for backbuffer\n");
5196 DeleteDC(hdc);
5197 return;
5200 SelectObject(hdc, hbmp);
5201 SelectObject(hdc, infoPtr->hFont);
5203 if(GetClipBox(hdcOrig, &rcClient))
5204 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5205 } else {
5206 /* Save dc values we're gonna trash while drawing
5207 * FIXME: Should be done in LISTVIEW_DrawItem() */
5208 hOldFont = SelectObject(hdc, infoPtr->hFont);
5209 oldBkMode = GetBkMode(hdc);
5210 oldBkColor = GetBkColor(hdc);
5211 oldTextColor = GetTextColor(hdc);
5214 infoPtr->bIsDrawing = TRUE;
5216 if (prcErase) {
5217 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5218 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5219 /* If no erasing was done (usually because RedrawWindow was called
5220 * with RDW_INVALIDATE only) we need to copy the old contents into
5221 * the backbuffer before continuing. */
5222 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5223 infoPtr->rcList.right - infoPtr->rcList.left,
5224 infoPtr->rcList.bottom - infoPtr->rcList.top,
5225 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5228 GetClientRect(infoPtr->hwndSelf, &rcClient);
5229 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5230 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5231 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5233 /* nothing to draw */
5234 if(infoPtr->nItemCount == 0) goto enddraw;
5236 /* figure out what we need to draw */
5237 iterator_visibleitems(&i, infoPtr, hdc);
5238 range = iterator_range(&i);
5240 /* send cache hint notification */
5241 if (infoPtr->dwStyle & LVS_OWNERDATA)
5243 NMLVCACHEHINT nmlv;
5245 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5246 nmlv.iFrom = range.lower;
5247 nmlv.iTo = range.upper - 1;
5248 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5251 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5252 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5253 else
5255 if (infoPtr->uView == LV_VIEW_DETAILS)
5256 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5257 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5258 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5260 /* if we have a focus rect and it's visible, draw it */
5261 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5262 (range.upper - 1) >= infoPtr->nFocusedItem)
5263 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5265 iterator_destroy(&i);
5267 enddraw:
5268 /* For LVS_EX_GRIDLINES go and draw lines */
5269 /* This includes the case where there were *no* items */
5270 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5271 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5273 /* Draw marquee rectangle if appropriate */
5274 if (infoPtr->bMarqueeSelect)
5275 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5277 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5278 notify_postpaint(infoPtr, &nmlvcd);
5280 if(hbmp) {
5281 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5282 infoPtr->rcList.right - infoPtr->rcList.left,
5283 infoPtr->rcList.bottom - infoPtr->rcList.top,
5284 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5286 DeleteObject(hbmp);
5287 DeleteDC(hdc);
5288 } else {
5289 SelectObject(hdc, hOldFont);
5290 SetBkMode(hdc, oldBkMode);
5291 SetBkColor(hdc, oldBkColor);
5292 SetTextColor(hdc, oldTextColor);
5295 infoPtr->bIsDrawing = FALSE;
5299 /***
5300 * DESCRIPTION:
5301 * Calculates the approximate width and height of a given number of items.
5303 * PARAMETER(S):
5304 * [I] infoPtr : valid pointer to the listview structure
5305 * [I] nItemCount : number of items
5306 * [I] wWidth : width
5307 * [I] wHeight : height
5309 * RETURN:
5310 * Returns a DWORD. The width in the low word and the height in high word.
5312 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5313 WORD wWidth, WORD wHeight)
5315 DWORD dwViewRect = 0;
5317 if (nItemCount == -1)
5318 nItemCount = infoPtr->nItemCount;
5320 if (infoPtr->uView == LV_VIEW_LIST)
5322 INT nItemCountPerColumn = 1;
5323 INT nColumnCount = 0;
5325 if (wHeight == 0xFFFF)
5327 /* use current height */
5328 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5331 if (wHeight < infoPtr->nItemHeight)
5332 wHeight = infoPtr->nItemHeight;
5334 if (nItemCount > 0)
5336 if (infoPtr->nItemHeight > 0)
5338 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5339 if (nItemCountPerColumn == 0)
5340 nItemCountPerColumn = 1;
5342 if (nItemCount % nItemCountPerColumn != 0)
5343 nColumnCount = nItemCount / nItemCountPerColumn;
5344 else
5345 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5349 /* Microsoft padding magic */
5350 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5351 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5353 dwViewRect = MAKELONG(wWidth, wHeight);
5355 else if (infoPtr->uView == LV_VIEW_DETAILS)
5357 RECT rcBox;
5359 if (infoPtr->nItemCount > 0)
5361 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5362 wWidth = rcBox.right - rcBox.left;
5363 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5365 else
5367 /* use current height and width */
5368 if (wHeight == 0xffff)
5369 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5370 if (wWidth == 0xffff)
5371 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5374 dwViewRect = MAKELONG(wWidth, wHeight);
5376 else if (infoPtr->uView == LV_VIEW_ICON)
5378 UINT rows,cols;
5379 UINT nItemWidth;
5380 UINT nItemHeight;
5382 nItemWidth = infoPtr->iconSpacing.cx;
5383 nItemHeight = infoPtr->iconSpacing.cy;
5385 if (wWidth == 0xffff)
5386 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5388 if (wWidth < nItemWidth)
5389 wWidth = nItemWidth;
5391 cols = wWidth / nItemWidth;
5392 if (cols > nItemCount)
5393 cols = nItemCount;
5394 if (cols < 1)
5395 cols = 1;
5397 if (nItemCount)
5399 rows = nItemCount / cols;
5400 if (nItemCount % cols)
5401 rows++;
5403 else
5404 rows = 0;
5406 wHeight = (nItemHeight * rows)+2;
5407 wWidth = (nItemWidth * cols)+2;
5409 dwViewRect = MAKELONG(wWidth, wHeight);
5411 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5412 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5414 return dwViewRect;
5417 /***
5418 * DESCRIPTION:
5419 * Cancel edit label with saving item text.
5421 * PARAMETER(S):
5422 * [I] infoPtr : valid pointer to the listview structure
5424 * RETURN:
5425 * Always returns TRUE.
5427 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5429 if (infoPtr->hwndEdit)
5431 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5432 HWND edit = infoPtr->hwndEdit;
5434 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5435 SendMessageW(edit, WM_CLOSE, 0, 0);
5438 return TRUE;
5441 /***
5442 * DESCRIPTION:
5443 * Create a drag image list for the specified item.
5445 * PARAMETER(S):
5446 * [I] infoPtr : valid pointer to the listview structure
5447 * [I] iItem : index of item
5448 * [O] lppt : Upper-left corner of the image
5450 * RETURN:
5451 * Returns a handle to the image list if successful, NULL otherwise.
5453 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5455 RECT rcItem;
5456 SIZE size;
5457 POINT pos;
5458 HDC hdc, hdcOrig;
5459 HBITMAP hbmp, hOldbmp;
5460 HFONT hOldFont;
5461 HIMAGELIST dragList = 0;
5462 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5464 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5465 return 0;
5467 rcItem.left = LVIR_BOUNDS;
5468 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5469 return 0;
5471 lppt->x = rcItem.left;
5472 lppt->y = rcItem.top;
5474 size.cx = rcItem.right - rcItem.left;
5475 size.cy = rcItem.bottom - rcItem.top;
5477 hdcOrig = GetDC(infoPtr->hwndSelf);
5478 hdc = CreateCompatibleDC(hdcOrig);
5479 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5480 hOldbmp = SelectObject(hdc, hbmp);
5481 hOldFont = SelectObject(hdc, infoPtr->hFont);
5483 SetRect(&rcItem, 0, 0, size.cx, size.cy);
5484 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5486 pos.x = pos.y = 0;
5487 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, NULL, pos, CDRF_DODEFAULT))
5489 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5490 SelectObject(hdc, hOldbmp);
5491 ImageList_Add(dragList, hbmp, 0);
5493 else
5494 SelectObject(hdc, hOldbmp);
5496 SelectObject(hdc, hOldFont);
5497 DeleteObject(hbmp);
5498 DeleteDC(hdc);
5499 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5501 TRACE("ret=%p\n", dragList);
5503 return dragList;
5507 /***
5508 * DESCRIPTION:
5509 * Removes all listview items and subitems.
5511 * PARAMETER(S):
5512 * [I] infoPtr : valid pointer to the listview structure
5514 * RETURN:
5515 * SUCCESS : TRUE
5516 * FAILURE : FALSE
5518 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5520 HDPA hdpaSubItems = NULL;
5521 BOOL suppress = FALSE;
5522 ITEMHDR *hdrItem;
5523 ITEM_INFO *lpItem;
5524 ITEM_ID *lpID;
5525 INT i, j;
5527 TRACE("()\n");
5529 /* we do it directly, to avoid notifications */
5530 ranges_clear(infoPtr->selectionRanges);
5531 infoPtr->nSelectionMark = -1;
5532 infoPtr->nFocusedItem = -1;
5533 SetRectEmpty(&infoPtr->rcFocus);
5534 /* But we are supposed to leave nHotItem as is! */
5536 /* send LVN_DELETEALLITEMS notification */
5537 if (!(infoPtr->dwStyle & LVS_OWNERDATA) || !destroy)
5539 NMLISTVIEW nmlv;
5541 memset(&nmlv, 0, sizeof(NMLISTVIEW));
5542 nmlv.iItem = -1;
5543 suppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5546 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5548 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5550 /* send LVN_DELETEITEM notification, if not suppressed
5551 and if it is not a virtual listview */
5552 if (!suppress) notify_deleteitem(infoPtr, i);
5553 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5554 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5555 /* free id struct */
5556 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5557 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5558 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5559 Free(lpID);
5560 /* both item and subitem start with ITEMHDR header */
5561 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5563 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5564 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5565 Free(hdrItem);
5567 DPA_Destroy(hdpaSubItems);
5568 DPA_DeletePtr(infoPtr->hdpaItems, i);
5570 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5571 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5572 infoPtr->nItemCount --;
5575 if (!destroy)
5577 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5578 LISTVIEW_UpdateScroll(infoPtr);
5580 LISTVIEW_InvalidateList(infoPtr);
5582 return TRUE;
5585 /***
5586 * DESCRIPTION:
5587 * Scrolls, and updates the columns, when a column is changing width.
5589 * PARAMETER(S):
5590 * [I] infoPtr : valid pointer to the listview structure
5591 * [I] nColumn : column to scroll
5592 * [I] dx : amount of scroll, in pixels
5594 * RETURN:
5595 * None.
5597 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5599 COLUMN_INFO *lpColumnInfo;
5600 RECT rcOld, rcCol;
5601 POINT ptOrigin;
5602 INT nCol;
5603 HDITEMW hdi;
5605 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5606 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5607 rcCol = lpColumnInfo->rcHeader;
5608 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5609 rcCol.left = rcCol.right;
5611 /* adjust the other columns */
5612 hdi.mask = HDI_ORDER;
5613 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5615 INT nOrder = hdi.iOrder;
5616 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5618 hdi.mask = HDI_ORDER;
5619 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5620 if (hdi.iOrder >= nOrder) {
5621 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5622 lpColumnInfo->rcHeader.left += dx;
5623 lpColumnInfo->rcHeader.right += dx;
5628 /* do not update screen if not in report mode */
5629 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5631 /* Need to reset the item width when inserting a new column */
5632 infoPtr->nItemWidth += dx;
5634 LISTVIEW_UpdateScroll(infoPtr);
5635 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5637 /* scroll to cover the deleted column, and invalidate for redraw */
5638 rcOld = infoPtr->rcList;
5639 rcOld.left = ptOrigin.x + rcCol.left + dx;
5640 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5643 /***
5644 * DESCRIPTION:
5645 * Removes a column from the listview control.
5647 * PARAMETER(S):
5648 * [I] infoPtr : valid pointer to the listview structure
5649 * [I] nColumn : column index
5651 * RETURN:
5652 * SUCCESS : TRUE
5653 * FAILURE : FALSE
5655 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5657 RECT rcCol;
5659 TRACE("nColumn=%d\n", nColumn);
5661 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5662 return FALSE;
5664 /* While the MSDN specifically says that column zero should not be deleted,
5665 what actually happens is that the column itself is deleted but no items or subitems
5666 are removed.
5669 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5671 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5672 return FALSE;
5674 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5675 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5677 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5679 SUBITEM_INFO *lpSubItem, *lpDelItem;
5680 HDPA hdpaSubItems;
5681 INT nItem, nSubItem, i;
5683 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5685 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5686 nSubItem = 0;
5687 lpDelItem = 0;
5688 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5690 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5691 if (lpSubItem->iSubItem == nColumn)
5693 nSubItem = i;
5694 lpDelItem = lpSubItem;
5696 else if (lpSubItem->iSubItem > nColumn)
5698 lpSubItem->iSubItem--;
5702 /* if we found our subitem, zap it */
5703 if (nSubItem > 0)
5705 /* free string */
5706 if (is_text(lpDelItem->hdr.pszText))
5707 Free(lpDelItem->hdr.pszText);
5709 /* free item */
5710 Free(lpDelItem);
5712 /* free dpa memory */
5713 DPA_DeletePtr(hdpaSubItems, nSubItem);
5718 /* update the other column info */
5719 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5720 LISTVIEW_InvalidateList(infoPtr);
5721 else
5722 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5723 LISTVIEW_UpdateItemSize(infoPtr);
5725 return TRUE;
5728 /***
5729 * DESCRIPTION:
5730 * Invalidates the listview after an item's insertion or deletion.
5732 * PARAMETER(S):
5733 * [I] infoPtr : valid pointer to the listview structure
5734 * [I] nItem : item index
5735 * [I] dir : -1 if deleting, 1 if inserting
5737 * RETURN:
5738 * None
5740 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5742 INT nPerCol, nItemCol, nItemRow;
5743 RECT rcScroll;
5744 POINT Origin;
5746 /* if we don't refresh, what's the point of scrolling? */
5747 if (!is_redrawing(infoPtr)) return;
5749 assert (abs(dir) == 1);
5751 /* arrange icons if autoarrange is on */
5752 if (is_autoarrange(infoPtr))
5754 BOOL arrange = TRUE;
5755 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5756 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5757 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5760 /* scrollbars need updating */
5761 LISTVIEW_UpdateScroll(infoPtr);
5763 /* figure out the item's position */
5764 if (infoPtr->uView == LV_VIEW_DETAILS)
5765 nPerCol = infoPtr->nItemCount + 1;
5766 else if (infoPtr->uView == LV_VIEW_LIST)
5767 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5768 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5769 return;
5771 nItemCol = nItem / nPerCol;
5772 nItemRow = nItem % nPerCol;
5773 LISTVIEW_GetOrigin(infoPtr, &Origin);
5775 /* move the items below up a slot */
5776 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5777 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5778 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5779 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5780 OffsetRect(&rcScroll, Origin.x, Origin.y);
5781 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5782 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5784 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5785 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5788 /* report has only that column, so we're done */
5789 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5791 /* now for LISTs, we have to deal with the columns to the right */
5792 SetRect(&rcScroll, (nItemCol + 1) * infoPtr->nItemWidth, 0,
5793 (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth,
5794 nPerCol * infoPtr->nItemHeight);
5795 OffsetRect(&rcScroll, Origin.x, Origin.y);
5796 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5797 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5800 /***
5801 * DESCRIPTION:
5802 * Removes an item from the listview control.
5804 * PARAMETER(S):
5805 * [I] infoPtr : valid pointer to the listview structure
5806 * [I] nItem : item index
5808 * RETURN:
5809 * SUCCESS : TRUE
5810 * FAILURE : FALSE
5812 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5814 LVITEMW item;
5815 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5816 INT focus = infoPtr->nFocusedItem;
5818 TRACE("(nItem=%d)\n", nItem);
5820 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5822 /* remove selection, and focus */
5823 item.state = 0;
5824 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5825 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5827 /* send LVN_DELETEITEM notification. */
5828 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5830 /* we need to do this here, because we'll be deleting stuff */
5831 if (is_icon)
5832 LISTVIEW_InvalidateItem(infoPtr, nItem);
5834 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5836 HDPA hdpaSubItems;
5837 ITEMHDR *hdrItem;
5838 ITEM_INFO *lpItem;
5839 ITEM_ID *lpID;
5840 INT i;
5842 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5843 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5845 /* free id struct */
5846 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5847 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5848 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5849 Free(lpID);
5850 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5852 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5853 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5854 Free(hdrItem);
5856 DPA_Destroy(hdpaSubItems);
5859 if (is_icon)
5861 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5862 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5865 infoPtr->nItemCount--;
5866 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5867 LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
5869 /* now is the invalidation fun */
5870 if (!is_icon)
5871 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5872 return TRUE;
5876 /***
5877 * DESCRIPTION:
5878 * Callback implementation for editlabel control
5880 * PARAMETER(S):
5881 * [I] infoPtr : valid pointer to the listview structure
5882 * [I] storeText : store edit box text as item text
5883 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5885 * RETURN:
5886 * SUCCESS : TRUE
5887 * FAILURE : FALSE
5889 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5891 HWND hwndSelf = infoPtr->hwndSelf;
5892 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5893 NMLVDISPINFOW dispInfo;
5894 INT editedItem = infoPtr->nEditLabelItem;
5895 BOOL same;
5896 WCHAR *pszText = NULL;
5897 BOOL res;
5899 if (storeText)
5901 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5903 if (len++)
5905 if (!(pszText = Alloc(len * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5906 return FALSE;
5908 if (isW)
5909 GetWindowTextW(infoPtr->hwndEdit, pszText, len);
5910 else
5911 GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len);
5915 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5917 ZeroMemory(&dispInfo, sizeof(dispInfo));
5918 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5919 dispInfo.item.iItem = editedItem;
5920 dispInfo.item.iSubItem = 0;
5921 dispInfo.item.stateMask = ~0;
5922 dispInfo.item.pszText = szDispText;
5923 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5924 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5926 res = FALSE;
5927 goto cleanup;
5930 if (isW)
5931 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5932 else
5934 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5935 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5936 textfreeT(tmp, FALSE);
5939 /* add the text from the edit in */
5940 dispInfo.item.mask |= LVIF_TEXT;
5941 dispInfo.item.pszText = same ? NULL : pszText;
5942 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5944 /* Do we need to update the Item Text */
5945 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5947 infoPtr->nEditLabelItem = -1;
5948 infoPtr->hwndEdit = 0;
5950 if (!res) goto cleanup;
5952 if (!IsWindow(hwndSelf))
5954 res = FALSE;
5955 goto cleanup;
5957 if (!pszText) return TRUE;
5958 if (same)
5960 res = TRUE;
5961 goto cleanup;
5964 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5966 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5967 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5968 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5970 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5971 res = TRUE;
5972 goto cleanup;
5976 ZeroMemory(&dispInfo, sizeof(dispInfo));
5977 dispInfo.item.mask = LVIF_TEXT;
5978 dispInfo.item.iItem = editedItem;
5979 dispInfo.item.iSubItem = 0;
5980 dispInfo.item.pszText = pszText;
5981 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5982 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5984 cleanup:
5985 Free(pszText);
5987 return res;
5990 /***
5991 * DESCRIPTION:
5992 * Subclassed edit control windproc function
5994 * PARAMETER(S):
5995 * [I] hwnd : the edit window handle
5996 * [I] uMsg : the message that is to be processed
5997 * [I] wParam : first message parameter
5998 * [I] lParam : second message parameter
5999 * [I] isW : TRUE if input is Unicode
6001 * RETURN:
6002 * Zero.
6004 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
6006 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
6007 BOOL save = TRUE;
6009 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
6010 hwnd, uMsg, wParam, lParam, isW);
6012 switch (uMsg)
6014 case WM_GETDLGCODE:
6015 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
6017 case WM_DESTROY:
6019 WNDPROC editProc = infoPtr->EditWndProc;
6020 infoPtr->EditWndProc = 0;
6021 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
6022 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
6025 case WM_KEYDOWN:
6026 if (VK_ESCAPE == (INT)wParam)
6028 save = FALSE;
6029 break;
6031 else if (VK_RETURN == (INT)wParam)
6032 break;
6034 default:
6035 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
6038 /* kill the edit */
6039 if (infoPtr->hwndEdit)
6040 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
6042 SendMessageW(hwnd, WM_CLOSE, 0, 0);
6043 return 0;
6046 /***
6047 * DESCRIPTION:
6048 * Subclassed edit control Unicode windproc function
6050 * PARAMETER(S):
6051 * [I] hwnd : the edit window handle
6052 * [I] uMsg : the message that is to be processed
6053 * [I] wParam : first message parameter
6054 * [I] lParam : second message parameter
6056 * RETURN:
6058 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6060 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
6063 /***
6064 * DESCRIPTION:
6065 * Subclassed edit control ANSI windproc function
6067 * PARAMETER(S):
6068 * [I] hwnd : the edit window handle
6069 * [I] uMsg : the message that is to be processed
6070 * [I] wParam : first message parameter
6071 * [I] lParam : second message parameter
6073 * RETURN:
6075 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6077 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
6080 /***
6081 * DESCRIPTION:
6082 * Creates a subclassed edit control
6084 * PARAMETER(S):
6085 * [I] infoPtr : valid pointer to the listview structure
6086 * [I] text : initial text for the edit
6087 * [I] style : the window style
6088 * [I] isW : TRUE if input is Unicode
6090 * RETURN:
6092 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
6094 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
6095 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
6096 HWND hedit;
6098 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
6100 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
6101 if (isW)
6102 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6103 else
6104 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6106 if (!hedit) return 0;
6108 infoPtr->EditWndProc = (WNDPROC)
6109 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
6110 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
6112 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
6113 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
6115 return hedit;
6118 /***
6119 * DESCRIPTION:
6120 * Begin in place editing of specified list view item
6122 * PARAMETER(S):
6123 * [I] infoPtr : valid pointer to the listview structure
6124 * [I] nItem : item index
6125 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
6127 * RETURN:
6128 * SUCCESS : TRUE
6129 * FAILURE : FALSE
6131 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
6133 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
6134 HWND hwndSelf = infoPtr->hwndSelf;
6135 NMLVDISPINFOW dispInfo;
6136 HFONT hOldFont = NULL;
6137 TEXTMETRICW tm;
6138 RECT rect;
6139 SIZE sz;
6140 HDC hdc;
6142 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
6144 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
6146 /* remove existing edit box */
6147 if (infoPtr->hwndEdit)
6149 SetFocus(infoPtr->hwndSelf);
6150 infoPtr->hwndEdit = 0;
6153 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6155 infoPtr->nEditLabelItem = nItem;
6157 LISTVIEW_SetSelection(infoPtr, nItem);
6158 LISTVIEW_SetItemFocus(infoPtr, nItem);
6159 LISTVIEW_InvalidateItem(infoPtr, nItem);
6161 rect.left = LVIR_LABEL;
6162 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6164 ZeroMemory(&dispInfo, sizeof(dispInfo));
6165 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6166 dispInfo.item.iItem = nItem;
6167 dispInfo.item.iSubItem = 0;
6168 dispInfo.item.stateMask = ~0;
6169 dispInfo.item.pszText = disptextW;
6170 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6171 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6173 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6174 if (!infoPtr->hwndEdit) return 0;
6176 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6178 if (!IsWindow(hwndSelf))
6179 return 0;
6180 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6181 infoPtr->hwndEdit = 0;
6182 return 0;
6185 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6187 /* position and display edit box */
6188 hdc = GetDC(infoPtr->hwndSelf);
6190 /* select the font to get appropriate metric dimensions */
6191 if (infoPtr->hFont)
6192 hOldFont = SelectObject(hdc, infoPtr->hFont);
6194 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6195 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6196 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6198 /* get string length in pixels */
6199 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6201 /* add extra spacing for the next character */
6202 GetTextMetricsW(hdc, &tm);
6203 sz.cx += tm.tmMaxCharWidth * 2;
6205 if (infoPtr->hFont)
6206 SelectObject(hdc, hOldFont);
6208 ReleaseDC(infoPtr->hwndSelf, hdc);
6210 sz.cy = rect.bottom - rect.top + 2;
6211 rect.left -= 2;
6212 rect.top -= 1;
6213 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6214 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6215 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6216 SetFocus(infoPtr->hwndEdit);
6217 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6218 return infoPtr->hwndEdit;
6222 /***
6223 * DESCRIPTION:
6224 * Ensures the specified item is visible, scrolling into view if necessary.
6226 * PARAMETER(S):
6227 * [I] infoPtr : valid pointer to the listview structure
6228 * [I] nItem : item index
6229 * [I] bPartial : partially or entirely visible
6231 * RETURN:
6232 * SUCCESS : TRUE
6233 * FAILURE : FALSE
6235 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6237 INT nScrollPosHeight = 0;
6238 INT nScrollPosWidth = 0;
6239 INT nHorzAdjust = 0;
6240 INT nVertAdjust = 0;
6241 INT nHorzDiff = 0;
6242 INT nVertDiff = 0;
6243 RECT rcItem, rcTemp;
6245 rcItem.left = LVIR_BOUNDS;
6246 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6248 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6250 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6252 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6253 if (infoPtr->uView == LV_VIEW_LIST)
6254 nScrollPosWidth = infoPtr->nItemWidth;
6255 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6256 nScrollPosWidth = 1;
6258 if (rcItem.left < infoPtr->rcList.left)
6260 nHorzAdjust = -1;
6261 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6263 else
6265 nHorzAdjust = 1;
6266 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6270 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6272 /* scroll up/down, but not in LVS_LIST mode */
6273 if (infoPtr->uView == LV_VIEW_DETAILS)
6274 nScrollPosHeight = infoPtr->nItemHeight;
6275 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6276 nScrollPosHeight = 1;
6278 if (rcItem.top < infoPtr->rcList.top)
6280 nVertAdjust = -1;
6281 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6283 else
6285 nVertAdjust = 1;
6286 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6290 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6292 if (nScrollPosWidth)
6294 INT diff = nHorzDiff / nScrollPosWidth;
6295 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6296 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6299 if (nScrollPosHeight)
6301 INT diff = nVertDiff / nScrollPosHeight;
6302 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6303 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6306 return TRUE;
6309 /***
6310 * DESCRIPTION:
6311 * Searches for an item with specific characteristics.
6313 * PARAMETER(S):
6314 * [I] hwnd : window handle
6315 * [I] nStart : base item index
6316 * [I] lpFindInfo : item information to look for
6318 * RETURN:
6319 * SUCCESS : index of item
6320 * FAILURE : -1
6322 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6323 const LVFINDINFOW *lpFindInfo)
6325 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6326 BOOL bWrap = FALSE, bNearest = FALSE;
6327 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6328 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6329 POINT Position, Destination;
6330 LVITEMW lvItem;
6332 /* Search in virtual listviews should be done by application, not by
6333 listview control, so we just send LVN_ODFINDITEMW and return the result */
6334 if (infoPtr->dwStyle & LVS_OWNERDATA)
6336 NMLVFINDITEMW nmlv;
6338 nmlv.iStart = nStart;
6339 nmlv.lvfi = *lpFindInfo;
6340 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6343 if (!lpFindInfo || nItem < 0) return -1;
6345 lvItem.mask = 0;
6346 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING))
6348 lvItem.mask |= LVIF_TEXT;
6349 lvItem.pszText = szDispText;
6350 lvItem.cchTextMax = DISP_TEXT_SIZE;
6353 if (lpFindInfo->flags & LVFI_WRAP)
6354 bWrap = TRUE;
6356 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6357 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6359 POINT Origin;
6360 RECT rcArea;
6362 LISTVIEW_GetOrigin(infoPtr, &Origin);
6363 Destination.x = lpFindInfo->pt.x - Origin.x;
6364 Destination.y = lpFindInfo->pt.y - Origin.y;
6365 switch(lpFindInfo->vkDirection)
6367 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6368 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6369 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6370 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6371 case VK_HOME: Destination.x = Destination.y = 0; break;
6372 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6373 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6374 case VK_END:
6375 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6376 Destination.x = rcArea.right;
6377 Destination.y = rcArea.bottom;
6378 break;
6379 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6381 bNearest = TRUE;
6383 else Destination.x = Destination.y = 0;
6385 /* if LVFI_PARAM is specified, all other flags are ignored */
6386 if (lpFindInfo->flags & LVFI_PARAM)
6388 lvItem.mask |= LVIF_PARAM;
6389 bNearest = FALSE;
6390 lvItem.mask &= ~LVIF_TEXT;
6393 nItem = bNearest ? -1 : nStart + 1;
6395 again:
6396 for (; nItem < nLast; nItem++)
6398 lvItem.iItem = nItem;
6399 lvItem.iSubItem = 0;
6400 lvItem.pszText = szDispText;
6401 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6403 if (lvItem.mask & LVIF_PARAM)
6405 if (lpFindInfo->lParam == lvItem.lParam)
6406 return nItem;
6407 else
6408 continue;
6411 if (lvItem.mask & LVIF_TEXT)
6413 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6415 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6416 if (!p || p != lvItem.pszText) continue;
6418 else
6420 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6424 if (!bNearest) return nItem;
6426 /* This is very inefficient. To do a good job here,
6427 * we need a sorted array of (x,y) item positions */
6428 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6430 /* compute the distance^2 to the destination */
6431 xdist = Destination.x - Position.x;
6432 ydist = Destination.y - Position.y;
6433 dist = xdist * xdist + ydist * ydist;
6435 /* remember the distance, and item if it's closer */
6436 if (dist < mindist)
6438 mindist = dist;
6439 nNearestItem = nItem;
6443 if (bWrap)
6445 nItem = 0;
6446 nLast = min(nStart + 1, infoPtr->nItemCount);
6447 bWrap = FALSE;
6448 goto again;
6451 return nNearestItem;
6454 /***
6455 * DESCRIPTION:
6456 * Searches for an item with specific characteristics.
6458 * PARAMETER(S):
6459 * [I] hwnd : window handle
6460 * [I] nStart : base item index
6461 * [I] lpFindInfo : item information to look for
6463 * RETURN:
6464 * SUCCESS : index of item
6465 * FAILURE : -1
6467 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6468 const LVFINDINFOA *lpFindInfo)
6470 LVFINDINFOW fiw;
6471 INT res;
6472 LPWSTR strW = NULL;
6474 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6475 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING))
6476 fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6477 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6478 textfreeT(strW, FALSE);
6479 return res;
6482 /***
6483 * DESCRIPTION:
6484 * Retrieves column attributes.
6486 * PARAMETER(S):
6487 * [I] infoPtr : valid pointer to the listview structure
6488 * [I] nColumn : column index
6489 * [IO] lpColumn : column information
6490 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6491 * otherwise it is in fact a LPLVCOLUMNA
6493 * RETURN:
6494 * SUCCESS : TRUE
6495 * FAILURE : FALSE
6497 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6499 COLUMN_INFO *lpColumnInfo;
6500 HDITEMW hdi;
6502 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6503 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6505 /* initialize memory */
6506 ZeroMemory(&hdi, sizeof(hdi));
6508 if (lpColumn->mask & LVCF_TEXT)
6510 hdi.mask |= HDI_TEXT;
6511 hdi.pszText = lpColumn->pszText;
6512 hdi.cchTextMax = lpColumn->cchTextMax;
6515 if (lpColumn->mask & LVCF_IMAGE)
6516 hdi.mask |= HDI_IMAGE;
6518 if (lpColumn->mask & LVCF_ORDER)
6519 hdi.mask |= HDI_ORDER;
6521 if (lpColumn->mask & LVCF_SUBITEM)
6522 hdi.mask |= HDI_LPARAM;
6524 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6526 if (lpColumn->mask & LVCF_FMT)
6527 lpColumn->fmt = lpColumnInfo->fmt;
6529 if (lpColumn->mask & LVCF_WIDTH)
6530 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6532 if (lpColumn->mask & LVCF_IMAGE)
6533 lpColumn->iImage = hdi.iImage;
6535 if (lpColumn->mask & LVCF_ORDER)
6536 lpColumn->iOrder = hdi.iOrder;
6538 if (lpColumn->mask & LVCF_SUBITEM)
6539 lpColumn->iSubItem = hdi.lParam;
6541 if (lpColumn->mask & LVCF_MINWIDTH)
6542 lpColumn->cxMin = lpColumnInfo->cxMin;
6544 return TRUE;
6547 static inline BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6549 if (!infoPtr->hwndHeader) return FALSE;
6550 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6553 /***
6554 * DESCRIPTION:
6555 * Retrieves the column width.
6557 * PARAMETER(S):
6558 * [I] infoPtr : valid pointer to the listview structure
6559 * [I] int : column index
6561 * RETURN:
6562 * SUCCESS : column width
6563 * FAILURE : zero
6565 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6567 INT nColumnWidth = 0;
6568 HDITEMW hdItem;
6570 TRACE("nColumn=%d\n", nColumn);
6572 /* we have a 'column' in LIST and REPORT mode only */
6573 switch(infoPtr->uView)
6575 case LV_VIEW_LIST:
6576 nColumnWidth = infoPtr->nItemWidth;
6577 break;
6578 case LV_VIEW_DETAILS:
6579 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6580 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6581 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6583 hdItem.mask = HDI_WIDTH;
6584 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6586 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6587 return 0;
6589 nColumnWidth = hdItem.cxy;
6590 break;
6593 TRACE("nColumnWidth=%d\n", nColumnWidth);
6594 return nColumnWidth;
6597 /***
6598 * DESCRIPTION:
6599 * In list or report display mode, retrieves the number of items that can fit
6600 * vertically in the visible area. In icon or small icon display mode,
6601 * retrieves the total number of visible items.
6603 * PARAMETER(S):
6604 * [I] infoPtr : valid pointer to the listview structure
6606 * RETURN:
6607 * Number of fully visible items.
6609 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6611 switch (infoPtr->uView)
6613 case LV_VIEW_ICON:
6614 case LV_VIEW_SMALLICON:
6615 return infoPtr->nItemCount;
6616 case LV_VIEW_DETAILS:
6617 return LISTVIEW_GetCountPerColumn(infoPtr);
6618 case LV_VIEW_LIST:
6619 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6621 assert(FALSE);
6622 return 0;
6625 /***
6626 * DESCRIPTION:
6627 * Retrieves an image list handle.
6629 * PARAMETER(S):
6630 * [I] infoPtr : valid pointer to the listview structure
6631 * [I] nImageList : image list identifier
6633 * RETURN:
6634 * SUCCESS : image list handle
6635 * FAILURE : NULL
6637 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6639 switch (nImageList)
6641 case LVSIL_NORMAL: return infoPtr->himlNormal;
6642 case LVSIL_SMALL: return infoPtr->himlSmall;
6643 case LVSIL_STATE: return infoPtr->himlState;
6644 case LVSIL_GROUPHEADER:
6645 FIXME("LVSIL_GROUPHEADER not supported\n");
6646 break;
6647 default:
6648 WARN("got unknown imagelist index - %d\n", nImageList);
6650 return NULL;
6653 /* LISTVIEW_GetISearchString */
6655 /***
6656 * DESCRIPTION:
6657 * Retrieves item attributes.
6659 * PARAMETER(S):
6660 * [I] hwnd : window handle
6661 * [IO] lpLVItem : item info
6662 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6663 * if FALSE, then lpLVItem is a LPLVITEMA.
6665 * NOTE:
6666 * This is the internal 'GetItem' interface -- it tries to
6667 * be smart and avoid text copies, if possible, by modifying
6668 * lpLVItem->pszText to point to the text string. Please note
6669 * that this is not always possible (e.g. OWNERDATA), so on
6670 * entry you *must* supply valid values for pszText, and cchTextMax.
6671 * The only difference to the documented interface is that upon
6672 * return, you should use *only* the lpLVItem->pszText, rather than
6673 * the buffer pointer you provided on input. Most code already does
6674 * that, so it's not a problem.
6675 * For the two cases when the text must be copied (that is,
6676 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6678 * RETURN:
6679 * SUCCESS : TRUE
6680 * FAILURE : FALSE
6682 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6684 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6685 NMLVDISPINFOW dispInfo;
6686 ITEM_INFO *lpItem;
6687 ITEMHDR* pItemHdr;
6688 HDPA hdpaSubItems;
6689 INT isubitem;
6691 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6693 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6694 return FALSE;
6696 if (lpLVItem->mask == 0) return TRUE;
6697 TRACE("mask=%x\n", lpLVItem->mask);
6699 /* make a local copy */
6700 isubitem = lpLVItem->iSubItem;
6702 /* a quick optimization if all we're asked is the focus state
6703 * these queries are worth optimising since they are common,
6704 * and can be answered in constant time, without the heavy accesses */
6705 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6706 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6708 lpLVItem->state = 0;
6709 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6710 lpLVItem->state |= LVIS_FOCUSED;
6711 return TRUE;
6714 ZeroMemory(&dispInfo, sizeof(dispInfo));
6716 /* if the app stores all the data, handle it separately */
6717 if (infoPtr->dwStyle & LVS_OWNERDATA)
6719 dispInfo.item.state = 0;
6721 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6722 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6723 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6725 UINT mask = lpLVItem->mask;
6727 /* NOTE: copy only fields which we _know_ are initialized, some apps
6728 * depend on the uninitialized fields being 0 */
6729 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6730 dispInfo.item.iItem = lpLVItem->iItem;
6731 dispInfo.item.iSubItem = isubitem;
6732 if (lpLVItem->mask & LVIF_TEXT)
6734 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6735 /* reset mask */
6736 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6737 else
6739 dispInfo.item.pszText = lpLVItem->pszText;
6740 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6743 if (lpLVItem->mask & LVIF_STATE)
6744 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6745 /* could be zeroed on LVIF_NORECOMPUTE case */
6746 if (dispInfo.item.mask)
6748 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6749 dispInfo.item.stateMask = lpLVItem->stateMask;
6750 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6752 /* full size structure expected - _WIN32IE >= 0x560 */
6753 *lpLVItem = dispInfo.item;
6755 else if (lpLVItem->mask & LVIF_INDENT)
6757 /* indent member expected - _WIN32IE >= 0x300 */
6758 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6760 else
6762 /* minimal structure expected */
6763 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6765 lpLVItem->mask = mask;
6766 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6770 /* make sure lParam is zeroed out */
6771 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6773 /* callback marked pointer required here */
6774 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6775 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6777 /* we store only a little state, so if we're not asked, we're done */
6778 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6780 /* if focus is handled by us, report it */
6781 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6783 lpLVItem->state &= ~LVIS_FOCUSED;
6784 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6785 lpLVItem->state |= LVIS_FOCUSED;
6788 /* and do the same for selection, if we handle it */
6789 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6791 lpLVItem->state &= ~LVIS_SELECTED;
6792 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6793 lpLVItem->state |= LVIS_SELECTED;
6796 return TRUE;
6799 /* find the item and subitem structures before we proceed */
6800 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6801 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6802 assert (lpItem);
6804 if (isubitem)
6806 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6807 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6808 if (!lpSubItem)
6810 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6811 isubitem = 0;
6814 else
6815 pItemHdr = &lpItem->hdr;
6817 /* Do we need to query the state from the app? */
6818 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6820 dispInfo.item.mask |= LVIF_STATE;
6821 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6824 /* Do we need to enquire about the image? */
6825 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6826 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6828 dispInfo.item.mask |= LVIF_IMAGE;
6829 dispInfo.item.iImage = I_IMAGECALLBACK;
6832 /* Only items support indentation */
6833 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6834 (isubitem == 0))
6836 dispInfo.item.mask |= LVIF_INDENT;
6837 dispInfo.item.iIndent = I_INDENTCALLBACK;
6840 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6841 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6842 !is_text(pItemHdr->pszText))
6844 dispInfo.item.mask |= LVIF_TEXT;
6845 dispInfo.item.pszText = lpLVItem->pszText;
6846 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6847 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6848 *dispInfo.item.pszText = '\0';
6851 /* If we don't have all the requested info, query the application */
6852 if (dispInfo.item.mask)
6854 dispInfo.item.iItem = lpLVItem->iItem;
6855 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6856 dispInfo.item.lParam = lpItem->lParam;
6857 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6858 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6861 /* we should not store values for subitems */
6862 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6864 /* Now, handle the iImage field */
6865 if (dispInfo.item.mask & LVIF_IMAGE)
6867 lpLVItem->iImage = dispInfo.item.iImage;
6868 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6869 pItemHdr->iImage = dispInfo.item.iImage;
6871 else if (lpLVItem->mask & LVIF_IMAGE)
6873 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6874 lpLVItem->iImage = pItemHdr->iImage;
6875 else
6876 lpLVItem->iImage = 0;
6879 /* The pszText field */
6880 if (dispInfo.item.mask & LVIF_TEXT)
6882 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6883 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6885 lpLVItem->pszText = dispInfo.item.pszText;
6887 else if (lpLVItem->mask & LVIF_TEXT)
6889 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6890 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6891 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6894 /* Next is the lParam field */
6895 if (dispInfo.item.mask & LVIF_PARAM)
6897 lpLVItem->lParam = dispInfo.item.lParam;
6898 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6899 lpItem->lParam = dispInfo.item.lParam;
6901 else if (lpLVItem->mask & LVIF_PARAM)
6902 lpLVItem->lParam = lpItem->lParam;
6904 /* if this is a subitem, we're done */
6905 if (isubitem) return TRUE;
6907 /* ... the state field (this one is different due to uCallbackmask) */
6908 if (lpLVItem->mask & LVIF_STATE)
6910 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6911 if (dispInfo.item.mask & LVIF_STATE)
6913 lpLVItem->state &= ~dispInfo.item.stateMask;
6914 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6916 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6918 lpLVItem->state &= ~LVIS_FOCUSED;
6919 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6920 lpLVItem->state |= LVIS_FOCUSED;
6922 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6924 lpLVItem->state &= ~LVIS_SELECTED;
6925 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6926 lpLVItem->state |= LVIS_SELECTED;
6930 /* and last, but not least, the indent field */
6931 if (dispInfo.item.mask & LVIF_INDENT)
6933 lpLVItem->iIndent = dispInfo.item.iIndent;
6934 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6935 lpItem->iIndent = dispInfo.item.iIndent;
6937 else if (lpLVItem->mask & LVIF_INDENT)
6939 lpLVItem->iIndent = lpItem->iIndent;
6942 return TRUE;
6945 /***
6946 * DESCRIPTION:
6947 * Retrieves item attributes.
6949 * PARAMETER(S):
6950 * [I] hwnd : window handle
6951 * [IO] lpLVItem : item info
6952 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6953 * if FALSE, then lpLVItem is a LPLVITEMA.
6955 * NOTE:
6956 * This is the external 'GetItem' interface -- it properly copies
6957 * the text in the provided buffer.
6959 * RETURN:
6960 * SUCCESS : TRUE
6961 * FAILURE : FALSE
6963 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6965 LPWSTR pszText;
6966 BOOL bResult;
6968 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6969 return FALSE;
6971 pszText = lpLVItem->pszText;
6972 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6973 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6975 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6976 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6977 else
6978 pszText = LPSTR_TEXTCALLBACKW;
6980 lpLVItem->pszText = pszText;
6982 return bResult;
6986 /***
6987 * DESCRIPTION:
6988 * Retrieves the position (upper-left) of the listview control item.
6989 * Note that for LVS_ICON style, the upper-left is that of the icon
6990 * and not the bounding box.
6992 * PARAMETER(S):
6993 * [I] infoPtr : valid pointer to the listview structure
6994 * [I] nItem : item index
6995 * [O] lpptPosition : coordinate information
6997 * RETURN:
6998 * SUCCESS : TRUE
6999 * FAILURE : FALSE
7001 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
7003 POINT Origin;
7005 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
7007 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7009 LISTVIEW_GetOrigin(infoPtr, &Origin);
7010 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
7012 if (infoPtr->uView == LV_VIEW_ICON)
7014 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7015 lpptPosition->y += ICON_TOP_PADDING;
7017 lpptPosition->x += Origin.x;
7018 lpptPosition->y += Origin.y;
7020 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
7021 return TRUE;
7025 /***
7026 * DESCRIPTION:
7027 * Retrieves the bounding rectangle for a listview control item.
7029 * PARAMETER(S):
7030 * [I] infoPtr : valid pointer to the listview structure
7031 * [I] nItem : item index
7032 * [IO] lprc : bounding rectangle coordinates
7033 * lprc->left specifies the portion of the item for which the bounding
7034 * rectangle will be retrieved.
7036 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
7037 * including the icon and label.
7039 * * For LVS_ICON
7040 * * Experiment shows that native control returns:
7041 * * width = min (48, length of text line)
7042 * * .left = position.x - (width - iconsize.cx)/2
7043 * * .right = .left + width
7044 * * height = #lines of text * ntmHeight + icon height + 8
7045 * * .top = position.y - 2
7046 * * .bottom = .top + height
7047 * * separation between items .y = itemSpacing.cy - height
7048 * * .x = itemSpacing.cx - width
7049 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
7051 * * For LVS_ICON
7052 * * Experiment shows that native control returns:
7053 * * width = iconSize.cx + 16
7054 * * .left = position.x - (width - iconsize.cx)/2
7055 * * .right = .left + width
7056 * * height = iconSize.cy + 4
7057 * * .top = position.y - 2
7058 * * .bottom = .top + height
7059 * * separation between items .y = itemSpacing.cy - height
7060 * * .x = itemSpacing.cx - width
7061 * LVIR_LABEL Returns the bounding rectangle of the item text.
7063 * * For LVS_ICON
7064 * * Experiment shows that native control returns:
7065 * * width = text length
7066 * * .left = position.x - width/2
7067 * * .right = .left + width
7068 * * height = ntmH * linecount + 2
7069 * * .top = position.y + iconSize.cy + 6
7070 * * .bottom = .top + height
7071 * * separation between items .y = itemSpacing.cy - height
7072 * * .x = itemSpacing.cx - width
7073 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
7074 * rectangles, but excludes columns in report view.
7076 * RETURN:
7077 * SUCCESS : TRUE
7078 * FAILURE : FALSE
7080 * NOTES
7081 * Note that the bounding rectangle of the label in the LVS_ICON view depends
7082 * upon whether the window has the focus currently and on whether the item
7083 * is the one with the focus. Ensure that the control's record of which
7084 * item has the focus agrees with the items' records.
7086 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
7088 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7089 BOOL doLabel = TRUE, oversizedBox = FALSE;
7090 POINT Position, Origin;
7091 LVITEMW lvItem;
7092 LONG mode;
7094 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
7096 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7098 LISTVIEW_GetOrigin(infoPtr, &Origin);
7099 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7101 /* Be smart and try to figure out the minimum we have to do */
7102 if (lprc->left == LVIR_ICON) doLabel = FALSE;
7103 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
7104 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
7105 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
7106 oversizedBox = TRUE;
7108 /* get what we need from the item before hand, so we make
7109 * only one request. This can speed up things, if data
7110 * is stored on the app side */
7111 lvItem.mask = 0;
7112 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7113 if (doLabel) lvItem.mask |= LVIF_TEXT;
7114 lvItem.iItem = nItem;
7115 lvItem.iSubItem = 0;
7116 lvItem.pszText = szDispText;
7117 lvItem.cchTextMax = DISP_TEXT_SIZE;
7118 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
7119 /* we got the state already up, simulate it here, to avoid a reget */
7120 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
7122 lvItem.mask |= LVIF_STATE;
7123 lvItem.stateMask = LVIS_FOCUSED;
7124 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
7127 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
7128 lprc->left = LVIR_BOUNDS;
7130 mode = lprc->left;
7131 switch(lprc->left)
7133 case LVIR_ICON:
7134 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7135 break;
7137 case LVIR_LABEL:
7138 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
7139 break;
7141 case LVIR_BOUNDS:
7142 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7143 break;
7145 case LVIR_SELECTBOUNDS:
7146 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
7147 break;
7149 default:
7150 WARN("Unknown value: %d\n", lprc->left);
7151 return FALSE;
7154 if (infoPtr->uView == LV_VIEW_DETAILS)
7156 if (mode != LVIR_BOUNDS)
7157 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7158 Position.y + Origin.y);
7159 else
7160 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7162 else
7163 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7165 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7167 return TRUE;
7170 /***
7171 * DESCRIPTION:
7172 * Retrieves the spacing between listview control items.
7174 * PARAMETER(S):
7175 * [I] infoPtr : valid pointer to the listview structure
7176 * [IO] lprc : rectangle to receive the output
7177 * on input, lprc->top = nSubItem
7178 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7180 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7181 * not only those of the first column.
7183 * RETURN:
7184 * TRUE: success
7185 * FALSE: failure
7187 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7189 RECT rect = { 0, 0, 0, 0 };
7190 POINT origin;
7191 INT y;
7193 if (!lprc) return FALSE;
7195 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7196 /* Subitem of '0' means item itself, and this works for all control view modes */
7197 if (lprc->top == 0)
7198 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7200 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7202 LISTVIEW_GetOrigin(infoPtr, &origin);
7203 /* this works for any item index, no matter if it exists or not */
7204 y = item * infoPtr->nItemHeight + origin.y;
7206 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7208 rect.top = 0;
7209 rect.bottom = infoPtr->nItemHeight;
7211 else
7213 /* Native implementation is broken for this case and garbage is left for left and right fields,
7214 we zero them to get predictable output */
7215 lprc->left = lprc->right = lprc->top = 0;
7216 lprc->bottom = infoPtr->nItemHeight;
7217 OffsetRect(lprc, origin.x, y);
7218 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7219 return TRUE;
7222 switch (lprc->left)
7224 case LVIR_ICON:
7226 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7227 if (infoPtr->himlSmall)
7228 rect.right = rect.left + infoPtr->iconSize.cx;
7229 else
7230 rect.right = rect.left;
7232 rect.bottom = rect.top + infoPtr->iconSize.cy;
7233 break;
7235 case LVIR_LABEL:
7236 case LVIR_BOUNDS:
7237 break;
7239 default:
7240 ERR("Unknown bounds=%d\n", lprc->left);
7241 return FALSE;
7244 OffsetRect(&rect, origin.x, y);
7245 *lprc = rect;
7246 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7248 return TRUE;
7251 /***
7252 * DESCRIPTION:
7253 * Retrieves the spacing between listview control items.
7255 * PARAMETER(S):
7256 * [I] infoPtr : valid pointer to the listview structure
7257 * [I] bSmall : flag for small or large icon
7259 * RETURN:
7260 * Horizontal + vertical spacing
7262 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7264 LONG lResult;
7266 if (!bSmall)
7268 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7270 else
7272 if (infoPtr->uView == LV_VIEW_ICON)
7273 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7274 else
7275 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7277 return lResult;
7280 /***
7281 * DESCRIPTION:
7282 * Retrieves the state of a listview control item.
7284 * PARAMETER(S):
7285 * [I] infoPtr : valid pointer to the listview structure
7286 * [I] nItem : item index
7287 * [I] uMask : state mask
7289 * RETURN:
7290 * State specified by the mask.
7292 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7294 LVITEMW lvItem;
7296 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7298 lvItem.iItem = nItem;
7299 lvItem.iSubItem = 0;
7300 lvItem.mask = LVIF_STATE;
7301 lvItem.stateMask = uMask;
7302 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7304 return lvItem.state & uMask;
7307 /***
7308 * DESCRIPTION:
7309 * Retrieves the text of a listview control item or subitem.
7311 * PARAMETER(S):
7312 * [I] hwnd : window handle
7313 * [I] nItem : item index
7314 * [IO] lpLVItem : item information
7315 * [I] isW : TRUE if lpLVItem is Unicode
7317 * RETURN:
7318 * SUCCESS : string length
7319 * FAILURE : 0
7321 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7323 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7325 lpLVItem->mask = LVIF_TEXT;
7326 lpLVItem->iItem = nItem;
7327 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7329 return textlenT(lpLVItem->pszText, isW);
7332 /***
7333 * DESCRIPTION:
7334 * Searches for an item based on properties + relationships.
7336 * PARAMETER(S):
7337 * [I] infoPtr : valid pointer to the listview structure
7338 * [I] nItem : item index
7339 * [I] uFlags : relationship flag
7341 * RETURN:
7342 * SUCCESS : item index
7343 * FAILURE : -1
7345 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7347 UINT uMask = 0;
7348 LVFINDINFOW lvFindInfo;
7349 INT nCountPerColumn;
7350 INT nCountPerRow;
7351 INT i;
7353 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7354 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7356 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7358 if (uFlags & LVNI_CUT)
7359 uMask |= LVIS_CUT;
7361 if (uFlags & LVNI_DROPHILITED)
7362 uMask |= LVIS_DROPHILITED;
7364 if (uFlags & LVNI_FOCUSED)
7365 uMask |= LVIS_FOCUSED;
7367 if (uFlags & LVNI_SELECTED)
7368 uMask |= LVIS_SELECTED;
7370 /* if we're asked for the focused item, that's only one,
7371 * so it's worth optimizing */
7372 if (uFlags & LVNI_FOCUSED)
7374 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7375 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7378 if (uFlags & LVNI_ABOVE)
7380 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7382 while (nItem >= 0)
7384 nItem--;
7385 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7386 return nItem;
7389 else
7391 /* Special case for autoarrange - move 'til the top of a list */
7392 if (is_autoarrange(infoPtr))
7394 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7395 while (nItem - nCountPerRow >= 0)
7397 nItem -= nCountPerRow;
7398 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7399 return nItem;
7401 return -1;
7403 lvFindInfo.flags = LVFI_NEARESTXY;
7404 lvFindInfo.vkDirection = VK_UP;
7405 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7406 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7408 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7409 return nItem;
7413 else if (uFlags & LVNI_BELOW)
7415 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7417 while (nItem < infoPtr->nItemCount)
7419 nItem++;
7420 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7421 return nItem;
7424 else
7426 /* Special case for autoarrange - move 'til the bottom of a list */
7427 if (is_autoarrange(infoPtr))
7429 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7430 while (nItem + nCountPerRow < infoPtr->nItemCount )
7432 nItem += nCountPerRow;
7433 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7434 return nItem;
7436 return -1;
7438 lvFindInfo.flags = LVFI_NEARESTXY;
7439 lvFindInfo.vkDirection = VK_DOWN;
7440 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7441 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7443 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7444 return nItem;
7448 else if (uFlags & LVNI_TOLEFT)
7450 if (infoPtr->uView == LV_VIEW_LIST)
7452 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7453 while (nItem - nCountPerColumn >= 0)
7455 nItem -= nCountPerColumn;
7456 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7457 return nItem;
7460 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7462 /* Special case for autoarrange - move 'til the beginning of a row */
7463 if (is_autoarrange(infoPtr))
7465 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7466 while (nItem % nCountPerRow > 0)
7468 nItem --;
7469 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7470 return nItem;
7472 return -1;
7474 lvFindInfo.flags = LVFI_NEARESTXY;
7475 lvFindInfo.vkDirection = VK_LEFT;
7476 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7477 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7479 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7480 return nItem;
7484 else if (uFlags & LVNI_TORIGHT)
7486 if (infoPtr->uView == LV_VIEW_LIST)
7488 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7489 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7491 nItem += nCountPerColumn;
7492 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7493 return nItem;
7496 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7498 /* Special case for autoarrange - move 'til the end of a row */
7499 if (is_autoarrange(infoPtr))
7501 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7502 while (nItem % nCountPerRow < nCountPerRow - 1 )
7504 nItem ++;
7505 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7506 return nItem;
7508 return -1;
7510 lvFindInfo.flags = LVFI_NEARESTXY;
7511 lvFindInfo.vkDirection = VK_RIGHT;
7512 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7513 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7515 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7516 return nItem;
7520 else
7522 nItem++;
7524 /* search by index */
7525 for (i = nItem; i < infoPtr->nItemCount; i++)
7527 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7528 return i;
7532 return -1;
7535 /* LISTVIEW_GetNumberOfWorkAreas */
7537 /***
7538 * DESCRIPTION:
7539 * Retrieves the origin coordinates when in icon or small icon display mode.
7541 * PARAMETER(S):
7542 * [I] infoPtr : valid pointer to the listview structure
7543 * [O] lpptOrigin : coordinate information
7545 * RETURN:
7546 * None.
7548 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7550 INT nHorzPos = 0, nVertPos = 0;
7551 SCROLLINFO scrollInfo;
7553 scrollInfo.cbSize = sizeof(SCROLLINFO);
7554 scrollInfo.fMask = SIF_POS;
7556 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7557 nHorzPos = scrollInfo.nPos;
7558 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7559 nVertPos = scrollInfo.nPos;
7561 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7563 lpptOrigin->x = infoPtr->rcList.left;
7564 lpptOrigin->y = infoPtr->rcList.top;
7565 if (infoPtr->uView == LV_VIEW_LIST)
7566 nHorzPos *= infoPtr->nItemWidth;
7567 else if (infoPtr->uView == LV_VIEW_DETAILS)
7568 nVertPos *= infoPtr->nItemHeight;
7570 lpptOrigin->x -= nHorzPos;
7571 lpptOrigin->y -= nVertPos;
7573 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7576 /***
7577 * DESCRIPTION:
7578 * Retrieves the width of a string.
7580 * PARAMETER(S):
7581 * [I] hwnd : window handle
7582 * [I] lpszText : text string to process
7583 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7585 * RETURN:
7586 * SUCCESS : string width (in pixels)
7587 * FAILURE : zero
7589 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7591 SIZE stringSize;
7593 stringSize.cx = 0;
7594 if (is_text(lpszText))
7596 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7597 HDC hdc = GetDC(infoPtr->hwndSelf);
7598 HFONT hOldFont = SelectObject(hdc, hFont);
7600 if (isW)
7601 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7602 else
7603 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7604 SelectObject(hdc, hOldFont);
7605 ReleaseDC(infoPtr->hwndSelf, hdc);
7607 return stringSize.cx;
7610 /***
7611 * DESCRIPTION:
7612 * Determines which listview item is located at the specified position.
7614 * PARAMETER(S):
7615 * [I] infoPtr : valid pointer to the listview structure
7616 * [IO] lpht : hit test information
7617 * [I] subitem : fill out iSubItem.
7618 * [I] select : return the index only if the hit selects the item
7620 * NOTE:
7621 * (mm 20001022): We must not allow iSubItem to be touched, for
7622 * an app might pass only a structure with space up to iItem!
7623 * (MS Office 97 does that for instance in the file open dialog)
7625 * RETURN:
7626 * SUCCESS : item index
7627 * FAILURE : -1
7629 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7631 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7632 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7633 POINT Origin, Position, opt;
7634 BOOL is_fullrow;
7635 LVITEMW lvItem;
7636 ITERATOR i;
7637 INT iItem;
7639 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7641 lpht->flags = 0;
7642 lpht->iItem = -1;
7643 if (subitem) lpht->iSubItem = 0;
7645 LISTVIEW_GetOrigin(infoPtr, &Origin);
7647 /* set whole list relation flags */
7648 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7650 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7651 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7652 lpht->flags |= LVHT_TOLEFT;
7654 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7655 opt.y = lpht->pt.y + infoPtr->rcList.top;
7656 else
7657 opt.y = lpht->pt.y;
7659 if (infoPtr->rcList.bottom < opt.y)
7660 lpht->flags |= LVHT_BELOW;
7662 else
7664 if (infoPtr->rcList.left > lpht->pt.x)
7665 lpht->flags |= LVHT_TOLEFT;
7666 else if (infoPtr->rcList.right < lpht->pt.x)
7667 lpht->flags |= LVHT_TORIGHT;
7669 if (infoPtr->rcList.top > lpht->pt.y)
7670 lpht->flags |= LVHT_ABOVE;
7671 else if (infoPtr->rcList.bottom < lpht->pt.y)
7672 lpht->flags |= LVHT_BELOW;
7675 /* even if item is invalid try to find subitem */
7676 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7678 RECT *pRect;
7679 INT j;
7681 opt.x = lpht->pt.x - Origin.x;
7683 lpht->iSubItem = -1;
7684 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7686 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7688 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7690 lpht->iSubItem = j;
7691 break;
7694 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7696 /* if we're outside horizontal columns bounds there's nothing to test further */
7697 if (lpht->iSubItem == -1)
7699 lpht->iItem = -1;
7700 lpht->flags = LVHT_NOWHERE;
7701 return -1;
7705 TRACE("lpht->flags=0x%x\n", lpht->flags);
7706 if (lpht->flags) return -1;
7708 lpht->flags |= LVHT_NOWHERE;
7710 /* first deal with the large items */
7711 rcSearch.left = lpht->pt.x;
7712 rcSearch.top = lpht->pt.y;
7713 rcSearch.right = rcSearch.left + 1;
7714 rcSearch.bottom = rcSearch.top + 1;
7716 iterator_frameditems(&i, infoPtr, &rcSearch);
7717 iterator_next(&i); /* go to first item in the sequence */
7718 iItem = i.nItem;
7719 iterator_destroy(&i);
7721 TRACE("lpht->iItem=%d\n", iItem);
7722 if (iItem == -1) return -1;
7724 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7725 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7726 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7727 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7728 lvItem.iItem = iItem;
7729 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7730 lvItem.pszText = szDispText;
7731 lvItem.cchTextMax = DISP_TEXT_SIZE;
7732 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7733 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7735 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7736 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7737 opt.x = lpht->pt.x - Position.x - Origin.x;
7739 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7740 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7741 else
7742 opt.y = lpht->pt.y - Position.y - Origin.y;
7744 if (infoPtr->uView == LV_VIEW_DETAILS)
7746 rcBounds = rcBox;
7747 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7748 opt.x = lpht->pt.x - Origin.x;
7750 else
7752 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7753 UnionRect(&rcBounds, &rcBounds, &rcState);
7755 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7756 if (!PtInRect(&rcBounds, opt)) return -1;
7758 /* That's a special case - row rectangle is used as item rectangle and
7759 returned flags contain all item parts. */
7760 is_fullrow = (infoPtr->uView == LV_VIEW_DETAILS) && ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED));
7762 if (PtInRect(&rcIcon, opt))
7763 lpht->flags |= LVHT_ONITEMICON;
7764 else if (PtInRect(&rcLabel, opt))
7765 lpht->flags |= LVHT_ONITEMLABEL;
7766 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7767 lpht->flags |= LVHT_ONITEMSTATEICON;
7768 if (is_fullrow && !(lpht->flags & LVHT_ONITEM))
7770 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7772 if (lpht->flags & LVHT_ONITEM)
7773 lpht->flags &= ~LVHT_NOWHERE;
7774 TRACE("lpht->flags=0x%x\n", lpht->flags);
7776 if (select && !is_fullrow)
7778 if (infoPtr->uView == LV_VIEW_DETAILS)
7780 /* get main item bounds */
7781 lvItem.iSubItem = 0;
7782 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7783 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7784 UnionRect(&rcBounds, &rcBounds, &rcState);
7786 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7788 return lpht->iItem = iItem;
7791 /***
7792 * DESCRIPTION:
7793 * Inserts a new item in the listview control.
7795 * PARAMETER(S):
7796 * [I] infoPtr : valid pointer to the listview structure
7797 * [I] lpLVItem : item information
7798 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7800 * RETURN:
7801 * SUCCESS : new item index
7802 * FAILURE : -1
7804 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7806 INT nItem;
7807 HDPA hdpaSubItems;
7808 NMLISTVIEW nmlv;
7809 ITEM_INFO *lpItem;
7810 ITEM_ID *lpID;
7811 BOOL is_sorted, has_changed;
7812 LVITEMW item;
7813 HWND hwndSelf = infoPtr->hwndSelf;
7815 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7817 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7819 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7820 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7822 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7824 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7826 /* insert item in listview control data structure */
7827 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7828 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7830 /* link with id struct */
7831 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7832 lpItem->id = lpID;
7833 lpID->item = hdpaSubItems;
7834 lpID->id = get_next_itemid(infoPtr);
7835 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7837 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7838 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7840 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7842 /* calculate new item index */
7843 if (is_sorted)
7845 HDPA hItem;
7846 ITEM_INFO *item_s;
7847 INT i = 0, cmpv;
7848 WCHAR *textW;
7850 textW = textdupTtoW(lpLVItem->pszText, isW);
7852 while (i < infoPtr->nItemCount)
7854 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7855 item_s = DPA_GetPtr(hItem, 0);
7857 cmpv = textcmpWT(item_s->hdr.pszText, textW, TRUE);
7858 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7860 if (cmpv >= 0) break;
7861 i++;
7864 textfreeT(textW, isW);
7866 nItem = i;
7868 else
7869 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7871 TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7872 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7873 if (nItem == -1) goto fail;
7874 infoPtr->nItemCount++;
7876 /* shift indices first so they don't get tangled */
7877 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7879 /* set the item attributes */
7880 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7882 /* full size structure expected - _WIN32IE >= 0x560 */
7883 item = *lpLVItem;
7885 else if (lpLVItem->mask & LVIF_INDENT)
7887 /* indent member expected - _WIN32IE >= 0x300 */
7888 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7890 else
7892 /* minimal structure expected */
7893 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7895 item.iItem = nItem;
7896 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7898 if (item.mask & LVIF_STATE)
7900 item.stateMask |= LVIS_STATEIMAGEMASK;
7901 item.state &= ~LVIS_STATEIMAGEMASK;
7902 item.state |= INDEXTOSTATEIMAGEMASK(1);
7904 else
7906 item.mask |= LVIF_STATE;
7907 item.stateMask = LVIS_STATEIMAGEMASK;
7908 item.state = INDEXTOSTATEIMAGEMASK(1);
7912 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7914 /* make room for the position, if we are in the right mode */
7915 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7917 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7918 goto undo;
7919 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7921 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7922 goto undo;
7926 /* send LVN_INSERTITEM notification */
7927 memset(&nmlv, 0, sizeof(NMLISTVIEW));
7928 nmlv.iItem = nItem;
7929 nmlv.lParam = lpItem->lParam;
7930 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7931 if (!IsWindow(hwndSelf))
7932 return -1;
7934 /* align items (set position of each item) */
7935 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7937 POINT pt;
7939 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7940 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7941 else
7942 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7944 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7947 /* now is the invalidation fun */
7948 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7949 return nItem;
7951 undo:
7952 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7953 LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
7954 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7955 infoPtr->nItemCount--;
7956 fail:
7957 DPA_DeletePtr(hdpaSubItems, 0);
7958 DPA_Destroy (hdpaSubItems);
7959 Free (lpItem);
7960 return -1;
7963 /***
7964 * DESCRIPTION:
7965 * Checks item visibility.
7967 * PARAMETER(S):
7968 * [I] infoPtr : valid pointer to the listview structure
7969 * [I] nFirst : item index to check for
7971 * RETURN:
7972 * Item visible : TRUE
7973 * Item invisible or failure : FALSE
7975 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7977 POINT Origin, Position;
7978 RECT rcItem;
7979 HDC hdc;
7980 BOOL ret;
7982 TRACE("nItem=%d\n", nItem);
7984 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7986 LISTVIEW_GetOrigin(infoPtr, &Origin);
7987 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7988 rcItem.left = Position.x + Origin.x;
7989 rcItem.top = Position.y + Origin.y;
7990 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7991 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7993 hdc = GetDC(infoPtr->hwndSelf);
7994 if (!hdc) return FALSE;
7995 ret = RectVisible(hdc, &rcItem);
7996 ReleaseDC(infoPtr->hwndSelf, hdc);
7998 return ret;
8001 /***
8002 * DESCRIPTION:
8003 * Redraws a range of items.
8005 * PARAMETER(S):
8006 * [I] infoPtr : valid pointer to the listview structure
8007 * [I] nFirst : first item
8008 * [I] nLast : last item
8010 * RETURN:
8011 * SUCCESS : TRUE
8012 * FAILURE : FALSE
8014 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
8016 INT i;
8018 for (i = max(nFirst, 0); i <= min(nLast, infoPtr->nItemCount - 1); i++)
8019 LISTVIEW_InvalidateItem(infoPtr, i);
8021 return TRUE;
8024 /***
8025 * DESCRIPTION:
8026 * Scroll the content of a listview.
8028 * PARAMETER(S):
8029 * [I] infoPtr : valid pointer to the listview structure
8030 * [I] dx : horizontal scroll amount in pixels
8031 * [I] dy : vertical scroll amount in pixels
8033 * RETURN:
8034 * SUCCESS : TRUE
8035 * FAILURE : FALSE
8037 * COMMENTS:
8038 * If the control is in report view (LV_VIEW_DETAILS) the control can
8039 * be scrolled only in line increments. "dy" will be rounded to the
8040 * nearest number of pixels that are a whole line. Ex: if line height
8041 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
8042 * is passed, then the scroll will be 0. (per MSDN 7/2002)
8044 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8046 switch(infoPtr->uView) {
8047 case LV_VIEW_DETAILS:
8048 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
8049 dy /= infoPtr->nItemHeight;
8050 break;
8051 case LV_VIEW_LIST:
8052 if (dy != 0) return FALSE;
8053 break;
8054 default: /* icon */
8055 break;
8058 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
8059 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
8061 return TRUE;
8064 /***
8065 * DESCRIPTION:
8066 * Sets the background color.
8068 * PARAMETER(S):
8069 * [I] infoPtr : valid pointer to the listview structure
8070 * [I] color : background color
8072 * RETURN:
8073 * SUCCESS : TRUE
8074 * FAILURE : FALSE
8076 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8078 TRACE("(color=%x)\n", color);
8080 if(infoPtr->clrBk != color) {
8081 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8082 infoPtr->clrBk = color;
8083 if (color == CLR_NONE)
8084 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
8085 else
8087 infoPtr->hBkBrush = CreateSolidBrush(color);
8088 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
8092 return TRUE;
8095 /* LISTVIEW_SetBkImage */
8097 /*** Helper for {Insert,Set}ColumnT *only* */
8098 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
8099 const LVCOLUMNW *lpColumn, BOOL isW)
8101 if (lpColumn->mask & LVCF_FMT)
8103 /* format member is valid */
8104 lphdi->mask |= HDI_FORMAT;
8106 /* set text alignment (leftmost column must be left-aligned) */
8107 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8108 lphdi->fmt |= HDF_LEFT;
8109 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
8110 lphdi->fmt |= HDF_RIGHT;
8111 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
8112 lphdi->fmt |= HDF_CENTER;
8114 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
8115 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
8117 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
8119 lphdi->fmt |= HDF_IMAGE;
8120 lphdi->iImage = I_IMAGECALLBACK;
8123 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
8124 lphdi->fmt |= HDF_FIXEDWIDTH;
8127 if (lpColumn->mask & LVCF_WIDTH)
8129 lphdi->mask |= HDI_WIDTH;
8130 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
8132 /* make it fill the remainder of the controls width */
8133 RECT rcHeader;
8134 INT item_index;
8136 for(item_index = 0; item_index < (nColumn - 1); item_index++)
8138 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
8139 lphdi->cxy += rcHeader.right - rcHeader.left;
8142 /* retrieve the layout of the header */
8143 GetClientRect(infoPtr->hwndSelf, &rcHeader);
8144 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
8146 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
8148 else
8149 lphdi->cxy = lpColumn->cx;
8152 if (lpColumn->mask & LVCF_TEXT)
8154 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8155 lphdi->fmt |= HDF_STRING;
8156 lphdi->pszText = lpColumn->pszText;
8157 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8160 if (lpColumn->mask & LVCF_IMAGE)
8162 lphdi->mask |= HDI_IMAGE;
8163 lphdi->iImage = lpColumn->iImage;
8166 if (lpColumn->mask & LVCF_ORDER)
8168 lphdi->mask |= HDI_ORDER;
8169 lphdi->iOrder = lpColumn->iOrder;
8174 /***
8175 * DESCRIPTION:
8176 * Inserts a new column.
8178 * PARAMETER(S):
8179 * [I] infoPtr : valid pointer to the listview structure
8180 * [I] nColumn : column index
8181 * [I] lpColumn : column information
8182 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8184 * RETURN:
8185 * SUCCESS : new column index
8186 * FAILURE : -1
8188 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8189 const LVCOLUMNW *lpColumn, BOOL isW)
8191 COLUMN_INFO *lpColumnInfo;
8192 INT nNewColumn;
8193 HDITEMW hdi;
8195 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8197 if (!lpColumn || nColumn < 0) return -1;
8198 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8200 ZeroMemory(&hdi, sizeof(HDITEMW));
8201 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8204 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8205 * (can be seen in SPY) otherwise column never gets added.
8207 if (!(lpColumn->mask & LVCF_WIDTH)) {
8208 hdi.mask |= HDI_WIDTH;
8209 hdi.cxy = 10;
8213 * when the iSubItem is available Windows copies it to the header lParam. It seems
8214 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8216 if (lpColumn->mask & LVCF_SUBITEM)
8218 hdi.mask |= HDI_LPARAM;
8219 hdi.lParam = lpColumn->iSubItem;
8222 /* create header if not present */
8223 LISTVIEW_CreateHeader(infoPtr);
8224 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8225 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8227 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8230 /* insert item in header control */
8231 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8232 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8233 nColumn, (LPARAM)&hdi);
8234 if (nNewColumn == -1) return -1;
8235 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8237 /* create our own column info */
8238 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8239 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8241 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8242 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8243 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8244 goto fail;
8246 /* now we have to actually adjust the data */
8247 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8249 SUBITEM_INFO *lpSubItem;
8250 HDPA hdpaSubItems;
8251 INT nItem, i;
8252 LVITEMW item;
8253 BOOL changed;
8255 item.iSubItem = nNewColumn;
8256 item.mask = LVIF_TEXT | LVIF_IMAGE;
8257 item.iImage = I_IMAGECALLBACK;
8258 item.pszText = LPSTR_TEXTCALLBACKW;
8260 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8262 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8263 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8265 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8266 if (lpSubItem->iSubItem >= nNewColumn)
8267 lpSubItem->iSubItem++;
8270 /* add new subitem for each item */
8271 item.iItem = nItem;
8272 set_sub_item(infoPtr, &item, isW, &changed);
8276 /* make space for the new column */
8277 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8278 LISTVIEW_UpdateItemSize(infoPtr);
8280 return nNewColumn;
8282 fail:
8283 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8284 if (lpColumnInfo)
8286 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8287 Free(lpColumnInfo);
8289 return -1;
8292 /***
8293 * DESCRIPTION:
8294 * Sets the attributes of a header item.
8296 * PARAMETER(S):
8297 * [I] infoPtr : valid pointer to the listview structure
8298 * [I] nColumn : column index
8299 * [I] lpColumn : column attributes
8300 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8302 * RETURN:
8303 * SUCCESS : TRUE
8304 * FAILURE : FALSE
8306 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8307 const LVCOLUMNW *lpColumn, BOOL isW)
8309 HDITEMW hdi, hdiget;
8310 BOOL bResult;
8312 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8314 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8316 ZeroMemory(&hdi, sizeof(HDITEMW));
8317 if (lpColumn->mask & LVCF_FMT)
8319 hdi.mask |= HDI_FORMAT;
8320 hdiget.mask = HDI_FORMAT;
8321 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8322 hdi.fmt = hdiget.fmt & HDF_STRING;
8324 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8326 /* set header item attributes */
8327 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8328 if (!bResult) return FALSE;
8330 if (lpColumn->mask & LVCF_FMT)
8332 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8333 INT oldFmt = lpColumnInfo->fmt;
8335 lpColumnInfo->fmt = lpColumn->fmt;
8336 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8338 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8342 if (lpColumn->mask & LVCF_MINWIDTH)
8343 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8345 return TRUE;
8348 /***
8349 * DESCRIPTION:
8350 * Sets the column order array
8352 * PARAMETERS:
8353 * [I] infoPtr : valid pointer to the listview structure
8354 * [I] iCount : number of elements in column order array
8355 * [I] lpiArray : pointer to column order array
8357 * RETURN:
8358 * SUCCESS : TRUE
8359 * FAILURE : FALSE
8361 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8363 if (!infoPtr->hwndHeader) return FALSE;
8364 infoPtr->colRectsDirty = TRUE;
8365 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8368 /***
8369 * DESCRIPTION:
8370 * Sets the width of a column
8372 * PARAMETERS:
8373 * [I] infoPtr : valid pointer to the listview structure
8374 * [I] nColumn : column index
8375 * [I] cx : column width
8377 * RETURN:
8378 * SUCCESS : TRUE
8379 * FAILURE : FALSE
8381 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8383 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8384 INT max_cx = 0;
8385 HDITEMW hdi;
8387 TRACE("(nColumn=%d, cx=%d)\n", nColumn, cx);
8389 /* set column width only if in report or list mode */
8390 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8392 /* take care of invalid cx values - LVSCW_AUTOSIZE_* values are negative,
8393 with _USEHEADER being the lowest */
8394 if (infoPtr->uView == LV_VIEW_DETAILS && cx < LVSCW_AUTOSIZE_USEHEADER) cx = LVSCW_AUTOSIZE;
8395 else if (infoPtr->uView == LV_VIEW_LIST && cx <= 0) return FALSE;
8397 /* resize all columns if in LV_VIEW_LIST mode */
8398 if(infoPtr->uView == LV_VIEW_LIST)
8400 infoPtr->nItemWidth = cx;
8401 LISTVIEW_InvalidateList(infoPtr);
8402 return TRUE;
8405 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8407 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8409 INT nLabelWidth;
8410 LVITEMW lvItem;
8412 lvItem.mask = LVIF_TEXT;
8413 lvItem.iItem = 0;
8414 lvItem.iSubItem = nColumn;
8415 lvItem.cchTextMax = DISP_TEXT_SIZE;
8416 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8418 lvItem.pszText = szDispText;
8419 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8420 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8421 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8423 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8424 max_cx += infoPtr->iconSize.cx;
8425 max_cx += TRAILING_LABEL_PADDING;
8428 /* autosize based on listview items width */
8429 if(cx == LVSCW_AUTOSIZE)
8430 cx = max_cx;
8431 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8433 /* if iCol is the last column make it fill the remainder of the controls width */
8434 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8436 RECT rcHeader;
8437 POINT Origin;
8439 LISTVIEW_GetOrigin(infoPtr, &Origin);
8440 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8442 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8444 else
8446 /* Despite what the MS docs say, if this is not the last
8447 column, then MS resizes the column to the width of the
8448 largest text string in the column, including headers
8449 and items. This is different from LVSCW_AUTOSIZE in that
8450 LVSCW_AUTOSIZE ignores the header string length. */
8451 cx = 0;
8453 /* retrieve header text */
8454 hdi.mask = HDI_TEXT|HDI_FORMAT|HDI_IMAGE|HDI_BITMAP;
8455 hdi.cchTextMax = DISP_TEXT_SIZE;
8456 hdi.pszText = szDispText;
8457 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8459 HDC hdc = GetDC(infoPtr->hwndSelf);
8460 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8461 HIMAGELIST himl = (HIMAGELIST)SendMessageW(infoPtr->hwndHeader, HDM_GETIMAGELIST, 0, 0);
8462 INT bitmap_margin = 0;
8463 SIZE size;
8465 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8466 cx = size.cx + TRAILING_HEADER_PADDING;
8468 if (hdi.fmt & (HDF_IMAGE|HDF_BITMAP))
8469 bitmap_margin = SendMessageW(infoPtr->hwndHeader, HDM_GETBITMAPMARGIN, 0, 0);
8471 if ((hdi.fmt & HDF_IMAGE) && himl)
8473 INT icon_cx, icon_cy;
8475 if (!ImageList_GetIconSize(himl, &icon_cx, &icon_cy))
8476 cx += icon_cx + 2*bitmap_margin;
8478 else if (hdi.fmt & HDF_BITMAP)
8480 BITMAP bmp;
8482 GetObjectW(hdi.hbm, sizeof(BITMAP), &bmp);
8483 cx += bmp.bmWidth + 2*bitmap_margin;
8486 SelectObject(hdc, old_font);
8487 ReleaseDC(infoPtr->hwndSelf, hdc);
8489 cx = max (cx, max_cx);
8493 if (cx < 0) return FALSE;
8495 /* call header to update the column change */
8496 hdi.mask = HDI_WIDTH;
8497 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8498 TRACE("hdi.cxy=%d\n", hdi.cxy);
8499 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8502 /***
8503 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8506 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8508 HDC hdc_wnd, hdc;
8509 HBITMAP hbm_im, hbm_mask, hbm_orig;
8510 RECT rc;
8511 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8512 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8513 HIMAGELIST himl;
8515 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8516 ILC_COLOR | ILC_MASK, 2, 2);
8517 hdc_wnd = GetDC(infoPtr->hwndSelf);
8518 hdc = CreateCompatibleDC(hdc_wnd);
8519 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8520 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8521 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8523 SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8524 hbm_orig = SelectObject(hdc, hbm_mask);
8525 FillRect(hdc, &rc, hbr_white);
8526 InflateRect(&rc, -2, -2);
8527 FillRect(hdc, &rc, hbr_black);
8529 SelectObject(hdc, hbm_im);
8530 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8531 SelectObject(hdc, hbm_orig);
8532 ImageList_Add(himl, hbm_im, hbm_mask);
8534 SelectObject(hdc, hbm_im);
8535 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8536 SelectObject(hdc, hbm_orig);
8537 ImageList_Add(himl, hbm_im, hbm_mask);
8539 DeleteObject(hbm_mask);
8540 DeleteObject(hbm_im);
8541 DeleteDC(hdc);
8543 return himl;
8546 /***
8547 * DESCRIPTION:
8548 * Sets the extended listview style.
8550 * PARAMETERS:
8551 * [I] infoPtr : valid pointer to the listview structure
8552 * [I] dwMask : mask
8553 * [I] dwStyle : style
8555 * RETURN:
8556 * SUCCESS : previous style
8557 * FAILURE : 0
8559 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8561 DWORD old_ex_style = infoPtr->dwLvExStyle;
8563 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8565 /* set new style */
8566 if (mask)
8567 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8568 else
8569 infoPtr->dwLvExStyle = ex_style;
8571 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8573 HIMAGELIST himl = 0;
8574 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8576 LVITEMW item;
8577 item.mask = LVIF_STATE;
8578 item.stateMask = LVIS_STATEIMAGEMASK;
8579 item.state = INDEXTOSTATEIMAGEMASK(1);
8580 LISTVIEW_SetItemState(infoPtr, -1, &item);
8582 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8583 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8584 ImageList_Destroy(infoPtr->himlState);
8586 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8587 /* checkbox list replaces previous custom list or... */
8588 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8589 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8590 /* ...previous was checkbox list */
8591 (old_ex_style & LVS_EX_CHECKBOXES))
8592 ImageList_Destroy(himl);
8595 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8597 DWORD style;
8599 /* if not already created */
8600 LISTVIEW_CreateHeader(infoPtr);
8602 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8603 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8604 style |= HDS_DRAGDROP;
8605 else
8606 style &= ~HDS_DRAGDROP;
8607 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8610 /* GRIDLINES adds decoration at top so changes sizes */
8611 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8613 LISTVIEW_CreateHeader(infoPtr);
8614 LISTVIEW_UpdateSize(infoPtr);
8617 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8619 LISTVIEW_CreateHeader(infoPtr);
8622 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8624 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8625 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8628 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8630 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8631 LISTVIEW_CreateHeader(infoPtr);
8632 else
8633 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8634 LISTVIEW_UpdateSize(infoPtr);
8635 LISTVIEW_UpdateScroll(infoPtr);
8638 LISTVIEW_InvalidateList(infoPtr);
8639 return old_ex_style;
8642 /***
8643 * DESCRIPTION:
8644 * Sets the new hot cursor used during hot tracking and hover selection.
8646 * PARAMETER(S):
8647 * [I] infoPtr : valid pointer to the listview structure
8648 * [I] hCursor : the new hot cursor handle
8650 * RETURN:
8651 * Returns the previous hot cursor
8653 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8655 HCURSOR oldCursor = infoPtr->hHotCursor;
8657 infoPtr->hHotCursor = hCursor;
8659 return oldCursor;
8663 /***
8664 * DESCRIPTION:
8665 * Sets the hot item index.
8667 * PARAMETERS:
8668 * [I] infoPtr : valid pointer to the listview structure
8669 * [I] iIndex : index
8671 * RETURN:
8672 * SUCCESS : previous hot item index
8673 * FAILURE : -1 (no hot item)
8675 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8677 INT iOldIndex = infoPtr->nHotItem;
8679 infoPtr->nHotItem = iIndex;
8681 return iOldIndex;
8685 /***
8686 * DESCRIPTION:
8687 * Sets the amount of time the cursor must hover over an item before it is selected.
8689 * PARAMETER(S):
8690 * [I] infoPtr : valid pointer to the listview structure
8691 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8693 * RETURN:
8694 * Returns the previous hover time
8696 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8698 DWORD oldHoverTime = infoPtr->dwHoverTime;
8700 infoPtr->dwHoverTime = dwHoverTime;
8702 return oldHoverTime;
8705 /***
8706 * DESCRIPTION:
8707 * Sets spacing for icons of LVS_ICON style.
8709 * PARAMETER(S):
8710 * [I] infoPtr : valid pointer to the listview structure
8711 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8712 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8714 * RETURN:
8715 * MAKELONG(oldcx, oldcy)
8717 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8719 INT iconWidth = 0, iconHeight = 0;
8720 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8722 TRACE("requested=(%d,%d)\n", cx, cy);
8724 /* set to defaults, if instructed to */
8725 if (cx == -1 && cy == -1)
8727 infoPtr->autoSpacing = TRUE;
8728 if (infoPtr->himlNormal)
8729 ImageList_GetIconSize(infoPtr->himlNormal, &iconWidth, &iconHeight);
8730 cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON) + iconWidth;
8731 cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON) + iconHeight;
8733 else
8734 infoPtr->autoSpacing = FALSE;
8736 /* if 0 then keep width */
8737 if (cx != 0)
8738 infoPtr->iconSpacing.cx = cx;
8740 /* if 0 then keep height */
8741 if (cy != 0)
8742 infoPtr->iconSpacing.cy = cy;
8744 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8745 LOWORD(oldspacing), HIWORD(oldspacing), infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy,
8746 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8747 infoPtr->ntmHeight);
8749 /* these depend on the iconSpacing */
8750 LISTVIEW_UpdateItemSize(infoPtr);
8752 return oldspacing;
8755 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8757 INT cx, cy;
8759 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8761 size->cx = cx;
8762 size->cy = cy;
8764 else
8766 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8767 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8771 /***
8772 * DESCRIPTION:
8773 * Sets image lists.
8775 * PARAMETER(S):
8776 * [I] infoPtr : valid pointer to the listview structure
8777 * [I] nType : image list type
8778 * [I] himl : image list handle
8780 * RETURN:
8781 * SUCCESS : old image list
8782 * FAILURE : NULL
8784 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8786 INT oldHeight = infoPtr->nItemHeight;
8787 HIMAGELIST himlOld = 0;
8789 TRACE("(nType=%d, himl=%p)\n", nType, himl);
8791 switch (nType)
8793 case LVSIL_NORMAL:
8794 himlOld = infoPtr->himlNormal;
8795 infoPtr->himlNormal = himl;
8796 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8797 if (infoPtr->autoSpacing)
8798 LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
8799 break;
8801 case LVSIL_SMALL:
8802 himlOld = infoPtr->himlSmall;
8803 infoPtr->himlSmall = himl;
8804 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8805 if (infoPtr->hwndHeader)
8806 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
8807 break;
8809 case LVSIL_STATE:
8810 himlOld = infoPtr->himlState;
8811 infoPtr->himlState = himl;
8812 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8813 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8814 break;
8816 default:
8817 ERR("Unknown icon type=%d\n", nType);
8818 return NULL;
8821 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8822 if (infoPtr->nItemHeight != oldHeight)
8823 LISTVIEW_UpdateScroll(infoPtr);
8825 return himlOld;
8828 /***
8829 * DESCRIPTION:
8830 * Preallocates memory (does *not* set the actual count of items !)
8832 * PARAMETER(S):
8833 * [I] infoPtr : valid pointer to the listview structure
8834 * [I] nItems : item count (projected number of items to allocate)
8835 * [I] dwFlags : update flags
8837 * RETURN:
8838 * SUCCESS : TRUE
8839 * FAILURE : FALSE
8841 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8843 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8845 if (infoPtr->dwStyle & LVS_OWNERDATA)
8847 INT nOldCount = infoPtr->nItemCount;
8848 infoPtr->nItemCount = nItems;
8850 if (nItems < nOldCount)
8852 RANGE range = { nItems, nOldCount };
8853 ranges_del(infoPtr->selectionRanges, range);
8854 if (infoPtr->nFocusedItem >= nItems)
8856 LISTVIEW_SetItemFocus(infoPtr, -1);
8857 SetRectEmpty(&infoPtr->rcFocus);
8861 LISTVIEW_UpdateScroll(infoPtr);
8863 /* the flags are valid only in ownerdata report and list modes */
8864 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8866 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8867 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8869 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8870 LISTVIEW_InvalidateList(infoPtr);
8871 else
8873 INT nFrom, nTo;
8874 POINT Origin;
8875 RECT rcErase;
8877 LISTVIEW_GetOrigin(infoPtr, &Origin);
8878 nFrom = min(nOldCount, nItems);
8879 nTo = max(nOldCount, nItems);
8881 if (infoPtr->uView == LV_VIEW_DETAILS)
8883 SetRect(&rcErase, 0, nFrom * infoPtr->nItemHeight, infoPtr->nItemWidth,
8884 nTo * infoPtr->nItemHeight);
8885 OffsetRect(&rcErase, Origin.x, Origin.y);
8886 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8887 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8889 else /* LV_VIEW_LIST */
8891 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8893 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8894 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8895 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8896 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8897 OffsetRect(&rcErase, Origin.x, Origin.y);
8898 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8899 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8901 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8902 rcErase.top = 0;
8903 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8904 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8905 OffsetRect(&rcErase, Origin.x, Origin.y);
8906 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8907 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8911 else
8913 /* According to MSDN for non-LVS_OWNERDATA this is just
8914 * a performance issue. The control allocates its internal
8915 * data structures for the number of items specified. It
8916 * cuts down on the number of memory allocations. Therefore
8917 * we will just issue a WARN here
8919 WARN("for non-ownerdata performance option not implemented.\n");
8922 return TRUE;
8925 /***
8926 * DESCRIPTION:
8927 * Sets the position of an item.
8929 * PARAMETER(S):
8930 * [I] infoPtr : valid pointer to the listview structure
8931 * [I] nItem : item index
8932 * [I] pt : coordinate
8934 * RETURN:
8935 * SUCCESS : TRUE
8936 * FAILURE : FALSE
8938 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8940 POINT Origin, Pt;
8942 TRACE("(nItem=%d, pt=%s)\n", nItem, wine_dbgstr_point(pt));
8944 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8945 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8947 Pt = *pt;
8948 LISTVIEW_GetOrigin(infoPtr, &Origin);
8950 /* This point value seems to be an undocumented feature.
8951 * The best guess is that it means either at the origin,
8952 * or at true beginning of the list. I will assume the origin. */
8953 if ((Pt.x == -1) && (Pt.y == -1))
8954 Pt = Origin;
8956 if (infoPtr->uView == LV_VIEW_ICON)
8958 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8959 Pt.y -= ICON_TOP_PADDING;
8961 Pt.x -= Origin.x;
8962 Pt.y -= Origin.y;
8964 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8967 /***
8968 * DESCRIPTION:
8969 * Sets the state of one or many items.
8971 * PARAMETER(S):
8972 * [I] infoPtr : valid pointer to the listview structure
8973 * [I] nItem : item index
8974 * [I] item : item or subitem info
8976 * RETURN:
8977 * SUCCESS : TRUE
8978 * FAILURE : FALSE
8980 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8982 BOOL ret = TRUE;
8983 LVITEMW lvItem;
8985 if (!item) return FALSE;
8987 lvItem.iItem = nItem;
8988 lvItem.iSubItem = 0;
8989 lvItem.mask = LVIF_STATE;
8990 lvItem.state = item->state;
8991 lvItem.stateMask = item->stateMask;
8992 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8994 if (nItem == -1)
8996 UINT oldstate = 0;
8997 BOOL notify;
8999 /* special case optimization for recurring attempt to deselect all */
9000 if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
9001 return TRUE;
9003 /* select all isn't allowed in LVS_SINGLESEL */
9004 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
9005 return FALSE;
9007 /* focus all isn't allowed */
9008 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
9010 notify = infoPtr->bDoChangeNotify;
9011 if (infoPtr->dwStyle & LVS_OWNERDATA)
9013 infoPtr->bDoChangeNotify = FALSE;
9014 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
9015 oldstate |= LVIS_SELECTED;
9016 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
9019 /* apply to all items */
9020 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
9021 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
9023 if (infoPtr->dwStyle & LVS_OWNERDATA)
9025 NMLISTVIEW nmlv;
9027 infoPtr->bDoChangeNotify = notify;
9029 nmlv.iItem = -1;
9030 nmlv.iSubItem = 0;
9031 nmlv.uNewState = lvItem.state & lvItem.stateMask;
9032 nmlv.uOldState = oldstate & lvItem.stateMask;
9033 nmlv.uChanged = LVIF_STATE;
9034 nmlv.ptAction.x = nmlv.ptAction.y = 0;
9035 nmlv.lParam = 0;
9037 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
9040 else
9041 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
9043 return ret;
9046 /***
9047 * DESCRIPTION:
9048 * Sets the text of an item or subitem.
9050 * PARAMETER(S):
9051 * [I] hwnd : window handle
9052 * [I] nItem : item index
9053 * [I] lpLVItem : item or subitem info
9054 * [I] isW : TRUE if input is Unicode
9056 * RETURN:
9057 * SUCCESS : TRUE
9058 * FAILURE : FALSE
9060 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
9062 LVITEMW lvItem;
9064 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9065 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9067 lvItem.iItem = nItem;
9068 lvItem.iSubItem = lpLVItem->iSubItem;
9069 lvItem.mask = LVIF_TEXT;
9070 lvItem.pszText = lpLVItem->pszText;
9071 lvItem.cchTextMax = lpLVItem->cchTextMax;
9073 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
9075 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
9078 /***
9079 * DESCRIPTION:
9080 * Set item index that marks the start of a multiple selection.
9082 * PARAMETER(S):
9083 * [I] infoPtr : valid pointer to the listview structure
9084 * [I] nIndex : index
9086 * RETURN:
9087 * Index number or -1 if there is no selection mark.
9089 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
9091 INT nOldIndex = infoPtr->nSelectionMark;
9093 TRACE("(nIndex=%d)\n", nIndex);
9095 if (nIndex >= -1 && nIndex < infoPtr->nItemCount)
9096 infoPtr->nSelectionMark = nIndex;
9098 return nOldIndex;
9101 /***
9102 * DESCRIPTION:
9103 * Sets the text background color.
9105 * PARAMETER(S):
9106 * [I] infoPtr : valid pointer to the listview structure
9107 * [I] color : text background color
9109 * RETURN:
9110 * SUCCESS : TRUE
9111 * FAILURE : FALSE
9113 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
9115 TRACE("(color=%x)\n", color);
9117 infoPtr->clrTextBk = color;
9118 return TRUE;
9121 /***
9122 * DESCRIPTION:
9123 * Sets the text foreground color.
9125 * PARAMETER(S):
9126 * [I] infoPtr : valid pointer to the listview structure
9127 * [I] color : text color
9129 * RETURN:
9130 * SUCCESS : TRUE
9131 * FAILURE : FALSE
9133 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
9135 TRACE("(color=%x)\n", color);
9137 infoPtr->clrText = color;
9138 return TRUE;
9141 /***
9142 * DESCRIPTION:
9143 * Sets new ToolTip window to ListView control.
9145 * PARAMETER(S):
9146 * [I] infoPtr : valid pointer to the listview structure
9147 * [I] hwndNewToolTip : handle to new ToolTip
9149 * RETURN:
9150 * old tool tip
9152 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
9154 HWND hwndOldToolTip = infoPtr->hwndToolTip;
9155 infoPtr->hwndToolTip = hwndNewToolTip;
9156 return hwndOldToolTip;
9160 * DESCRIPTION:
9161 * sets the Unicode character format flag for the control
9162 * PARAMETER(S):
9163 * [I] infoPtr :valid pointer to the listview structure
9164 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9166 * RETURN:
9167 * Old Unicode Format
9169 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9171 SHORT rc = infoPtr->notifyFormat;
9172 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9173 return rc == NFR_UNICODE;
9177 * DESCRIPTION:
9178 * sets the control view mode
9179 * PARAMETER(S):
9180 * [I] infoPtr :valid pointer to the listview structure
9181 * [I] nView :new view mode value
9183 * RETURN:
9184 * SUCCESS: 1
9185 * FAILURE: -1
9187 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9189 HIMAGELIST himl;
9191 if (infoPtr->uView == nView) return 1;
9193 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9194 if (nView == LV_VIEW_TILE)
9196 FIXME("View LV_VIEW_TILE unimplemented\n");
9197 return -1;
9200 infoPtr->uView = nView;
9202 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9203 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9205 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9206 SetRectEmpty(&infoPtr->rcFocus);
9208 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9209 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9211 switch (nView)
9213 case LV_VIEW_ICON:
9214 case LV_VIEW_SMALLICON:
9215 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9216 break;
9217 case LV_VIEW_DETAILS:
9219 HDLAYOUT hl;
9220 WINDOWPOS wp;
9222 LISTVIEW_CreateHeader( infoPtr );
9224 hl.prc = &infoPtr->rcList;
9225 hl.pwpos = &wp;
9226 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9227 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9228 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9229 break;
9231 case LV_VIEW_LIST:
9232 break;
9235 LISTVIEW_UpdateItemSize(infoPtr);
9236 LISTVIEW_UpdateSize(infoPtr);
9237 LISTVIEW_UpdateScroll(infoPtr);
9238 LISTVIEW_InvalidateList(infoPtr);
9240 TRACE("nView=%d\n", nView);
9242 return 1;
9245 /* LISTVIEW_SetWorkAreas */
9247 /***
9248 * DESCRIPTION:
9249 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9251 * PARAMETER(S):
9252 * [I] first : pointer to first ITEM_INFO to compare
9253 * [I] second : pointer to second ITEM_INFO to compare
9254 * [I] lParam : HWND of control
9256 * RETURN:
9257 * if first comes before second : negative
9258 * if first comes after second : positive
9259 * if first and second are equivalent : zero
9261 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9263 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9264 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9265 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9267 /* Forward the call to the client defined callback */
9268 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9271 /***
9272 * DESCRIPTION:
9273 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9275 * PARAMETER(S):
9276 * [I] first : pointer to first ITEM_INFO to compare
9277 * [I] second : pointer to second ITEM_INFO to compare
9278 * [I] lParam : HWND of control
9280 * RETURN:
9281 * if first comes before second : negative
9282 * if first comes after second : positive
9283 * if first and second are equivalent : zero
9285 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9287 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9288 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9289 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9291 /* Forward the call to the client defined callback */
9292 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9295 /***
9296 * DESCRIPTION:
9297 * Sorts the listview items.
9299 * PARAMETER(S):
9300 * [I] infoPtr : valid pointer to the listview structure
9301 * [I] pfnCompare : application-defined value
9302 * [I] lParamSort : pointer to comparison callback
9303 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9305 * RETURN:
9306 * SUCCESS : TRUE
9307 * FAILURE : FALSE
9309 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9310 LPARAM lParamSort, BOOL IsEx)
9312 HDPA hdpaSubItems;
9313 ITEM_INFO *lpItem;
9314 LPVOID selectionMarkItem = NULL;
9315 LPVOID focusedItem = NULL;
9316 int i;
9318 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9320 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9322 if (!pfnCompare) return FALSE;
9323 if (!infoPtr->hdpaItems) return FALSE;
9325 /* if there are 0 or 1 items, there is no need to sort */
9326 if (infoPtr->nItemCount < 2) return TRUE;
9328 /* clear selection */
9329 ranges_clear(infoPtr->selectionRanges);
9331 /* save selection mark and focused item */
9332 if (infoPtr->nSelectionMark >= 0)
9333 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9334 if (infoPtr->nFocusedItem >= 0)
9335 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9337 infoPtr->pfnCompare = pfnCompare;
9338 infoPtr->lParamSort = lParamSort;
9339 if (IsEx)
9340 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9341 else
9342 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9344 /* restore selection ranges */
9345 for (i=0; i < infoPtr->nItemCount; i++)
9347 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9348 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9350 if (lpItem->state & LVIS_SELECTED)
9351 ranges_additem(infoPtr->selectionRanges, i);
9353 /* restore selection mark and focused item */
9354 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9355 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9357 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9359 /* refresh the display */
9360 LISTVIEW_InvalidateList(infoPtr);
9361 return TRUE;
9364 /***
9365 * DESCRIPTION:
9366 * Update theme handle after a theme change.
9368 * PARAMETER(S):
9369 * [I] infoPtr : valid pointer to the listview structure
9371 * RETURN:
9372 * SUCCESS : 0
9373 * FAILURE : something else
9375 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9377 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9378 CloseThemeData(theme);
9379 OpenThemeData(infoPtr->hwndSelf, themeClass);
9380 return 0;
9383 /***
9384 * DESCRIPTION:
9385 * Updates an items or rearranges the listview control.
9387 * PARAMETER(S):
9388 * [I] infoPtr : valid pointer to the listview structure
9389 * [I] nItem : item index
9391 * RETURN:
9392 * SUCCESS : TRUE
9393 * FAILURE : FALSE
9395 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9397 TRACE("(nItem=%d)\n", nItem);
9399 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9401 /* rearrange with default alignment style */
9402 if (is_autoarrange(infoPtr))
9403 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9404 else
9405 LISTVIEW_InvalidateItem(infoPtr, nItem);
9407 return TRUE;
9410 /***
9411 * DESCRIPTION:
9412 * Draw the track line at the place defined in the infoPtr structure.
9413 * The line is drawn with a XOR pen so drawing the line for the second time
9414 * in the same place erases the line.
9416 * PARAMETER(S):
9417 * [I] infoPtr : valid pointer to the listview structure
9419 * RETURN:
9420 * SUCCESS : TRUE
9421 * FAILURE : FALSE
9423 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9425 HDC hdc;
9427 if (infoPtr->xTrackLine == -1)
9428 return FALSE;
9430 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9431 return FALSE;
9432 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9433 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9434 ReleaseDC(infoPtr->hwndSelf, hdc);
9435 return TRUE;
9438 /***
9439 * DESCRIPTION:
9440 * Called when an edit control should be displayed. This function is called after
9441 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9443 * PARAMETER(S):
9444 * [I] hwnd : Handle to the listview
9445 * [I] uMsg : WM_TIMER (ignored)
9446 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9447 * [I] dwTimer : The elapsed time (ignored)
9449 * RETURN:
9450 * None.
9452 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9454 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9455 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9457 KillTimer(hwnd, idEvent);
9458 editItem->fEnabled = FALSE;
9459 /* check if the item is still selected */
9460 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9461 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9464 /***
9465 * DESCRIPTION:
9466 * Creates the listview control - the WM_NCCREATE phase.
9468 * PARAMETER(S):
9469 * [I] hwnd : window handle
9470 * [I] lpcs : the create parameters
9472 * RETURN:
9473 * Success: TRUE
9474 * Failure: FALSE
9476 static LRESULT LISTVIEW_NCCreate(HWND hwnd, WPARAM wParam, const CREATESTRUCTW *lpcs)
9478 LISTVIEW_INFO *infoPtr;
9479 LOGFONTW logFont;
9481 TRACE("(lpcs=%p)\n", lpcs);
9483 /* initialize info pointer */
9484 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9485 if (!infoPtr) return FALSE;
9487 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9489 infoPtr->hwndSelf = hwnd;
9490 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9491 map_style_view(infoPtr);
9492 /* determine the type of structures to use */
9493 infoPtr->hwndNotify = lpcs->hwndParent;
9494 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9496 /* initialize color information */
9497 infoPtr->clrBk = CLR_NONE;
9498 infoPtr->clrText = CLR_DEFAULT;
9499 infoPtr->clrTextBk = CLR_DEFAULT;
9500 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9502 /* set default values */
9503 infoPtr->nFocusedItem = -1;
9504 infoPtr->nSelectionMark = -1;
9505 infoPtr->nHotItem = -1;
9506 infoPtr->redraw = TRUE;
9507 infoPtr->bNoItemMetrics = TRUE;
9508 infoPtr->bDoChangeNotify = TRUE;
9509 infoPtr->autoSpacing = TRUE;
9510 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON);
9511 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON);
9512 infoPtr->nEditLabelItem = -1;
9513 infoPtr->nLButtonDownItem = -1;
9514 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9515 infoPtr->cWheelRemainder = 0;
9516 infoPtr->nMeasureItemHeight = 0;
9517 infoPtr->xTrackLine = -1; /* no track line */
9518 infoPtr->itemEdit.fEnabled = FALSE;
9519 infoPtr->iVersion = COMCTL32_VERSION;
9520 infoPtr->colRectsDirty = FALSE;
9522 /* get default font (icon title) */
9523 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9524 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9525 infoPtr->hFont = infoPtr->hDefaultFont;
9526 LISTVIEW_SaveTextMetrics(infoPtr);
9528 /* allocate memory for the data structure */
9529 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9530 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9531 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9532 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9533 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9534 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9536 return DefWindowProcW(hwnd, WM_NCCREATE, wParam, (LPARAM)lpcs);
9538 fail:
9539 DestroyWindow(infoPtr->hwndHeader);
9540 ranges_destroy(infoPtr->selectionRanges);
9541 DPA_Destroy(infoPtr->hdpaItems);
9542 DPA_Destroy(infoPtr->hdpaItemIds);
9543 DPA_Destroy(infoPtr->hdpaPosX);
9544 DPA_Destroy(infoPtr->hdpaPosY);
9545 DPA_Destroy(infoPtr->hdpaColumns);
9546 Free(infoPtr);
9547 return FALSE;
9550 /***
9551 * DESCRIPTION:
9552 * Creates the listview control - the WM_CREATE phase. Most of the data is
9553 * already set up in LISTVIEW_NCCreate
9555 * PARAMETER(S):
9556 * [I] hwnd : window handle
9557 * [I] lpcs : the create parameters
9559 * RETURN:
9560 * Success: 0
9561 * Failure: -1
9563 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9565 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9567 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9569 infoPtr->dwStyle = lpcs->style;
9570 map_style_view(infoPtr);
9572 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9573 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9574 /* on error defaulting to ANSI notifications */
9575 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9576 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9578 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9580 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9582 else
9583 infoPtr->hwndHeader = 0;
9585 /* init item size to avoid division by 0 */
9586 LISTVIEW_UpdateItemSize (infoPtr);
9587 LISTVIEW_UpdateSize (infoPtr);
9589 if (infoPtr->uView == LV_VIEW_DETAILS)
9591 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9593 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9595 LISTVIEW_UpdateScroll(infoPtr);
9596 /* send WM_MEASUREITEM notification */
9597 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9600 OpenThemeData(hwnd, themeClass);
9602 /* initialize the icon sizes */
9603 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9604 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9605 return 0;
9608 /***
9609 * DESCRIPTION:
9610 * Destroys the listview control.
9612 * PARAMETER(S):
9613 * [I] infoPtr : valid pointer to the listview structure
9615 * RETURN:
9616 * Success: 0
9617 * Failure: -1
9619 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9621 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9622 CloseThemeData(theme);
9624 /* delete all items */
9625 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9627 return 0;
9630 /***
9631 * DESCRIPTION:
9632 * Enables the listview control.
9634 * PARAMETER(S):
9635 * [I] infoPtr : valid pointer to the listview structure
9636 * [I] bEnable : specifies whether to enable or disable the window
9638 * RETURN:
9639 * SUCCESS : TRUE
9640 * FAILURE : FALSE
9642 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9644 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9645 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9646 return TRUE;
9649 /***
9650 * DESCRIPTION:
9651 * Erases the background of the listview control.
9653 * PARAMETER(S):
9654 * [I] infoPtr : valid pointer to the listview structure
9655 * [I] hdc : device context handle
9657 * RETURN:
9658 * SUCCESS : TRUE
9659 * FAILURE : FALSE
9661 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9663 RECT rc;
9665 TRACE("(hdc=%p)\n", hdc);
9667 if (!GetClipBox(hdc, &rc)) return FALSE;
9669 if (infoPtr->clrBk == CLR_NONE)
9671 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9672 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9673 (WPARAM)hdc, PRF_ERASEBKGND);
9674 else
9675 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9678 /* for double buffered controls we need to do this during refresh */
9679 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9681 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9685 /***
9686 * DESCRIPTION:
9687 * Helper function for LISTVIEW_[HV]Scroll *only*.
9688 * Performs vertical/horizontal scrolling by a give amount.
9690 * PARAMETER(S):
9691 * [I] infoPtr : valid pointer to the listview structure
9692 * [I] dx : amount of horizontal scroll
9693 * [I] dy : amount of vertical scroll
9695 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9697 /* now we can scroll the list */
9698 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9699 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9700 /* if we have focus, adjust rect */
9701 OffsetRect(&infoPtr->rcFocus, dx, dy);
9702 UpdateWindow(infoPtr->hwndSelf);
9705 /***
9706 * DESCRIPTION:
9707 * Performs vertical scrolling.
9709 * PARAMETER(S):
9710 * [I] infoPtr : valid pointer to the listview structure
9711 * [I] nScrollCode : scroll code
9712 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9713 * [I] hScrollWnd : scrollbar control window handle
9715 * RETURN:
9716 * Zero
9718 * NOTES:
9719 * SB_LINEUP/SB_LINEDOWN:
9720 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9721 * for LVS_REPORT is 1 line
9722 * for LVS_LIST cannot occur
9725 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9726 INT nScrollDiff)
9728 INT nOldScrollPos, nNewScrollPos;
9729 SCROLLINFO scrollInfo;
9730 BOOL is_an_icon;
9732 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9733 debugscrollcode(nScrollCode), nScrollDiff);
9735 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9737 scrollInfo.cbSize = sizeof(SCROLLINFO);
9738 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9740 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9742 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9744 nOldScrollPos = scrollInfo.nPos;
9745 switch (nScrollCode)
9747 case SB_INTERNAL:
9748 break;
9750 case SB_LINEUP:
9751 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9752 break;
9754 case SB_LINEDOWN:
9755 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9756 break;
9758 case SB_PAGEUP:
9759 nScrollDiff = -scrollInfo.nPage;
9760 break;
9762 case SB_PAGEDOWN:
9763 nScrollDiff = scrollInfo.nPage;
9764 break;
9766 case SB_THUMBPOSITION:
9767 case SB_THUMBTRACK:
9768 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9769 break;
9771 default:
9772 nScrollDiff = 0;
9775 /* quit right away if pos isn't changing */
9776 if (nScrollDiff == 0) return 0;
9778 /* calculate new position, and handle overflows */
9779 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9780 if (nScrollDiff > 0) {
9781 if (nNewScrollPos < nOldScrollPos ||
9782 nNewScrollPos > scrollInfo.nMax)
9783 nNewScrollPos = scrollInfo.nMax;
9784 } else {
9785 if (nNewScrollPos > nOldScrollPos ||
9786 nNewScrollPos < scrollInfo.nMin)
9787 nNewScrollPos = scrollInfo.nMin;
9790 /* set the new position, and reread in case it changed */
9791 scrollInfo.fMask = SIF_POS;
9792 scrollInfo.nPos = nNewScrollPos;
9793 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9795 /* carry on only if it really changed */
9796 if (nNewScrollPos == nOldScrollPos) return 0;
9798 /* now adjust to client coordinates */
9799 nScrollDiff = nOldScrollPos - nNewScrollPos;
9800 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9802 /* and scroll the window */
9803 scroll_list(infoPtr, 0, nScrollDiff);
9805 return 0;
9808 /***
9809 * DESCRIPTION:
9810 * Performs horizontal scrolling.
9812 * PARAMETER(S):
9813 * [I] infoPtr : valid pointer to the listview structure
9814 * [I] nScrollCode : scroll code
9815 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9816 * [I] hScrollWnd : scrollbar control window handle
9818 * RETURN:
9819 * Zero
9821 * NOTES:
9822 * SB_LINELEFT/SB_LINERIGHT:
9823 * for LVS_ICON, LVS_SMALLICON 1 pixel
9824 * for LVS_REPORT is 1 pixel
9825 * for LVS_LIST is 1 column --> which is a 1 because the
9826 * scroll is based on columns not pixels
9829 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9830 INT nScrollDiff)
9832 INT nOldScrollPos, nNewScrollPos;
9833 SCROLLINFO scrollInfo;
9834 BOOL is_an_icon;
9836 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9837 debugscrollcode(nScrollCode), nScrollDiff);
9839 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9841 scrollInfo.cbSize = sizeof(SCROLLINFO);
9842 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9844 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9846 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9848 nOldScrollPos = scrollInfo.nPos;
9850 switch (nScrollCode)
9852 case SB_INTERNAL:
9853 break;
9855 case SB_LINELEFT:
9856 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9857 break;
9859 case SB_LINERIGHT:
9860 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9861 break;
9863 case SB_PAGELEFT:
9864 nScrollDiff = -scrollInfo.nPage;
9865 break;
9867 case SB_PAGERIGHT:
9868 nScrollDiff = scrollInfo.nPage;
9869 break;
9871 case SB_THUMBPOSITION:
9872 case SB_THUMBTRACK:
9873 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9874 break;
9876 default:
9877 nScrollDiff = 0;
9880 /* quit right away if pos isn't changing */
9881 if (nScrollDiff == 0) return 0;
9883 /* calculate new position, and handle overflows */
9884 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9885 if (nScrollDiff > 0) {
9886 if (nNewScrollPos < nOldScrollPos ||
9887 nNewScrollPos > scrollInfo.nMax)
9888 nNewScrollPos = scrollInfo.nMax;
9889 } else {
9890 if (nNewScrollPos > nOldScrollPos ||
9891 nNewScrollPos < scrollInfo.nMin)
9892 nNewScrollPos = scrollInfo.nMin;
9895 /* set the new position, and reread in case it changed */
9896 scrollInfo.fMask = SIF_POS;
9897 scrollInfo.nPos = nNewScrollPos;
9898 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9900 /* carry on only if it really changed */
9901 if (nNewScrollPos == nOldScrollPos) return 0;
9903 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9905 /* now adjust to client coordinates */
9906 nScrollDiff = nOldScrollPos - nNewScrollPos;
9907 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9909 /* and scroll the window */
9910 scroll_list(infoPtr, nScrollDiff, 0);
9912 return 0;
9915 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9917 UINT pulScrollLines = 3;
9919 TRACE("(wheelDelta=%d)\n", wheelDelta);
9921 switch(infoPtr->uView)
9923 case LV_VIEW_ICON:
9924 case LV_VIEW_SMALLICON:
9926 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9927 * should be fixed in the future.
9929 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (wheelDelta > 0) ?
9930 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9931 break;
9933 case LV_VIEW_DETAILS:
9934 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9936 /* if scrolling changes direction, ignore left overs */
9937 if ((wheelDelta < 0 && infoPtr->cWheelRemainder < 0) ||
9938 (wheelDelta > 0 && infoPtr->cWheelRemainder > 0))
9939 infoPtr->cWheelRemainder += wheelDelta;
9940 else
9941 infoPtr->cWheelRemainder = wheelDelta;
9942 if (infoPtr->cWheelRemainder && pulScrollLines)
9944 int cLineScroll;
9945 pulScrollLines = min((UINT)LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9946 cLineScroll = pulScrollLines * (float)infoPtr->cWheelRemainder / WHEEL_DELTA;
9947 infoPtr->cWheelRemainder -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
9948 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, -cLineScroll);
9950 break;
9952 case LV_VIEW_LIST:
9953 LISTVIEW_HScroll(infoPtr, (wheelDelta > 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9954 break;
9956 return 0;
9959 /***
9960 * DESCRIPTION:
9961 * ???
9963 * PARAMETER(S):
9964 * [I] infoPtr : valid pointer to the listview structure
9965 * [I] nVirtualKey : virtual key
9966 * [I] lKeyData : key data
9968 * RETURN:
9969 * Zero
9971 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9973 HWND hwndSelf = infoPtr->hwndSelf;
9974 INT nItem = -1;
9975 NMLVKEYDOWN nmKeyDown;
9977 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9979 /* send LVN_KEYDOWN notification */
9980 nmKeyDown.wVKey = nVirtualKey;
9981 nmKeyDown.flags = 0;
9982 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9983 if (!IsWindow(hwndSelf))
9984 return 0;
9986 switch (nVirtualKey)
9988 case VK_SPACE:
9989 nItem = infoPtr->nFocusedItem;
9990 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9991 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9992 break;
9994 case VK_RETURN:
9995 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9997 if (!notify(infoPtr, NM_RETURN)) return 0;
9998 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
10000 break;
10002 case VK_HOME:
10003 if (infoPtr->nItemCount > 0)
10004 nItem = 0;
10005 break;
10007 case VK_END:
10008 if (infoPtr->nItemCount > 0)
10009 nItem = infoPtr->nItemCount - 1;
10010 break;
10012 case VK_LEFT:
10013 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
10014 break;
10016 case VK_UP:
10017 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
10018 break;
10020 case VK_RIGHT:
10021 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
10022 break;
10024 case VK_DOWN:
10025 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
10026 break;
10028 case VK_PRIOR:
10029 if (infoPtr->uView == LV_VIEW_DETAILS)
10031 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10032 if (infoPtr->nFocusedItem == topidx)
10033 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
10034 else
10035 nItem = topidx;
10037 else
10038 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
10039 * LISTVIEW_GetCountPerRow(infoPtr);
10040 if(nItem < 0) nItem = 0;
10041 break;
10043 case VK_NEXT:
10044 if (infoPtr->uView == LV_VIEW_DETAILS)
10046 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10047 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
10048 if (infoPtr->nFocusedItem == topidx + cnt - 1)
10049 nItem = infoPtr->nFocusedItem + cnt - 1;
10050 else
10051 nItem = topidx + cnt - 1;
10053 else
10054 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
10055 * LISTVIEW_GetCountPerRow(infoPtr);
10056 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
10057 break;
10060 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
10061 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
10063 return 0;
10066 /***
10067 * DESCRIPTION:
10068 * Kills the focus.
10070 * PARAMETER(S):
10071 * [I] infoPtr : valid pointer to the listview structure
10073 * RETURN:
10074 * Zero
10076 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
10078 TRACE("()\n");
10080 /* drop any left over scroll amount */
10081 infoPtr->cWheelRemainder = 0;
10083 /* if we did not have the focus, there's nothing more to do */
10084 if (!infoPtr->bFocus) return 0;
10086 /* send NM_KILLFOCUS notification */
10087 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
10089 /* if we have a focus rectangle, get rid of it */
10090 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
10092 /* if have a marquee selection, stop it */
10093 if (infoPtr->bMarqueeSelect)
10095 /* Remove the marquee rectangle and release our mouse capture */
10096 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
10097 ReleaseCapture();
10099 SetRectEmpty(&infoPtr->marqueeRect);
10101 infoPtr->bMarqueeSelect = FALSE;
10102 infoPtr->bScrolling = FALSE;
10103 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10106 /* set window focus flag */
10107 infoPtr->bFocus = FALSE;
10109 /* invalidate the selected items before resetting focus flag */
10110 LISTVIEW_InvalidateSelectedItems(infoPtr);
10112 return 0;
10115 /***
10116 * DESCRIPTION:
10117 * Processes double click messages (left mouse button).
10119 * PARAMETER(S):
10120 * [I] infoPtr : valid pointer to the listview structure
10121 * [I] wKey : key flag
10122 * [I] x,y : mouse coordinate
10124 * RETURN:
10125 * Zero
10127 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10129 LVHITTESTINFO htInfo;
10131 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10133 /* Cancel the item edition if any */
10134 if (infoPtr->itemEdit.fEnabled)
10136 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
10137 infoPtr->itemEdit.fEnabled = FALSE;
10140 /* send NM_RELEASEDCAPTURE notification */
10141 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10143 htInfo.pt.x = x;
10144 htInfo.pt.y = y;
10146 /* send NM_DBLCLK notification */
10147 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
10148 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
10150 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
10151 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
10153 return 0;
10156 static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
10158 MSG msg;
10159 RECT r;
10161 r.top = r.bottom = pt.y;
10162 r.left = r.right = pt.x;
10164 InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
10166 SetCapture(infoPtr->hwndSelf);
10168 while (1)
10170 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
10172 if (msg.message == WM_MOUSEMOVE)
10174 pt.x = (short)LOWORD(msg.lParam);
10175 pt.y = (short)HIWORD(msg.lParam);
10176 if (PtInRect(&r, pt))
10177 continue;
10178 else
10180 ReleaseCapture();
10181 return 1;
10184 else if (msg.message >= WM_LBUTTONDOWN &&
10185 msg.message <= WM_RBUTTONDBLCLK)
10187 break;
10190 DispatchMessageW(&msg);
10193 if (GetCapture() != infoPtr->hwndSelf)
10194 return 0;
10197 ReleaseCapture();
10198 return 0;
10202 /***
10203 * DESCRIPTION:
10204 * Processes mouse down messages (left mouse button).
10206 * PARAMETERS:
10207 * infoPtr [I ] valid pointer to the listview structure
10208 * wKey [I ] key flag
10209 * x,y [I ] mouse coordinate
10211 * RETURN:
10212 * Zero
10214 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10216 LVHITTESTINFO lvHitTestInfo;
10217 static BOOL bGroupSelect = TRUE;
10218 POINT pt = { x, y };
10219 INT nItem;
10221 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10223 /* send NM_RELEASEDCAPTURE notification */
10224 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10226 /* set left button down flag and record the click position */
10227 infoPtr->bLButtonDown = TRUE;
10228 infoPtr->ptClickPos = pt;
10229 infoPtr->bDragging = FALSE;
10230 infoPtr->bMarqueeSelect = FALSE;
10231 infoPtr->bScrolling = FALSE;
10233 lvHitTestInfo.pt.x = x;
10234 lvHitTestInfo.pt.y = y;
10236 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10237 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10238 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10240 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10242 toggle_checkbox_state(infoPtr, nItem);
10243 return 0;
10246 if (infoPtr->dwStyle & LVS_SINGLESEL)
10248 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10249 infoPtr->nEditLabelItem = nItem;
10250 else
10251 LISTVIEW_SetSelection(infoPtr, nItem);
10253 else
10255 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10257 if (bGroupSelect)
10259 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10260 LISTVIEW_SetItemFocus(infoPtr, nItem);
10261 infoPtr->nSelectionMark = nItem;
10263 else
10265 LVITEMW item;
10267 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10268 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10270 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10271 infoPtr->nSelectionMark = nItem;
10274 else if (wKey & MK_CONTROL)
10276 LVITEMW item;
10278 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10280 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10281 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10282 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10283 infoPtr->nSelectionMark = nItem;
10285 else if (wKey & MK_SHIFT)
10287 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10289 else
10291 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10293 infoPtr->nEditLabelItem = nItem;
10294 infoPtr->nLButtonDownItem = nItem;
10296 LISTVIEW_SetItemFocus(infoPtr, nItem);
10298 else
10299 /* set selection (clears other pre-existing selections) */
10300 LISTVIEW_SetSelection(infoPtr, nItem);
10304 if (!infoPtr->bFocus)
10305 SetFocus(infoPtr->hwndSelf);
10307 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10308 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10310 else
10312 if (!infoPtr->bFocus)
10313 SetFocus(infoPtr->hwndSelf);
10315 /* remove all selections */
10316 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10317 LISTVIEW_DeselectAll(infoPtr);
10318 ReleaseCapture();
10321 return 0;
10324 /***
10325 * DESCRIPTION:
10326 * Processes mouse up messages (left mouse button).
10328 * PARAMETERS:
10329 * infoPtr [I ] valid pointer to the listview structure
10330 * wKey [I ] key flag
10331 * x,y [I ] mouse coordinate
10333 * RETURN:
10334 * Zero
10336 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10338 LVHITTESTINFO lvHitTestInfo;
10340 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10342 if (!infoPtr->bLButtonDown) return 0;
10344 lvHitTestInfo.pt.x = x;
10345 lvHitTestInfo.pt.y = y;
10347 /* send NM_CLICK notification */
10348 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10349 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10351 /* set left button flag */
10352 infoPtr->bLButtonDown = FALSE;
10354 /* set a single selection, reset others */
10355 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10356 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10357 infoPtr->nLButtonDownItem = -1;
10359 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10361 /* Remove the marquee rectangle and release our mouse capture */
10362 if (infoPtr->bMarqueeSelect)
10364 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10365 ReleaseCapture();
10368 SetRectEmpty(&infoPtr->marqueeRect);
10369 SetRectEmpty(&infoPtr->marqueeDrawRect);
10371 infoPtr->bDragging = FALSE;
10372 infoPtr->bMarqueeSelect = FALSE;
10373 infoPtr->bScrolling = FALSE;
10375 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10376 return 0;
10379 /* if we clicked on a selected item, edit the label */
10380 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10382 /* we want to make sure the user doesn't want to do a double click. So we will
10383 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10385 infoPtr->itemEdit.fEnabled = TRUE;
10386 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10387 SetTimer(infoPtr->hwndSelf,
10388 (UINT_PTR)&infoPtr->itemEdit,
10389 GetDoubleClickTime(),
10390 LISTVIEW_DelayedEditItem);
10393 return 0;
10396 /***
10397 * DESCRIPTION:
10398 * Destroys the listview control (called after WM_DESTROY).
10400 * PARAMETER(S):
10401 * [I] infoPtr : valid pointer to the listview structure
10403 * RETURN:
10404 * Zero
10406 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10408 INT i;
10410 TRACE("()\n");
10412 /* destroy data structure */
10413 DPA_Destroy(infoPtr->hdpaItems);
10414 DPA_Destroy(infoPtr->hdpaItemIds);
10415 DPA_Destroy(infoPtr->hdpaPosX);
10416 DPA_Destroy(infoPtr->hdpaPosY);
10417 /* columns */
10418 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10419 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10420 DPA_Destroy(infoPtr->hdpaColumns);
10421 ranges_destroy(infoPtr->selectionRanges);
10423 /* destroy image lists */
10424 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10426 ImageList_Destroy(infoPtr->himlNormal);
10427 ImageList_Destroy(infoPtr->himlSmall);
10428 ImageList_Destroy(infoPtr->himlState);
10431 /* destroy font, bkgnd brush */
10432 infoPtr->hFont = 0;
10433 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10434 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10436 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10438 /* free listview info pointer*/
10439 Free(infoPtr);
10441 return 0;
10444 /***
10445 * DESCRIPTION:
10446 * Handles notifications.
10448 * PARAMETER(S):
10449 * [I] infoPtr : valid pointer to the listview structure
10450 * [I] lpnmhdr : notification information
10452 * RETURN:
10453 * Zero
10455 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10457 NMHEADERW *lpnmh;
10459 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10461 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10463 /* remember: HDN_LAST < HDN_FIRST */
10464 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10465 lpnmh = (NMHEADERW *)lpnmhdr;
10467 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10469 switch (lpnmhdr->code)
10471 case HDN_TRACKW:
10472 case HDN_TRACKA:
10474 COLUMN_INFO *lpColumnInfo;
10475 POINT ptOrigin;
10476 INT x;
10478 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10479 break;
10481 /* remove the old line (if any) */
10482 LISTVIEW_DrawTrackLine(infoPtr);
10484 /* compute & draw the new line */
10485 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10486 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10487 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10488 infoPtr->xTrackLine = x + ptOrigin.x;
10489 LISTVIEW_DrawTrackLine(infoPtr);
10490 return notify_forward_header(infoPtr, lpnmh);
10493 case HDN_ENDTRACKA:
10494 case HDN_ENDTRACKW:
10495 /* remove the track line (if any) */
10496 LISTVIEW_DrawTrackLine(infoPtr);
10497 infoPtr->xTrackLine = -1;
10498 return notify_forward_header(infoPtr, lpnmh);
10500 case HDN_BEGINDRAG:
10501 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10502 return notify_forward_header(infoPtr, lpnmh);
10504 case HDN_ENDDRAG:
10505 infoPtr->colRectsDirty = TRUE;
10506 LISTVIEW_InvalidateList(infoPtr);
10507 return notify_forward_header(infoPtr, lpnmh);
10509 case HDN_ITEMCHANGEDW:
10510 case HDN_ITEMCHANGEDA:
10512 COLUMN_INFO *lpColumnInfo;
10513 HDITEMW hdi;
10514 INT dx, cxy;
10516 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10518 hdi.mask = HDI_WIDTH;
10519 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10520 cxy = hdi.cxy;
10522 else
10523 cxy = lpnmh->pitem->cxy;
10525 /* determine how much we change since the last know position */
10526 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10527 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10528 if (dx != 0)
10530 lpColumnInfo->rcHeader.right += dx;
10532 hdi.mask = HDI_ORDER;
10533 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10535 /* not the rightmost one */
10536 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10538 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10539 hdi.iOrder + 1, 0);
10540 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10542 else
10544 /* only needs to update the scrolls */
10545 infoPtr->nItemWidth += dx;
10546 LISTVIEW_UpdateScroll(infoPtr);
10548 LISTVIEW_UpdateItemSize(infoPtr);
10549 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10551 POINT ptOrigin;
10552 RECT rcCol = lpColumnInfo->rcHeader;
10554 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10555 OffsetRect(&rcCol, ptOrigin.x, 0);
10557 rcCol.top = infoPtr->rcList.top;
10558 rcCol.bottom = infoPtr->rcList.bottom;
10560 /* resizing left-aligned columns leaves most of the left side untouched */
10561 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10563 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10564 if (dx > 0)
10565 nMaxDirty += dx;
10566 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10569 /* when shrinking the last column clear the now unused field */
10570 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10572 RECT right;
10574 rcCol.right -= dx;
10576 /* deal with right from rightmost column area */
10577 right.left = rcCol.right;
10578 right.top = rcCol.top;
10579 right.bottom = rcCol.bottom;
10580 right.right = infoPtr->rcList.right;
10582 LISTVIEW_InvalidateRect(infoPtr, &right);
10585 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10588 break;
10591 case HDN_ITEMCLICKW:
10592 case HDN_ITEMCLICKA:
10594 /* Handle sorting by Header Column */
10595 NMLISTVIEW nmlv;
10597 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10598 nmlv.iItem = -1;
10599 nmlv.iSubItem = lpnmh->iItem;
10600 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10601 return notify_forward_header(infoPtr, lpnmh);
10604 case HDN_DIVIDERDBLCLICKW:
10605 case HDN_DIVIDERDBLCLICKA:
10606 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10607 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10608 split needed for that */
10609 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10610 return notify_forward_header(infoPtr, lpnmh);
10612 return 0;
10615 /***
10616 * DESCRIPTION:
10617 * Paint non-client area of control.
10619 * PARAMETER(S):
10620 * [I] infoPtr : valid pointer to the listview structureof the sender
10621 * [I] region : update region
10623 * RETURN:
10624 * TRUE - frame was painted
10625 * FALSE - call default window proc
10627 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10629 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10630 HDC dc;
10631 RECT r;
10632 HRGN cliprgn;
10633 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10634 cyEdge = GetSystemMetrics (SM_CYEDGE);
10636 if (!theme)
10637 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10639 GetWindowRect(infoPtr->hwndSelf, &r);
10641 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10642 r.right - cxEdge, r.bottom - cyEdge);
10643 if (region != (HRGN)1)
10644 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10645 OffsetRect(&r, -r.left, -r.top);
10647 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10648 OffsetRect(&r, -r.left, -r.top);
10650 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10651 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10652 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10653 ReleaseDC(infoPtr->hwndSelf, dc);
10655 /* Call default proc to get the scrollbars etc. painted */
10656 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10658 return FALSE;
10661 /***
10662 * DESCRIPTION:
10663 * Determines the type of structure to use.
10665 * PARAMETER(S):
10666 * [I] infoPtr : valid pointer to the listview structureof the sender
10667 * [I] hwndFrom : listview window handle
10668 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10670 * RETURN:
10671 * Zero
10673 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10675 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10677 if (nCommand == NF_REQUERY)
10678 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10680 return infoPtr->notifyFormat;
10683 /***
10684 * DESCRIPTION:
10685 * Paints/Repaints the listview control. Internal use.
10687 * PARAMETER(S):
10688 * [I] infoPtr : valid pointer to the listview structure
10689 * [I] hdc : device context handle
10691 * RETURN:
10692 * Zero
10694 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10696 TRACE("(hdc=%p)\n", hdc);
10698 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10700 infoPtr->bNoItemMetrics = FALSE;
10701 LISTVIEW_UpdateItemSize(infoPtr);
10702 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10703 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10704 LISTVIEW_UpdateScroll(infoPtr);
10707 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10709 if (hdc)
10710 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10711 else
10713 PAINTSTRUCT ps;
10715 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10716 if (!hdc) return 1;
10717 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10718 EndPaint(infoPtr->hwndSelf, &ps);
10721 return 0;
10724 /***
10725 * DESCRIPTION:
10726 * Paints/Repaints the listview control, WM_PAINT handler.
10728 * PARAMETER(S):
10729 * [I] infoPtr : valid pointer to the listview structure
10730 * [I] hdc : device context handle
10732 * RETURN:
10733 * Zero
10735 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10737 TRACE("(hdc=%p)\n", hdc);
10739 if (!is_redrawing(infoPtr))
10740 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10742 return LISTVIEW_Paint(infoPtr, hdc);
10745 /***
10746 * DESCRIPTION:
10747 * Paints/Repaints the listview control.
10749 * PARAMETER(S):
10750 * [I] infoPtr : valid pointer to the listview structure
10751 * [I] hdc : device context handle
10752 * [I] options : drawing options
10754 * RETURN:
10755 * Zero
10757 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10759 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10760 return 0;
10762 if (options & ~(PRF_ERASEBKGND|PRF_CLIENT))
10763 FIXME("(hdc=%p options=0x%08x) partial stub\n", hdc, options);
10765 if (options & PRF_ERASEBKGND)
10766 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10768 if (options & PRF_CLIENT)
10769 LISTVIEW_Paint(infoPtr, hdc);
10771 return 0;
10775 /***
10776 * DESCRIPTION:
10777 * Processes double click messages (right mouse button).
10779 * PARAMETER(S):
10780 * [I] infoPtr : valid pointer to the listview structure
10781 * [I] wKey : key flag
10782 * [I] x,y : mouse coordinate
10784 * RETURN:
10785 * Zero
10787 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10789 LVHITTESTINFO lvHitTestInfo;
10791 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10793 /* send NM_RELEASEDCAPTURE notification */
10794 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10796 /* send NM_RDBLCLK notification */
10797 lvHitTestInfo.pt.x = x;
10798 lvHitTestInfo.pt.y = y;
10799 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10800 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10802 return 0;
10805 /***
10806 * DESCRIPTION:
10807 * Processes WM_RBUTTONDOWN message and corresponding drag operation.
10809 * PARAMETER(S):
10810 * [I] infoPtr : valid pointer to the listview structure
10811 * [I] wKey : key flag
10812 * [I] x, y : mouse coordinate
10814 * RETURN:
10815 * Zero
10817 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10819 LVHITTESTINFO ht;
10820 INT item;
10822 TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
10824 /* send NM_RELEASEDCAPTURE notification */
10825 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10827 /* determine the index of the selected item */
10828 ht.pt.x = x;
10829 ht.pt.y = y;
10830 item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
10832 /* make sure the listview control window has the focus */
10833 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10835 if ((item >= 0) && (item < infoPtr->nItemCount))
10837 LISTVIEW_SetItemFocus(infoPtr, item);
10838 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10839 !LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
10840 LISTVIEW_SetSelection(infoPtr, item);
10842 else
10843 LISTVIEW_DeselectAll(infoPtr);
10845 if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
10847 if (ht.iItem != -1)
10849 NMLISTVIEW nmlv;
10851 memset(&nmlv, 0, sizeof(nmlv));
10852 nmlv.iItem = ht.iItem;
10853 nmlv.ptAction = ht.pt;
10855 notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
10858 else
10860 SetFocus(infoPtr->hwndSelf);
10862 ht.pt.x = x;
10863 ht.pt.y = y;
10864 LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
10866 if (notify_click(infoPtr, NM_RCLICK, &ht))
10868 /* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
10869 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10870 (WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
10874 return 0;
10877 /***
10878 * DESCRIPTION:
10879 * Sets the cursor.
10881 * PARAMETER(S):
10882 * [I] infoPtr : valid pointer to the listview structure
10883 * [I] hwnd : window handle of window containing the cursor
10884 * [I] nHittest : hit-test code
10885 * [I] wMouseMsg : ideintifier of the mouse message
10887 * RETURN:
10888 * TRUE if cursor is set
10889 * FALSE otherwise
10891 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10893 LVHITTESTINFO lvHitTestInfo;
10895 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10897 if (!infoPtr->hHotCursor) goto forward;
10899 GetCursorPos(&lvHitTestInfo.pt);
10900 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10902 SetCursor(infoPtr->hHotCursor);
10904 return TRUE;
10906 forward:
10908 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10911 /***
10912 * DESCRIPTION:
10913 * Sets the focus.
10915 * PARAMETER(S):
10916 * [I] infoPtr : valid pointer to the listview structure
10917 * [I] hwndLoseFocus : handle of previously focused window
10919 * RETURN:
10920 * Zero
10922 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10924 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10926 /* if we have the focus already, there's nothing to do */
10927 if (infoPtr->bFocus) return 0;
10929 /* send NM_SETFOCUS notification */
10930 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10932 /* set window focus flag */
10933 infoPtr->bFocus = TRUE;
10935 /* put the focus rect back on */
10936 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10938 /* redraw all visible selected items */
10939 LISTVIEW_InvalidateSelectedItems(infoPtr);
10941 return 0;
10944 /***
10945 * DESCRIPTION:
10946 * Sets the font.
10948 * PARAMETER(S):
10949 * [I] infoPtr : valid pointer to the listview structure
10950 * [I] fRedraw : font handle
10951 * [I] fRedraw : redraw flag
10953 * RETURN:
10954 * Zero
10956 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10958 HFONT oldFont = infoPtr->hFont;
10959 INT oldHeight = infoPtr->nItemHeight;
10961 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10963 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10964 if (infoPtr->hFont == oldFont) return 0;
10966 LISTVIEW_SaveTextMetrics(infoPtr);
10968 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10970 if (infoPtr->uView == LV_VIEW_DETAILS)
10972 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10973 LISTVIEW_UpdateSize(infoPtr);
10974 LISTVIEW_UpdateScroll(infoPtr);
10976 else if (infoPtr->nItemHeight != oldHeight)
10977 LISTVIEW_UpdateScroll(infoPtr);
10979 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10981 return 0;
10984 /***
10985 * DESCRIPTION:
10986 * Message handling for WM_SETREDRAW.
10987 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10989 * PARAMETER(S):
10990 * [I] infoPtr : valid pointer to the listview structure
10991 * [I] redraw: state of redraw flag
10993 * RETURN:
10994 * Zero.
10996 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL redraw)
10998 TRACE("old=%d, new=%d\n", infoPtr->redraw, redraw);
11000 if (infoPtr->redraw == !!redraw)
11001 return 0;
11003 if (!(infoPtr->redraw = !!redraw))
11004 return 0;
11006 if (is_autoarrange(infoPtr))
11007 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11008 LISTVIEW_UpdateScroll(infoPtr);
11010 /* despite what the WM_SETREDRAW docs says, apps expect us
11011 * to invalidate the listview here... stupid! */
11012 LISTVIEW_InvalidateList(infoPtr);
11014 return 0;
11017 /***
11018 * DESCRIPTION:
11019 * Resizes the listview control. This function processes WM_SIZE
11020 * messages. At this time, the width and height are not used.
11022 * PARAMETER(S):
11023 * [I] infoPtr : valid pointer to the listview structure
11024 * [I] Width : new width
11025 * [I] Height : new height
11027 * RETURN:
11028 * Zero
11030 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
11032 RECT rcOld = infoPtr->rcList;
11034 TRACE("(width=%d, height=%d)\n", Width, Height);
11036 LISTVIEW_UpdateSize(infoPtr);
11037 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
11039 /* do not bother with display related stuff if we're not redrawing */
11040 if (!is_redrawing(infoPtr)) return 0;
11042 if (is_autoarrange(infoPtr))
11043 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11045 LISTVIEW_UpdateScroll(infoPtr);
11047 /* refresh all only for lists whose height changed significantly */
11048 if ((infoPtr->uView == LV_VIEW_LIST) &&
11049 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
11050 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
11051 LISTVIEW_InvalidateList(infoPtr);
11053 return 0;
11056 /***
11057 * DESCRIPTION:
11058 * Sets the size information.
11060 * PARAMETER(S):
11061 * [I] infoPtr : valid pointer to the listview structure
11063 * RETURN:
11064 * None
11066 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
11068 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
11070 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
11072 if (infoPtr->uView == LV_VIEW_LIST)
11074 /* Apparently the "LIST" style is supposed to have the same
11075 * number of items in a column even if there is no scroll bar.
11076 * Since if a scroll bar already exists then the bottom is already
11077 * reduced, only reduce if the scroll bar does not currently exist.
11078 * The "2" is there to mimic the native control. I think it may be
11079 * related to either padding or edges. (GLA 7/2002)
11081 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
11082 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
11083 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
11086 /* if control created invisible header isn't created */
11087 if (infoPtr->hwndHeader)
11089 HDLAYOUT hl;
11090 WINDOWPOS wp;
11092 hl.prc = &infoPtr->rcList;
11093 hl.pwpos = &wp;
11094 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11095 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
11097 if (LISTVIEW_IsHeaderEnabled(infoPtr))
11098 wp.flags |= SWP_SHOWWINDOW;
11099 else
11101 wp.flags |= SWP_HIDEWINDOW;
11102 wp.cy = 0;
11105 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
11106 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
11108 infoPtr->rcList.top = max(wp.cy, 0);
11110 /* extra padding for grid */
11111 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
11112 infoPtr->rcList.top += 2;
11114 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
11117 /***
11118 * DESCRIPTION:
11119 * Processes WM_STYLECHANGED messages.
11121 * PARAMETER(S):
11122 * [I] infoPtr : valid pointer to the listview structure
11123 * [I] wStyleType : window style type (normal or extended)
11124 * [I] lpss : window style information
11126 * RETURN:
11127 * Zero
11129 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
11130 const STYLESTRUCT *lpss)
11132 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
11133 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
11134 UINT style;
11136 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11137 wStyleType, lpss->styleOld, lpss->styleNew);
11139 if (wStyleType != GWL_STYLE) return 0;
11141 infoPtr->dwStyle = lpss->styleNew;
11143 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
11144 ((lpss->styleNew & WS_HSCROLL) == 0))
11145 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
11147 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
11148 ((lpss->styleNew & WS_VSCROLL) == 0))
11149 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
11151 if (uNewView != uOldView)
11153 HIMAGELIST himl;
11155 /* LVM_SETVIEW doesn't change window style bits within LVS_TYPEMASK,
11156 changing style updates current view only when view bits change. */
11157 map_style_view(infoPtr);
11158 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
11159 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
11161 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
11162 SetRectEmpty(&infoPtr->rcFocus);
11164 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
11165 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
11167 if (uNewView == LVS_REPORT)
11169 HDLAYOUT hl;
11170 WINDOWPOS wp;
11172 LISTVIEW_CreateHeader( infoPtr );
11174 hl.prc = &infoPtr->rcList;
11175 hl.pwpos = &wp;
11176 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11177 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
11178 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
11179 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
11182 LISTVIEW_UpdateItemSize(infoPtr);
11185 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
11187 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
11189 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
11191 /* Turn off the header control */
11192 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
11193 TRACE("Hide header control, was 0x%08x\n", style);
11194 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11195 } else {
11196 /* Turn on the header control */
11197 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11199 TRACE("Show header control, was 0x%08x\n", style);
11200 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11206 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11207 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11208 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11210 /* update the size of the client area */
11211 LISTVIEW_UpdateSize(infoPtr);
11213 /* add scrollbars if needed */
11214 LISTVIEW_UpdateScroll(infoPtr);
11216 /* invalidate client area + erase background */
11217 LISTVIEW_InvalidateList(infoPtr);
11219 return 0;
11222 /***
11223 * DESCRIPTION:
11224 * Processes WM_STYLECHANGING messages.
11226 * PARAMETER(S):
11227 * [I] wStyleType : window style type (normal or extended)
11228 * [I0] lpss : window style information
11230 * RETURN:
11231 * Zero
11233 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11234 STYLESTRUCT *lpss)
11236 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11237 wStyleType, lpss->styleOld, lpss->styleNew);
11239 /* don't forward LVS_OWNERDATA only if not already set to */
11240 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11242 if (lpss->styleOld & LVS_OWNERDATA)
11243 lpss->styleNew |= LVS_OWNERDATA;
11244 else
11245 lpss->styleNew &= ~LVS_OWNERDATA;
11248 return 0;
11251 /***
11252 * DESCRIPTION:
11253 * Processes WM_SHOWWINDOW messages.
11255 * PARAMETER(S):
11256 * [I] infoPtr : valid pointer to the listview structure
11257 * [I] bShown : window is being shown (FALSE when hidden)
11258 * [I] iStatus : window show status
11260 * RETURN:
11261 * Zero
11263 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11265 /* header delayed creation */
11266 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11268 LISTVIEW_CreateHeader(infoPtr);
11270 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11271 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11274 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11277 /***
11278 * DESCRIPTION:
11279 * Processes CCM_GETVERSION messages.
11281 * PARAMETER(S):
11282 * [I] infoPtr : valid pointer to the listview structure
11284 * RETURN:
11285 * Current version
11287 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11289 return infoPtr->iVersion;
11292 /***
11293 * DESCRIPTION:
11294 * Processes CCM_SETVERSION messages.
11296 * PARAMETER(S):
11297 * [I] infoPtr : valid pointer to the listview structure
11298 * [I] iVersion : version to be set
11300 * RETURN:
11301 * -1 when requested version is greater than DLL version;
11302 * previous version otherwise
11304 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11306 INT iOldVersion = infoPtr->iVersion;
11308 if (iVersion > COMCTL32_VERSION)
11309 return -1;
11311 infoPtr->iVersion = iVersion;
11313 TRACE("new version %d\n", iVersion);
11315 return iOldVersion;
11318 /***
11319 * DESCRIPTION:
11320 * Window procedure of the listview control.
11323 static LRESULT WINAPI
11324 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11326 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11328 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11330 if (!infoPtr && (uMsg != WM_NCCREATE))
11331 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11333 switch (uMsg)
11335 case LVM_APPROXIMATEVIEWRECT:
11336 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11337 LOWORD(lParam), HIWORD(lParam));
11338 case LVM_ARRANGE:
11339 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11341 case LVM_CANCELEDITLABEL:
11342 return LISTVIEW_CancelEditLabel(infoPtr);
11344 case LVM_CREATEDRAGIMAGE:
11345 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11347 case LVM_DELETEALLITEMS:
11348 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11350 case LVM_DELETECOLUMN:
11351 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11353 case LVM_DELETEITEM:
11354 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11356 case LVM_EDITLABELA:
11357 case LVM_EDITLABELW:
11358 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11359 uMsg == LVM_EDITLABELW);
11360 /* case LVM_ENABLEGROUPVIEW: */
11362 case LVM_ENSUREVISIBLE:
11363 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11365 case LVM_FINDITEMW:
11366 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11368 case LVM_FINDITEMA:
11369 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11371 case LVM_GETBKCOLOR:
11372 return infoPtr->clrBk;
11374 /* case LVM_GETBKIMAGE: */
11376 case LVM_GETCALLBACKMASK:
11377 return infoPtr->uCallbackMask;
11379 case LVM_GETCOLUMNA:
11380 case LVM_GETCOLUMNW:
11381 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11382 uMsg == LVM_GETCOLUMNW);
11384 case LVM_GETCOLUMNORDERARRAY:
11385 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11387 case LVM_GETCOLUMNWIDTH:
11388 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11390 case LVM_GETCOUNTPERPAGE:
11391 return LISTVIEW_GetCountPerPage(infoPtr);
11393 case LVM_GETEDITCONTROL:
11394 return (LRESULT)infoPtr->hwndEdit;
11396 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11397 return infoPtr->dwLvExStyle;
11399 /* case LVM_GETGROUPINFO: */
11401 /* case LVM_GETGROUPMETRICS: */
11403 case LVM_GETHEADER:
11404 return (LRESULT)infoPtr->hwndHeader;
11406 case LVM_GETHOTCURSOR:
11407 return (LRESULT)infoPtr->hHotCursor;
11409 case LVM_GETHOTITEM:
11410 return infoPtr->nHotItem;
11412 case LVM_GETHOVERTIME:
11413 return infoPtr->dwHoverTime;
11415 case LVM_GETIMAGELIST:
11416 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11418 /* case LVM_GETINSERTMARK: */
11420 /* case LVM_GETINSERTMARKCOLOR: */
11422 /* case LVM_GETINSERTMARKRECT: */
11424 case LVM_GETISEARCHSTRINGA:
11425 case LVM_GETISEARCHSTRINGW:
11426 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11427 return FALSE;
11429 case LVM_GETITEMA:
11430 case LVM_GETITEMW:
11431 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11433 case LVM_GETITEMCOUNT:
11434 return infoPtr->nItemCount;
11436 case LVM_GETITEMPOSITION:
11437 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11439 case LVM_GETITEMRECT:
11440 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11442 case LVM_GETITEMSPACING:
11443 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11445 case LVM_GETITEMSTATE:
11446 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11448 case LVM_GETITEMTEXTA:
11449 case LVM_GETITEMTEXTW:
11450 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11451 uMsg == LVM_GETITEMTEXTW);
11453 case LVM_GETNEXTITEM:
11454 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11456 case LVM_GETNUMBEROFWORKAREAS:
11457 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11458 return 1;
11460 case LVM_GETORIGIN:
11461 if (!lParam) return FALSE;
11462 if (infoPtr->uView == LV_VIEW_DETAILS ||
11463 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11464 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11465 return TRUE;
11467 /* case LVM_GETOUTLINECOLOR: */
11469 /* case LVM_GETSELECTEDCOLUMN: */
11471 case LVM_GETSELECTEDCOUNT:
11472 return LISTVIEW_GetSelectedCount(infoPtr);
11474 case LVM_GETSELECTIONMARK:
11475 return infoPtr->nSelectionMark;
11477 case LVM_GETSTRINGWIDTHA:
11478 case LVM_GETSTRINGWIDTHW:
11479 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11480 uMsg == LVM_GETSTRINGWIDTHW);
11482 case LVM_GETSUBITEMRECT:
11483 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11485 case LVM_GETTEXTBKCOLOR:
11486 return infoPtr->clrTextBk;
11488 case LVM_GETTEXTCOLOR:
11489 return infoPtr->clrText;
11491 /* case LVM_GETTILEINFO: */
11493 /* case LVM_GETTILEVIEWINFO: */
11495 case LVM_GETTOOLTIPS:
11496 if( !infoPtr->hwndToolTip )
11497 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11498 return (LRESULT)infoPtr->hwndToolTip;
11500 case LVM_GETTOPINDEX:
11501 return LISTVIEW_GetTopIndex(infoPtr);
11503 case LVM_GETUNICODEFORMAT:
11504 return (infoPtr->notifyFormat == NFR_UNICODE);
11506 case LVM_GETVIEW:
11507 return infoPtr->uView;
11509 case LVM_GETVIEWRECT:
11510 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11512 case LVM_GETWORKAREAS:
11513 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11514 return FALSE;
11516 /* case LVM_HASGROUP: */
11518 case LVM_HITTEST:
11519 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11521 case LVM_INSERTCOLUMNA:
11522 case LVM_INSERTCOLUMNW:
11523 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11524 uMsg == LVM_INSERTCOLUMNW);
11526 /* case LVM_INSERTGROUP: */
11528 /* case LVM_INSERTGROUPSORTED: */
11530 case LVM_INSERTITEMA:
11531 case LVM_INSERTITEMW:
11532 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11534 /* case LVM_INSERTMARKHITTEST: */
11536 /* case LVM_ISGROUPVIEWENABLED: */
11538 case LVM_ISITEMVISIBLE:
11539 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11541 case LVM_MAPIDTOINDEX:
11542 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11544 case LVM_MAPINDEXTOID:
11545 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11547 /* case LVM_MOVEGROUP: */
11549 /* case LVM_MOVEITEMTOGROUP: */
11551 case LVM_REDRAWITEMS:
11552 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11554 /* case LVM_REMOVEALLGROUPS: */
11556 /* case LVM_REMOVEGROUP: */
11558 case LVM_SCROLL:
11559 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11561 case LVM_SETBKCOLOR:
11562 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11564 /* case LVM_SETBKIMAGE: */
11566 case LVM_SETCALLBACKMASK:
11567 infoPtr->uCallbackMask = (UINT)wParam;
11568 return TRUE;
11570 case LVM_SETCOLUMNA:
11571 case LVM_SETCOLUMNW:
11572 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11573 uMsg == LVM_SETCOLUMNW);
11575 case LVM_SETCOLUMNORDERARRAY:
11576 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11578 case LVM_SETCOLUMNWIDTH:
11579 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11581 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11582 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11584 /* case LVM_SETGROUPINFO: */
11586 /* case LVM_SETGROUPMETRICS: */
11588 case LVM_SETHOTCURSOR:
11589 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11591 case LVM_SETHOTITEM:
11592 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11594 case LVM_SETHOVERTIME:
11595 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11597 case LVM_SETICONSPACING:
11598 if(lParam == -1)
11599 return LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
11600 return LISTVIEW_SetIconSpacing(infoPtr, LOWORD(lParam), HIWORD(lParam));
11602 case LVM_SETIMAGELIST:
11603 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11605 /* case LVM_SETINFOTIP: */
11607 /* case LVM_SETINSERTMARK: */
11609 /* case LVM_SETINSERTMARKCOLOR: */
11611 case LVM_SETITEMA:
11612 case LVM_SETITEMW:
11614 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11615 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11618 case LVM_SETITEMCOUNT:
11619 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11621 case LVM_SETITEMPOSITION:
11623 POINT pt;
11624 pt.x = (short)LOWORD(lParam);
11625 pt.y = (short)HIWORD(lParam);
11626 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11629 case LVM_SETITEMPOSITION32:
11630 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11632 case LVM_SETITEMSTATE:
11633 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11635 case LVM_SETITEMTEXTA:
11636 case LVM_SETITEMTEXTW:
11637 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11638 uMsg == LVM_SETITEMTEXTW);
11640 /* case LVM_SETOUTLINECOLOR: */
11642 /* case LVM_SETSELECTEDCOLUMN: */
11644 case LVM_SETSELECTIONMARK:
11645 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11647 case LVM_SETTEXTBKCOLOR:
11648 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11650 case LVM_SETTEXTCOLOR:
11651 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11653 /* case LVM_SETTILEINFO: */
11655 /* case LVM_SETTILEVIEWINFO: */
11657 /* case LVM_SETTILEWIDTH: */
11659 case LVM_SETTOOLTIPS:
11660 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11662 case LVM_SETUNICODEFORMAT:
11663 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11665 case LVM_SETVIEW:
11666 return LISTVIEW_SetView(infoPtr, wParam);
11668 /* case LVM_SETWORKAREAS: */
11670 /* case LVM_SORTGROUPS: */
11672 case LVM_SORTITEMS:
11673 case LVM_SORTITEMSEX:
11674 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11675 uMsg == LVM_SORTITEMSEX);
11676 case LVM_SUBITEMHITTEST:
11677 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11679 case LVM_UPDATE:
11680 return LISTVIEW_Update(infoPtr, (INT)wParam);
11682 case CCM_GETVERSION:
11683 return LISTVIEW_GetVersion(infoPtr);
11685 case CCM_SETVERSION:
11686 return LISTVIEW_SetVersion(infoPtr, wParam);
11688 case WM_CHAR:
11689 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11691 case WM_COMMAND:
11692 return LISTVIEW_Command(infoPtr, wParam, lParam);
11694 case WM_NCCREATE:
11695 return LISTVIEW_NCCreate(hwnd, wParam, (LPCREATESTRUCTW)lParam);
11697 case WM_CREATE:
11698 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11700 case WM_DESTROY:
11701 return LISTVIEW_Destroy(infoPtr);
11703 case WM_ENABLE:
11704 return LISTVIEW_Enable(infoPtr);
11706 case WM_ERASEBKGND:
11707 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11709 case WM_GETDLGCODE:
11710 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11712 case WM_GETFONT:
11713 return (LRESULT)infoPtr->hFont;
11715 case WM_HSCROLL:
11716 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11718 case WM_KEYDOWN:
11719 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11721 case WM_KILLFOCUS:
11722 return LISTVIEW_KillFocus(infoPtr);
11724 case WM_LBUTTONDBLCLK:
11725 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11727 case WM_LBUTTONDOWN:
11728 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11730 case WM_LBUTTONUP:
11731 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11733 case WM_MOUSEMOVE:
11734 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11736 case WM_MOUSEHOVER:
11737 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11739 case WM_NCDESTROY:
11740 return LISTVIEW_NCDestroy(infoPtr);
11742 case WM_NCPAINT:
11743 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11745 case WM_NOTIFY:
11746 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11748 case WM_NOTIFYFORMAT:
11749 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11751 case WM_PRINTCLIENT:
11752 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11754 case WM_PAINT:
11755 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11757 case WM_RBUTTONDBLCLK:
11758 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11760 case WM_RBUTTONDOWN:
11761 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11763 case WM_SETCURSOR:
11764 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11766 case WM_SETFOCUS:
11767 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11769 case WM_SETFONT:
11770 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11772 case WM_SETREDRAW:
11773 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11775 case WM_SHOWWINDOW:
11776 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11778 case WM_STYLECHANGED:
11779 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11781 case WM_STYLECHANGING:
11782 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11784 case WM_SYSCOLORCHANGE:
11785 COMCTL32_RefreshSysColors();
11786 return 0;
11788 /* case WM_TIMER: */
11789 case WM_THEMECHANGED:
11790 return LISTVIEW_ThemeChanged(infoPtr);
11792 case WM_VSCROLL:
11793 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11795 case WM_MOUSEWHEEL:
11796 if (wParam & (MK_SHIFT | MK_CONTROL))
11797 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11798 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11800 case WM_WINDOWPOSCHANGED:
11801 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11803 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11804 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11806 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11808 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11810 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11812 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11814 /* case WM_WININICHANGE: */
11816 default:
11817 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11818 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11820 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11825 /***
11826 * DESCRIPTION:
11827 * Registers the window class.
11829 * PARAMETER(S):
11830 * None
11832 * RETURN:
11833 * None
11835 void LISTVIEW_Register(void)
11837 WNDCLASSW wndClass;
11839 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11840 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11841 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11842 wndClass.cbClsExtra = 0;
11843 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11844 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11845 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11846 wndClass.lpszClassName = WC_LISTVIEWW;
11847 RegisterClassW(&wndClass);
11850 /***
11851 * DESCRIPTION:
11852 * Unregisters the window class.
11854 * PARAMETER(S):
11855 * None
11857 * RETURN:
11858 * None
11860 void LISTVIEW_Unregister(void)
11862 UnregisterClassW(WC_LISTVIEWW, NULL);
11865 /***
11866 * DESCRIPTION:
11867 * Handle any WM_COMMAND messages
11869 * PARAMETER(S):
11870 * [I] infoPtr : valid pointer to the listview structure
11871 * [I] wParam : the first message parameter
11872 * [I] lParam : the second message parameter
11874 * RETURN:
11875 * Zero.
11877 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11880 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11882 if (!infoPtr->hwndEdit) return 0;
11884 switch (HIWORD(wParam))
11886 case EN_UPDATE:
11889 * Adjust the edit window size
11891 WCHAR buffer[1024];
11892 HDC hdc = GetDC(infoPtr->hwndEdit);
11893 HFONT hFont, hOldFont = 0;
11894 RECT rect;
11895 SIZE sz;
11897 if (!infoPtr->hwndEdit || !hdc) return 0;
11898 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11899 GetWindowRect(infoPtr->hwndEdit, &rect);
11901 /* Select font to get the right dimension of the string */
11902 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11903 if (hFont)
11905 hOldFont = SelectObject(hdc, hFont);
11908 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11910 TEXTMETRICW textMetric;
11912 /* Add Extra spacing for the next character */
11913 GetTextMetricsW(hdc, &textMetric);
11914 sz.cx += (textMetric.tmMaxCharWidth * 2);
11916 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11917 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11919 if (hFont)
11920 SelectObject(hdc, hOldFont);
11922 ReleaseDC(infoPtr->hwndEdit, hdc);
11924 break;
11926 case EN_KILLFOCUS:
11928 LISTVIEW_CancelEditLabel(infoPtr);
11929 break;
11932 default:
11933 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
11936 return 0;