d3d9/tests: Add a test for cube texture mipmap autogeneration.
[wine.git] / dlls / comctl32 / listview.c
blob82d8ca4a970dd7def36c70faae8e2f7d2204f0c9
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 * TODO:
29 * Default Message Processing
30 * -- WM_CREATE: create the icon and small icon image lists at this point only if
31 * the LVS_SHAREIMAGELISTS style is not specified.
32 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
33 * or small icon and the LVS_AUTOARRANGE style is specified.
34 * -- WM_TIMER
35 * -- WM_WININICHANGE
37 * Features
38 * -- Hot item handling, mouse hovering
39 * -- Workareas support
40 * -- Tilemode support
41 * -- Groups support
43 * Bugs
44 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
45 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
46 * -- LVA_SNAPTOGRID not implemented
47 * -- LISTVIEW_ApproximateViewRect partially implemented
48 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
50 * Speedups
51 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
52 * linear in the number of items in the list, and this is
53 * unacceptable for large lists.
54 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
55 * binary search to calculate item index (e.g. DPA_Search()).
56 * This requires sorted state to be reliably tracked in item modifiers.
57 * -- we should keep an ordered array of coordinates in iconic mode.
58 * This would allow framing items (iterator_frameditems),
59 * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
61 * Flags
62 * -- LVIF_COLUMNS
63 * -- LVIF_GROUPID
65 * States
66 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
67 * -- LVIS_DROPHILITED
69 * Styles
70 * -- LVS_NOLABELWRAP
71 * -- LVS_NOSCROLL (see Q137520)
72 * -- LVS_ALIGNTOP
74 * Extended Styles
75 * -- LVS_EX_BORDERSELECT
76 * -- LVS_EX_FLATSB
77 * -- LVS_EX_INFOTIP
78 * -- LVS_EX_LABELTIP
79 * -- LVS_EX_MULTIWORKAREAS
80 * -- LVS_EX_REGIONAL
81 * -- LVS_EX_SIMPLESELECT
82 * -- LVS_EX_TWOCLICKACTIVATE
83 * -- LVS_EX_UNDERLINECOLD
84 * -- LVS_EX_UNDERLINEHOT
86 * Notifications:
87 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
88 * -- LVN_GETINFOTIP
89 * -- LVN_HOTTRACK
90 * -- LVN_SETDISPINFO
92 * Messages:
93 * -- LVM_ENABLEGROUPVIEW
94 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
95 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
96 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
97 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
98 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
99 * -- LVM_GETINSERTMARKRECT
100 * -- LVM_GETNUMBEROFWORKAREAS
101 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
102 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
103 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
104 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
105 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
106 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
107 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
108 * -- LVM_INSERTGROUPSORTED
109 * -- LVM_INSERTMARKHITTEST
110 * -- LVM_ISGROUPVIEWENABLED
111 * -- LVM_MOVEGROUP
112 * -- LVM_MOVEITEMTOGROUP
113 * -- LVM_SETINFOTIP
114 * -- LVM_SETTILEWIDTH
115 * -- LVM_SORTGROUPS
117 * Macros:
118 * -- ListView_GetHoverTime, ListView_SetHoverTime
119 * -- ListView_GetISearchString
120 * -- ListView_GetNumberOfWorkAreas
121 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
123 * Functions:
124 * -- LVGroupComparE
127 #include "config.h"
128 #include "wine/port.h"
130 #include <assert.h>
131 #include <ctype.h>
132 #include <string.h>
133 #include <stdlib.h>
134 #include <stdarg.h>
135 #include <stdio.h>
137 #include "windef.h"
138 #include "winbase.h"
139 #include "winnt.h"
140 #include "wingdi.h"
141 #include "winuser.h"
142 #include "winnls.h"
143 #include "commctrl.h"
144 #include "comctl32.h"
145 #include "uxtheme.h"
147 #include "wine/debug.h"
148 #include "wine/unicode.h"
150 WINE_DEFAULT_DEBUG_CHANNEL(listview);
152 typedef struct tagCOLUMN_INFO
154 RECT rcHeader; /* tracks the header's rectangle */
155 INT fmt; /* same as LVCOLUMN.fmt */
156 INT cxMin;
157 } COLUMN_INFO;
159 typedef struct tagITEMHDR
161 LPWSTR pszText;
162 INT iImage;
163 } ITEMHDR, *LPITEMHDR;
165 typedef struct tagSUBITEM_INFO
167 ITEMHDR hdr;
168 INT iSubItem;
169 } SUBITEM_INFO;
171 typedef struct tagITEM_ID ITEM_ID;
173 typedef struct tagITEM_INFO
175 ITEMHDR hdr;
176 UINT state;
177 LPARAM lParam;
178 INT iIndent;
179 ITEM_ID *id;
180 } ITEM_INFO;
182 struct tagITEM_ID
184 UINT id; /* item id */
185 HDPA item; /* link to item data */
188 typedef struct tagRANGE
190 INT lower;
191 INT upper;
192 } RANGE;
194 typedef struct tagRANGES
196 HDPA hdpa;
197 } *RANGES;
199 typedef struct tagITERATOR
201 INT nItem;
202 INT nSpecial;
203 RANGE range;
204 RANGES ranges;
205 INT index;
206 } ITERATOR;
208 typedef struct tagDELAYED_ITEM_EDIT
210 BOOL fEnabled;
211 INT iItem;
212 } DELAYED_ITEM_EDIT;
214 typedef struct tagLISTVIEW_INFO
216 /* control window */
217 HWND hwndSelf;
218 RECT rcList; /* This rectangle is really the window
219 * client rectangle possibly reduced by the
220 * horizontal scroll bar and/or header - see
221 * LISTVIEW_UpdateSize. This rectangle offset
222 * by the LISTVIEW_GetOrigin value is in
223 * client coordinates */
225 /* notification window */
226 SHORT notifyFormat;
227 HWND hwndNotify;
228 BOOL bDoChangeNotify; /* send change notification messages? */
229 UINT uCallbackMask;
231 /* tooltips */
232 HWND hwndToolTip;
234 /* items */
235 INT nItemCount; /* the number of items in the list */
236 HDPA hdpaItems; /* array ITEM_INFO pointers */
237 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
238 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
239 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
240 RANGES selectionRanges;
241 INT nSelectionMark; /* item to start next multiselection from */
242 INT nHotItem;
244 /* columns */
245 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
246 BOOL colRectsDirty; /* trigger column rectangles requery from header */
248 /* item metrics */
249 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
250 INT nItemHeight;
251 INT nItemWidth;
253 /* sorting */
254 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
255 LPARAM lParamSort;
257 /* style */
258 DWORD dwStyle; /* the cached window GWL_STYLE */
259 DWORD dwLvExStyle; /* extended listview style */
260 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
262 /* edit item */
263 HWND hwndEdit;
264 WNDPROC EditWndProc;
265 INT nEditLabelItem;
266 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
268 /* icons */
269 HIMAGELIST himlNormal;
270 HIMAGELIST himlSmall;
271 HIMAGELIST himlState;
272 SIZE iconSize;
273 BOOL autoSpacing;
274 SIZE iconSpacing;
275 SIZE iconStateSize;
276 POINT currIconPos; /* this is the position next icon will be placed */
278 /* header */
279 HWND hwndHeader;
280 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
282 /* marquee selection */
283 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
284 BOOL bScrolling;
285 RECT marqueeRect; /* absolute coordinates of marquee selection */
286 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
287 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
289 /* focus drawing */
290 BOOL bFocus; /* control has focus */
291 INT nFocusedItem;
292 RECT rcFocus; /* focus bounds */
294 /* colors */
295 HBRUSH hBkBrush;
296 COLORREF clrBk;
297 COLORREF clrText;
298 COLORREF clrTextBk;
300 /* font */
301 HFONT hDefaultFont;
302 HFONT hFont;
303 INT ntmHeight; /* Some cached metrics of the font used */
304 INT ntmMaxCharWidth; /* by the listview to draw items */
305 INT nEllipsisWidth;
307 /* mouse operation */
308 BOOL bLButtonDown;
309 BOOL bDragging;
310 POINT ptClickPos; /* point where the user clicked */
311 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
312 DWORD dwHoverTime;
313 HCURSOR hHotCursor;
314 INT cWheelRemainder;
316 /* keyboard operation */
317 DWORD lastKeyPressTimestamp;
318 WPARAM charCode;
319 INT nSearchParamLength;
320 WCHAR szSearchParam[ MAX_PATH ];
322 /* painting */
323 BOOL bIsDrawing; /* Drawing in progress */
324 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
325 BOOL redraw; /* WM_SETREDRAW switch */
327 /* misc */
328 DWORD iVersion; /* CCM_[G,S]ETVERSION */
329 } LISTVIEW_INFO;
332 * constants
334 /* How many we debug buffer to allocate */
335 #define DEBUG_BUFFERS 20
336 /* The size of a single debug buffer */
337 #define DEBUG_BUFFER_SIZE 256
339 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
340 #define SB_INTERNAL -1
342 /* maximum size of a label */
343 #define DISP_TEXT_SIZE 260
345 /* padding for items in list and small icon display modes */
346 #define WIDTH_PADDING 12
348 /* padding for items in list, report and small icon display modes */
349 #define HEIGHT_PADDING 1
351 /* offset of items in report display mode */
352 #define REPORT_MARGINX 2
354 /* padding for icon in large icon display mode
355 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
356 * that HITTEST will see.
357 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
358 * ICON_TOP_PADDING - sum of the two above.
359 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
360 * LABEL_HOR_PADDING - between text and sides of box
361 * LABEL_VERT_PADDING - between bottom of text and end of box
363 * ICON_LR_PADDING - additional width above icon size.
364 * ICON_LR_HALF - half of the above value
366 #define ICON_TOP_PADDING_NOTHITABLE 2
367 #define ICON_TOP_PADDING_HITABLE 2
368 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
369 #define ICON_BOTTOM_PADDING 4
370 #define LABEL_HOR_PADDING 5
371 #define LABEL_VERT_PADDING 7
372 #define ICON_LR_PADDING 16
373 #define ICON_LR_HALF (ICON_LR_PADDING/2)
375 /* default label width for items in list and small icon display modes */
376 #define DEFAULT_LABEL_WIDTH 40
377 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
378 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
380 /* default column width for items in list display mode */
381 #define DEFAULT_COLUMN_WIDTH 128
383 /* Size of "line" scroll for V & H scrolls */
384 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
386 /* Padding between image and label */
387 #define IMAGE_PADDING 2
389 /* Padding behind the label */
390 #define TRAILING_LABEL_PADDING 12
391 #define TRAILING_HEADER_PADDING 11
393 /* Border for the icon caption */
394 #define CAPTION_BORDER 2
396 /* Standard DrawText flags */
397 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
398 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
399 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
401 /* Image index from state */
402 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
404 /* The time in milliseconds to reset the search in the list */
405 #define KEY_DELAY 450
407 /* Dump the LISTVIEW_INFO structure to the debug channel */
408 #define LISTVIEW_DUMP(iP) do { \
409 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
410 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
411 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
412 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
413 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
414 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
415 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
416 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
417 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
418 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
419 } while(0)
421 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
424 * forward declarations
426 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
427 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
428 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
429 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
430 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
431 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
432 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
433 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
434 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
435 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
436 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
437 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
438 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
439 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
440 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
441 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
442 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
443 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
444 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
445 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
447 /******** Text handling functions *************************************/
449 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
450 * text string. The string may be ANSI or Unicode, in which case
451 * the boolean isW tells us the type of the string.
453 * The name of the function tell what type of strings it expects:
454 * W: Unicode, T: ANSI/Unicode - function of isW
457 static inline BOOL is_text(LPCWSTR text)
459 return text != NULL && text != LPSTR_TEXTCALLBACKW;
462 static inline int textlenT(LPCWSTR text, BOOL isW)
464 return !is_text(text) ? 0 :
465 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
468 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
470 if (isDestW)
471 if (isSrcW) lstrcpynW(dest, src, max);
472 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
473 else
474 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
475 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
478 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
480 LPWSTR wstr = (LPWSTR)text;
482 if (!isW && is_text(text))
484 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
485 wstr = Alloc(len * sizeof(WCHAR));
486 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
488 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
489 return wstr;
492 static inline void textfreeT(LPWSTR wstr, BOOL isW)
494 if (!isW && is_text(wstr)) Free (wstr);
498 * dest is a pointer to a Unicode string
499 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
501 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
503 BOOL bResult = TRUE;
505 if (src == LPSTR_TEXTCALLBACKW)
507 if (is_text(*dest)) Free(*dest);
508 *dest = LPSTR_TEXTCALLBACKW;
510 else
512 LPWSTR pszText = textdupTtoW(src, isW);
513 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
514 bResult = Str_SetPtrW(dest, pszText);
515 textfreeT(pszText, isW);
517 return bResult;
521 * compares a Unicode to a Unicode/ANSI text string
523 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
525 if (!aw) return bt ? -1 : 0;
526 if (!bt) return 1;
527 if (aw == LPSTR_TEXTCALLBACKW)
528 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
529 if (bt != LPSTR_TEXTCALLBACKW)
531 LPWSTR bw = textdupTtoW(bt, isW);
532 int r = bw ? lstrcmpW(aw, bw) : 1;
533 textfreeT(bw, isW);
534 return r;
537 return 1;
540 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
542 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
543 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
546 /******** Debugging functions *****************************************/
548 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
550 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
551 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
554 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
556 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
557 n = min(textlenT(text, isW), n);
558 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
561 static char* debug_getbuf(void)
563 static int index = 0;
564 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
565 return buffers[index++ % DEBUG_BUFFERS];
568 static inline const char* debugrange(const RANGE *lprng)
570 if (!lprng) return "(null)";
571 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
574 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
576 char* buf = debug_getbuf(), *text = buf;
577 int len, size = DEBUG_BUFFER_SIZE;
579 if (pScrollInfo == NULL) return "(null)";
580 len = snprintf(buf, size, "{cbSize=%u, ", pScrollInfo->cbSize);
581 if (len == -1) goto end;
582 buf += len; size -= len;
583 if (pScrollInfo->fMask & SIF_RANGE)
584 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
585 else len = 0;
586 if (len == -1) goto end;
587 buf += len; size -= len;
588 if (pScrollInfo->fMask & SIF_PAGE)
589 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
590 else len = 0;
591 if (len == -1) goto end;
592 buf += len; size -= len;
593 if (pScrollInfo->fMask & SIF_POS)
594 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
595 else len = 0;
596 if (len == -1) goto end;
597 buf += len; size -= len;
598 if (pScrollInfo->fMask & SIF_TRACKPOS)
599 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
600 else len = 0;
601 if (len == -1) goto end;
602 buf += len;
603 goto undo;
604 end:
605 buf = text + strlen(text);
606 undo:
607 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
608 return text;
611 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
613 if (!plvnm) return "(null)";
614 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
615 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
616 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
617 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
620 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
622 char* buf = debug_getbuf(), *text = buf;
623 int len, size = DEBUG_BUFFER_SIZE;
625 if (lpLVItem == NULL) return "(null)";
626 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
627 if (len == -1) goto end;
628 buf += len; size -= len;
629 if (lpLVItem->mask & LVIF_STATE)
630 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
631 else len = 0;
632 if (len == -1) goto end;
633 buf += len; size -= len;
634 if (lpLVItem->mask & LVIF_TEXT)
635 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
636 else len = 0;
637 if (len == -1) goto end;
638 buf += len; size -= len;
639 if (lpLVItem->mask & LVIF_IMAGE)
640 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
641 else len = 0;
642 if (len == -1) goto end;
643 buf += len; size -= len;
644 if (lpLVItem->mask & LVIF_PARAM)
645 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
646 else len = 0;
647 if (len == -1) goto end;
648 buf += len; size -= len;
649 if (lpLVItem->mask & LVIF_INDENT)
650 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
651 else len = 0;
652 if (len == -1) goto end;
653 buf += len;
654 goto undo;
655 end:
656 buf = text + strlen(text);
657 undo:
658 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
659 return text;
662 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
664 char* buf = debug_getbuf(), *text = buf;
665 int len, size = DEBUG_BUFFER_SIZE;
667 if (lpColumn == NULL) return "(null)";
668 len = snprintf(buf, size, "{");
669 if (len == -1) goto end;
670 buf += len; size -= len;
671 if (lpColumn->mask & LVCF_SUBITEM)
672 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
673 else len = 0;
674 if (len == -1) goto end;
675 buf += len; size -= len;
676 if (lpColumn->mask & LVCF_FMT)
677 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
678 else len = 0;
679 if (len == -1) goto end;
680 buf += len; size -= len;
681 if (lpColumn->mask & LVCF_WIDTH)
682 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
683 else len = 0;
684 if (len == -1) goto end;
685 buf += len; size -= len;
686 if (lpColumn->mask & LVCF_TEXT)
687 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
688 else len = 0;
689 if (len == -1) goto end;
690 buf += len; size -= len;
691 if (lpColumn->mask & LVCF_IMAGE)
692 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
693 else len = 0;
694 if (len == -1) goto end;
695 buf += len; size -= len;
696 if (lpColumn->mask & LVCF_ORDER)
697 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
698 else len = 0;
699 if (len == -1) goto end;
700 buf += len;
701 goto undo;
702 end:
703 buf = text + strlen(text);
704 undo:
705 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
706 return text;
709 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
711 if (!lpht) return "(null)";
713 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
714 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
717 /* Return the corresponding text for a given scroll value */
718 static inline LPCSTR debugscrollcode(int nScrollCode)
720 switch(nScrollCode)
722 case SB_LINELEFT: return "SB_LINELEFT";
723 case SB_LINERIGHT: return "SB_LINERIGHT";
724 case SB_PAGELEFT: return "SB_PAGELEFT";
725 case SB_PAGERIGHT: return "SB_PAGERIGHT";
726 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
727 case SB_THUMBTRACK: return "SB_THUMBTRACK";
728 case SB_ENDSCROLL: return "SB_ENDSCROLL";
729 case SB_INTERNAL: return "SB_INTERNAL";
730 default: return "unknown";
735 /******** Notification functions ************************************/
737 static int get_ansi_notification(UINT unicodeNotificationCode)
739 switch (unicodeNotificationCode)
741 case LVN_BEGINLABELEDITA:
742 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
743 case LVN_ENDLABELEDITA:
744 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
745 case LVN_GETDISPINFOA:
746 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
747 case LVN_SETDISPINFOA:
748 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
749 case LVN_ODFINDITEMA:
750 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
751 case LVN_GETINFOTIPA:
752 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
753 /* header forwards */
754 case HDN_TRACKA:
755 case HDN_TRACKW: return HDN_TRACKA;
756 case HDN_ENDTRACKA:
757 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
758 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
759 case HDN_ENDDRAG: return HDN_ENDDRAG;
760 case HDN_ITEMCHANGINGA:
761 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
762 case HDN_ITEMCHANGEDA:
763 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
764 case HDN_ITEMCLICKA:
765 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
766 case HDN_DIVIDERDBLCLICKA:
767 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
768 default: break;
770 FIXME("unknown notification %x\n", unicodeNotificationCode);
771 return unicodeNotificationCode;
774 /* forwards header notifications to listview parent */
775 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
777 LPCWSTR text = NULL, filter = NULL;
778 LRESULT ret;
779 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
781 /* on unicode format exit earlier */
782 if (infoPtr->notifyFormat == NFR_UNICODE)
783 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
784 (LPARAM)lpnmh);
786 /* header always supplies unicode notifications,
787 all we have to do is to convert strings to ANSI */
788 if (lpnmh->pitem)
790 /* convert item text */
791 if (lpnmh->pitem->mask & HDI_TEXT)
793 text = (LPCWSTR)lpnmh->pitem->pszText;
794 lpnmh->pitem->pszText = NULL;
795 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
797 /* convert filter text */
798 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
799 lpnmh->pitem->pvFilter)
801 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
802 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = NULL;
803 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
806 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
808 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
809 (LPARAM)lpnmh);
811 /* cleanup */
812 if(text)
814 Free(lpnmh->pitem->pszText);
815 lpnmh->pitem->pszText = (LPSTR)text;
817 if(filter)
819 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
820 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
823 return ret;
826 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
828 LRESULT result;
830 TRACE("(code=%d)\n", code);
832 pnmh->hwndFrom = infoPtr->hwndSelf;
833 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
834 pnmh->code = code;
835 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
837 TRACE(" <= %ld\n", result);
839 return result;
842 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
844 NMHDR nmh;
845 HWND hwnd = infoPtr->hwndSelf;
846 notify_hdr(infoPtr, code, &nmh);
847 return IsWindow(hwnd);
850 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
852 NMITEMACTIVATE nmia;
853 LVITEMW item;
855 nmia.uNewState = 0;
856 nmia.uOldState = 0;
857 nmia.uChanged = 0;
858 nmia.uKeyFlags = 0;
860 item.mask = LVIF_PARAM|LVIF_STATE;
861 item.iItem = htInfo->iItem;
862 item.iSubItem = 0;
863 item.stateMask = (UINT)-1;
864 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
865 nmia.lParam = item.lParam;
866 nmia.uOldState = item.state;
867 nmia.uNewState = item.state | LVIS_ACTIVATING;
868 nmia.uChanged = LVIF_STATE;
871 nmia.iItem = htInfo->iItem;
872 nmia.iSubItem = htInfo->iSubItem;
873 nmia.ptAction = htInfo->pt;
875 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
876 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
877 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
879 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
882 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
884 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
885 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
888 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
889 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
891 NMITEMACTIVATE nmia;
892 LVITEMW item;
893 HWND hwnd = infoPtr->hwndSelf;
894 LRESULT ret;
896 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
897 ZeroMemory(&nmia, sizeof(nmia));
898 nmia.iItem = lvht->iItem;
899 nmia.iSubItem = lvht->iSubItem;
900 nmia.ptAction = lvht->pt;
901 item.mask = LVIF_PARAM;
902 item.iItem = lvht->iItem;
903 item.iSubItem = 0;
904 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
905 ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
906 return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
909 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
911 NMLISTVIEW nmlv;
912 LVITEMW item;
913 HWND hwnd = infoPtr->hwndSelf;
915 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
916 nmlv.iItem = nItem;
917 item.mask = LVIF_PARAM;
918 item.iItem = nItem;
919 item.iSubItem = 0;
920 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
921 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
922 return IsWindow(hwnd);
926 Send notification. depends on dispinfoW having same
927 structure as dispinfoA.
928 infoPtr : listview struct
929 code : *Unicode* notification code
930 pdi : dispinfo structure (can be unicode or ansi)
931 isW : TRUE if dispinfo is Unicode
933 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
935 INT length = 0, ret_length;
936 LPWSTR buffer = NULL, ret_text;
937 BOOL return_ansi = FALSE;
938 BOOL return_unicode = FALSE;
939 BOOL ret;
941 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
943 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
944 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
947 ret_length = pdi->item.cchTextMax;
948 ret_text = pdi->item.pszText;
950 if (return_unicode || return_ansi)
952 if (code != LVN_GETDISPINFOW)
954 length = return_ansi ?
955 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
956 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
958 else
960 length = pdi->item.cchTextMax;
961 *pdi->item.pszText = 0; /* make sure we don't process garbage */
964 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
965 if (!buffer) return FALSE;
967 if (return_ansi)
968 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
969 buffer, length);
970 else
971 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
972 length, NULL, NULL);
974 pdi->item.pszText = buffer;
975 pdi->item.cchTextMax = length;
978 if (infoPtr->notifyFormat == NFR_ANSI)
979 code = get_ansi_notification(code);
981 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
982 ret = notify_hdr(infoPtr, code, &pdi->hdr);
983 TRACE(" resulting code=%d\n", pdi->hdr.code);
985 if (return_ansi || return_unicode)
987 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
989 strcpy((char*)ret_text, (char*)pdi->item.pszText);
991 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
993 strcpyW(ret_text, pdi->item.pszText);
995 else if (return_ansi) /* note : pointer can be changed by app ! */
997 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
998 ret_length, NULL, NULL);
1000 else
1001 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
1002 ret_text, ret_length);
1004 pdi->item.pszText = ret_text; /* restores our buffer */
1005 pdi->item.cchTextMax = ret_length;
1007 Free(buffer);
1008 return ret;
1011 /* if dispinfo holder changed notification code then convert */
1012 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1014 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1016 buffer = Alloc(length * sizeof(CHAR));
1017 if (!buffer) return FALSE;
1019 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1020 ret_length, NULL, NULL);
1022 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1023 Free(buffer);
1026 return ret;
1029 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1030 const RECT *rcBounds, const LVITEMW *lplvItem)
1032 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1033 lpnmlvcd->nmcd.hdc = hdc;
1034 lpnmlvcd->nmcd.rc = *rcBounds;
1035 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1036 lpnmlvcd->clrText = infoPtr->clrText;
1037 if (!lplvItem) return;
1038 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1039 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1040 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1041 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1042 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1043 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1046 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1048 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1049 DWORD result;
1051 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1052 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1053 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1054 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1055 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1056 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1057 return result;
1060 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1062 COLORREF backcolor, textcolor;
1064 /* apparently, for selected items, we have to override the returned values */
1065 if (!SubItem)
1067 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1069 if (infoPtr->bFocus)
1071 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1072 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1074 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1076 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1077 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1082 backcolor = lpnmlvcd->clrTextBk;
1083 textcolor = lpnmlvcd->clrText;
1085 if (backcolor == CLR_DEFAULT)
1086 backcolor = comctl32_color.clrWindow;
1087 if (textcolor == CLR_DEFAULT)
1088 textcolor = comctl32_color.clrWindowText;
1090 /* Set the text attributes */
1091 if (backcolor != CLR_NONE)
1093 SetBkMode(hdc, OPAQUE);
1094 SetBkColor(hdc, backcolor);
1096 else
1097 SetBkMode(hdc, TRANSPARENT);
1098 SetTextColor(hdc, textcolor);
1101 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1103 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1106 /* returns TRUE when repaint needed, FALSE otherwise */
1107 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1109 MEASUREITEMSTRUCT mis;
1110 mis.CtlType = ODT_LISTVIEW;
1111 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1112 mis.itemID = -1;
1113 mis.itemWidth = 0;
1114 mis.itemData = 0;
1115 mis.itemHeight= infoPtr->nItemHeight;
1116 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1117 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1119 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1120 return TRUE;
1122 return FALSE;
1125 /******** Item iterator functions **********************************/
1127 static RANGES ranges_create(int count);
1128 static void ranges_destroy(RANGES ranges);
1129 static BOOL ranges_add(RANGES ranges, RANGE range);
1130 static BOOL ranges_del(RANGES ranges, RANGE range);
1131 static void ranges_dump(RANGES ranges);
1133 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1135 RANGE range = { nItem, nItem + 1 };
1137 return ranges_add(ranges, range);
1140 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1142 RANGE range = { nItem, nItem + 1 };
1144 return ranges_del(ranges, range);
1147 /***
1148 * ITERATOR DOCUMENTATION
1150 * The iterator functions allow for easy, and convenient iteration
1151 * over items of interest in the list. Typically, you create an
1152 * iterator, use it, and destroy it, as such:
1153 * ITERATOR i;
1155 * iterator_xxxitems(&i, ...);
1156 * while (iterator_{prev,next}(&i)
1158 * //code which uses i.nItem
1160 * iterator_destroy(&i);
1162 * where xxx is either: framed, or visible.
1163 * Note that it is important that the code destroys the iterator
1164 * after it's done with it, as the creation of the iterator may
1165 * allocate memory, which thus needs to be freed.
1167 * You can iterate both forwards, and backwards through the list,
1168 * by using iterator_next or iterator_prev respectively.
1170 * Lower numbered items are draw on top of higher number items in
1171 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1172 * items may overlap). So, to test items, you should use
1173 * iterator_next
1174 * which lists the items top to bottom (in Z-order).
1175 * For drawing items, you should use
1176 * iterator_prev
1177 * which lists the items bottom to top (in Z-order).
1178 * If you keep iterating over the items after the end-of-items
1179 * marker (-1) is returned, the iterator will start from the
1180 * beginning. Typically, you don't need to test for -1,
1181 * because iterator_{next,prev} will return TRUE if more items
1182 * are to be iterated over, or FALSE otherwise.
1184 * Note: the iterator is defined to be bidirectional. That is,
1185 * any number of prev followed by any number of next, or
1186 * five versa, should leave the iterator at the same item:
1187 * prev * n, next * n = next * n, prev * n
1189 * The iterator has a notion of an out-of-order, special item,
1190 * which sits at the start of the list. This is used in
1191 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1192 * which needs to be first, as it may overlap other items.
1194 * The code is a bit messy because we have:
1195 * - a special item to deal with
1196 * - simple range, or composite range
1197 * - empty range.
1198 * If you find bugs, or want to add features, please make sure you
1199 * always check/modify *both* iterator_prev, and iterator_next.
1202 /****
1203 * This function iterates through the items in increasing order,
1204 * but prefixed by the special item, then -1. That is:
1205 * special, 1, 2, 3, ..., n, -1.
1206 * Each item is listed only once.
1208 static inline BOOL iterator_next(ITERATOR* i)
1210 if (i->nItem == -1)
1212 i->nItem = i->nSpecial;
1213 if (i->nItem != -1) return TRUE;
1215 if (i->nItem == i->nSpecial)
1217 if (i->ranges) i->index = 0;
1218 goto pickarange;
1221 i->nItem++;
1222 testitem:
1223 if (i->nItem == i->nSpecial) i->nItem++;
1224 if (i->nItem < i->range.upper) return TRUE;
1226 pickarange:
1227 if (i->ranges)
1229 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1230 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1231 else goto end;
1233 else if (i->nItem >= i->range.upper) goto end;
1235 i->nItem = i->range.lower;
1236 if (i->nItem >= 0) goto testitem;
1237 end:
1238 i->nItem = -1;
1239 return FALSE;
1242 /****
1243 * This function iterates through the items in decreasing order,
1244 * followed by the special item, then -1. That is:
1245 * n, n-1, ..., 3, 2, 1, special, -1.
1246 * Each item is listed only once.
1248 static inline BOOL iterator_prev(ITERATOR* i)
1250 BOOL start = FALSE;
1252 if (i->nItem == -1)
1254 start = TRUE;
1255 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1256 goto pickarange;
1258 if (i->nItem == i->nSpecial)
1260 i->nItem = -1;
1261 return FALSE;
1264 testitem:
1265 i->nItem--;
1266 if (i->nItem == i->nSpecial) i->nItem--;
1267 if (i->nItem >= i->range.lower) return TRUE;
1269 pickarange:
1270 if (i->ranges)
1272 if (i->index > 0)
1273 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1274 else goto end;
1276 else if (!start && i->nItem < i->range.lower) goto end;
1278 i->nItem = i->range.upper;
1279 if (i->nItem > 0) goto testitem;
1280 end:
1281 return (i->nItem = i->nSpecial) != -1;
1284 static RANGE iterator_range(const ITERATOR *i)
1286 RANGE range;
1288 if (!i->ranges) return i->range;
1290 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1292 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1293 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1295 else range.lower = range.upper = 0;
1297 return range;
1300 /***
1301 * Releases resources associated with this iterator.
1303 static inline void iterator_destroy(const ITERATOR *i)
1305 ranges_destroy(i->ranges);
1308 /***
1309 * Create an empty iterator.
1311 static inline BOOL iterator_empty(ITERATOR* i)
1313 ZeroMemory(i, sizeof(*i));
1314 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1315 return TRUE;
1318 /***
1319 * Create an iterator over a range.
1321 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1323 iterator_empty(i);
1324 i->range = range;
1325 return TRUE;
1328 /***
1329 * Create an iterator over a bunch of ranges.
1330 * Please note that the iterator will take ownership of the ranges,
1331 * and will free them upon destruction.
1333 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1335 iterator_empty(i);
1336 i->ranges = ranges;
1337 return TRUE;
1340 /***
1341 * Creates an iterator over the items which intersect frame.
1342 * Uses absolute coordinates rather than compensating for the current offset.
1344 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1346 RECT rcItem, rcTemp;
1348 /* in case we fail, we want to return an empty iterator */
1349 if (!iterator_empty(i)) return FALSE;
1351 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1353 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1355 INT nItem;
1357 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1359 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1360 if (IntersectRect(&rcTemp, &rcItem, frame))
1361 i->nSpecial = infoPtr->nFocusedItem;
1363 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1364 /* to do better here, we need to have PosX, and PosY sorted */
1365 TRACE("building icon ranges:\n");
1366 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1368 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1369 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1370 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1371 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1372 if (IntersectRect(&rcTemp, &rcItem, frame))
1373 ranges_additem(i->ranges, nItem);
1375 return TRUE;
1377 else if (infoPtr->uView == LV_VIEW_DETAILS)
1379 RANGE range;
1381 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1382 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1384 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1385 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1386 if (range.upper <= range.lower) return TRUE;
1387 if (!iterator_rangeitems(i, range)) return FALSE;
1388 TRACE(" report=%s\n", debugrange(&i->range));
1390 else
1392 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1393 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1394 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1395 INT nFirstCol;
1396 INT nLastCol;
1397 INT lower;
1398 RANGE item_range;
1399 INT nCol;
1401 if (infoPtr->nItemWidth)
1403 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1404 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1406 else
1408 nFirstCol = max(frame->left, 0);
1409 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1412 lower = nFirstCol * nPerCol + nFirstRow;
1414 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1415 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1417 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1419 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1420 TRACE("building list ranges:\n");
1421 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1423 item_range.lower = nCol * nPerCol + nFirstRow;
1424 if(item_range.lower >= infoPtr->nItemCount) break;
1425 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1426 TRACE(" list=%s\n", debugrange(&item_range));
1427 ranges_add(i->ranges, item_range);
1431 return TRUE;
1434 /***
1435 * Creates an iterator over the items which intersect lprc.
1437 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1439 RECT frame = *lprc;
1440 POINT Origin;
1442 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1444 LISTVIEW_GetOrigin(infoPtr, &Origin);
1445 OffsetRect(&frame, -Origin.x, -Origin.y);
1447 return iterator_frameditems_absolute(i, infoPtr, &frame);
1450 /***
1451 * Creates an iterator over the items which intersect the visible region of hdc.
1453 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1455 POINT Origin, Position;
1456 RECT rcItem, rcClip;
1457 INT rgntype;
1459 rgntype = GetClipBox(hdc, &rcClip);
1460 if (rgntype == NULLREGION) return iterator_empty(i);
1461 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1462 if (rgntype == SIMPLEREGION) return TRUE;
1464 /* first deal with the special item */
1465 if (i->nSpecial != -1)
1467 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1468 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1471 /* if we can't deal with the region, we'll just go with the simple range */
1472 LISTVIEW_GetOrigin(infoPtr, &Origin);
1473 TRACE("building visible range:\n");
1474 if (!i->ranges && i->range.lower < i->range.upper)
1476 if (!(i->ranges = ranges_create(50))) return TRUE;
1477 if (!ranges_add(i->ranges, i->range))
1479 ranges_destroy(i->ranges);
1480 i->ranges = 0;
1481 return TRUE;
1485 /* now delete the invisible items from the list */
1486 while(iterator_next(i))
1488 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1489 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1490 rcItem.top = Position.y + Origin.y;
1491 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1492 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1493 if (!RectVisible(hdc, &rcItem))
1494 ranges_delitem(i->ranges, i->nItem);
1496 /* the iterator should restart on the next iterator_next */
1497 TRACE("done\n");
1499 return TRUE;
1502 /* Remove common elements from two iterators */
1503 /* Passed iterators have to point on the first elements */
1504 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1506 if(!iter1->ranges || !iter2->ranges) {
1507 int lower, upper;
1509 if(iter1->ranges || iter2->ranges ||
1510 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1511 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1512 ERR("result is not a one range iterator\n");
1513 return FALSE;
1516 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1517 return TRUE;
1519 lower = iter1->range.lower;
1520 upper = iter1->range.upper;
1522 if(lower < iter2->range.lower)
1523 iter1->range.upper = iter2->range.lower;
1524 else if(upper > iter2->range.upper)
1525 iter1->range.lower = iter2->range.upper;
1526 else
1527 iter1->range.lower = iter1->range.upper = -1;
1529 if(iter2->range.lower < lower)
1530 iter2->range.upper = lower;
1531 else if(iter2->range.upper > upper)
1532 iter2->range.lower = upper;
1533 else
1534 iter2->range.lower = iter2->range.upper = -1;
1536 return TRUE;
1539 iterator_next(iter1);
1540 iterator_next(iter2);
1542 while(1) {
1543 if(iter1->nItem==-1 || iter2->nItem==-1)
1544 break;
1546 if(iter1->nItem == iter2->nItem) {
1547 int delete = iter1->nItem;
1549 iterator_prev(iter1);
1550 iterator_prev(iter2);
1551 ranges_delitem(iter1->ranges, delete);
1552 ranges_delitem(iter2->ranges, delete);
1553 iterator_next(iter1);
1554 iterator_next(iter2);
1555 } else if(iter1->nItem > iter2->nItem)
1556 iterator_next(iter2);
1557 else
1558 iterator_next(iter1);
1561 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1562 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1563 return TRUE;
1566 /******** Misc helper functions ************************************/
1568 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1569 WPARAM wParam, LPARAM lParam, BOOL isW)
1571 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1572 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1575 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1577 return (infoPtr->dwStyle & LVS_AUTOARRANGE) &&
1578 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1581 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1583 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1584 if(state == 1 || state == 2)
1586 LVITEMW lvitem;
1587 state ^= 3;
1588 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1589 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1590 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1594 /* this should be called after window style got updated,
1595 it used to reset view state to match current window style */
1596 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1598 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1600 case LVS_ICON:
1601 infoPtr->uView = LV_VIEW_ICON;
1602 break;
1603 case LVS_REPORT:
1604 infoPtr->uView = LV_VIEW_DETAILS;
1605 break;
1606 case LVS_SMALLICON:
1607 infoPtr->uView = LV_VIEW_SMALLICON;
1608 break;
1609 case LVS_LIST:
1610 infoPtr->uView = LV_VIEW_LIST;
1614 /* computes next item id value */
1615 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1617 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1619 if (count > 0)
1621 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1622 return lpID->id + 1;
1624 return 0;
1627 /******** Internal API functions ************************************/
1629 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1631 static COLUMN_INFO mainItem;
1633 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1634 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1636 /* update cached column rectangles */
1637 if (infoPtr->colRectsDirty)
1639 COLUMN_INFO *info;
1640 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1641 INT i;
1643 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1644 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1645 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1647 Ptr->colRectsDirty = FALSE;
1650 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1653 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1655 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1656 HINSTANCE hInst;
1658 if (infoPtr->hwndHeader) return 0;
1660 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1662 /* setup creation flags */
1663 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1664 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1666 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1668 /* create header */
1669 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1670 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1671 if (!infoPtr->hwndHeader) return -1;
1673 /* set header unicode format */
1674 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1676 /* set header font */
1677 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1679 /* set header image list */
1680 if (infoPtr->himlSmall)
1681 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1683 LISTVIEW_UpdateSize(infoPtr);
1685 return 0;
1688 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1690 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1693 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1695 return (infoPtr->uView == LV_VIEW_DETAILS ||
1696 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1697 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1700 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1702 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1705 /* used to handle collapse main item column case */
1706 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1708 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1709 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1712 /* Listview invalidation functions: use _only_ these functions to invalidate */
1714 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1716 return infoPtr->redraw;
1719 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1721 if(!is_redrawing(infoPtr)) return;
1722 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1723 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1726 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1728 RECT rcBox;
1730 if(!is_redrawing(infoPtr)) return;
1731 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1732 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1735 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1737 POINT Origin, Position;
1738 RECT rcBox;
1740 if(!is_redrawing(infoPtr)) return;
1741 assert (infoPtr->uView == LV_VIEW_DETAILS);
1742 LISTVIEW_GetOrigin(infoPtr, &Origin);
1743 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1744 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1745 rcBox.top = 0;
1746 rcBox.bottom = infoPtr->nItemHeight;
1747 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1748 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1751 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1753 LISTVIEW_InvalidateRect(infoPtr, NULL);
1756 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1758 RECT rcCol;
1760 if(!is_redrawing(infoPtr)) return;
1761 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1762 rcCol.top = infoPtr->rcList.top;
1763 rcCol.bottom = infoPtr->rcList.bottom;
1764 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1767 /***
1768 * DESCRIPTION:
1769 * Retrieves the number of items that can fit vertically in the client area.
1771 * PARAMETER(S):
1772 * [I] infoPtr : valid pointer to the listview structure
1774 * RETURN:
1775 * Number of items per row.
1777 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1779 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1781 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1784 /***
1785 * DESCRIPTION:
1786 * Retrieves the number of items that can fit horizontally in the client
1787 * area.
1789 * PARAMETER(S):
1790 * [I] infoPtr : valid pointer to the listview structure
1792 * RETURN:
1793 * Number of items per column.
1795 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1797 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1799 return max(nListHeight / infoPtr->nItemHeight, 1);
1803 /*************************************************************************
1804 * LISTVIEW_ProcessLetterKeys
1806 * Processes keyboard messages generated by pressing the letter keys
1807 * on the keyboard.
1808 * What this does is perform a case insensitive search from the
1809 * current position with the following quirks:
1810 * - If two chars or more are pressed in quick succession we search
1811 * for the corresponding string (e.g. 'abc').
1812 * - If there is a delay we wipe away the current search string and
1813 * restart with just that char.
1814 * - If the user keeps pressing the same character, whether slowly or
1815 * fast, so that the search string is entirely composed of this
1816 * character ('aaaaa' for instance), then we search for first item
1817 * that starting with that character.
1818 * - If the user types the above character in quick succession, then
1819 * we must also search for the corresponding string ('aaaaa'), and
1820 * go to that string if there is a match.
1822 * PARAMETERS
1823 * [I] hwnd : handle to the window
1824 * [I] charCode : the character code, the actual character
1825 * [I] keyData : key data
1827 * RETURNS
1829 * Zero.
1831 * BUGS
1833 * - The current implementation has a list of characters it will
1834 * accept and it ignores everything else. In particular it will
1835 * ignore accentuated characters which seems to match what
1836 * Windows does. But I'm not sure it makes sense to follow
1837 * Windows there.
1838 * - We don't sound a beep when the search fails.
1840 * SEE ALSO
1842 * TREEVIEW_ProcessLetterKeys
1844 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1846 WCHAR buffer[MAX_PATH];
1847 DWORD prevTime;
1848 LVITEMW item;
1849 int startidx;
1850 INT nItem;
1851 INT diff;
1853 /* simple parameter checking */
1854 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1856 /* only allow the valid WM_CHARs through */
1857 if (!isalnumW(charCode) &&
1858 charCode != '.' && charCode != '`' && charCode != '!' &&
1859 charCode != '@' && charCode != '#' && charCode != '$' &&
1860 charCode != '%' && charCode != '^' && charCode != '&' &&
1861 charCode != '*' && charCode != '(' && charCode != ')' &&
1862 charCode != '-' && charCode != '_' && charCode != '+' &&
1863 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1864 charCode != '}' && charCode != '[' && charCode != '{' &&
1865 charCode != '/' && charCode != '?' && charCode != '>' &&
1866 charCode != '<' && charCode != ',' && charCode != '~')
1867 return 0;
1869 /* update the search parameters */
1870 prevTime = infoPtr->lastKeyPressTimestamp;
1871 infoPtr->lastKeyPressTimestamp = GetTickCount();
1872 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1874 if (diff >= 0 && diff < KEY_DELAY)
1876 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1877 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1879 if (infoPtr->charCode != charCode)
1880 infoPtr->charCode = charCode = 0;
1882 else
1884 infoPtr->charCode = charCode;
1885 infoPtr->szSearchParam[0] = charCode;
1886 infoPtr->nSearchParamLength = 1;
1889 /* should start from next after focused item, so next item that matches
1890 will be selected, if there isn't any and focused matches it will be selected
1891 on second search stage from beginning of the list */
1892 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1894 /* with some accumulated search data available start with current focus, otherwise
1895 it's excluded from search */
1896 startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
1897 if (startidx == infoPtr->nItemCount) startidx = 0;
1899 else
1900 startidx = 0;
1902 /* let application handle this for virtual listview */
1903 if (infoPtr->dwStyle & LVS_OWNERDATA)
1905 NMLVFINDITEMW nmlv;
1907 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1908 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1909 nmlv.lvfi.psz = infoPtr->szSearchParam;
1910 nmlv.iStart = startidx;
1912 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1914 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1916 else
1918 int i = startidx, endidx;
1920 /* and search from the current position */
1921 nItem = -1;
1922 endidx = infoPtr->nItemCount;
1924 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1925 while (1)
1927 /* start from first item if not found with >= startidx */
1928 if (i == infoPtr->nItemCount && startidx > 0)
1930 endidx = startidx;
1931 startidx = 0;
1934 for (i = startidx; i < endidx; i++)
1936 /* retrieve text */
1937 item.mask = LVIF_TEXT;
1938 item.iItem = i;
1939 item.iSubItem = 0;
1940 item.pszText = buffer;
1941 item.cchTextMax = MAX_PATH;
1942 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1944 if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
1946 nItem = i;
1947 break;
1949 /* this is used to find first char match when search string is not available yet,
1950 otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1951 already waiting for user to complete a string */
1952 else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
1954 /* this would work but we must keep looking for a longer match */
1955 nItem = i;
1959 if ( nItem != -1 || /* found something */
1960 endidx != infoPtr->nItemCount || /* second search done */
1961 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1962 break;
1966 if (nItem != -1)
1967 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1969 return 0;
1972 /*************************************************************************
1973 * LISTVIEW_UpdateHeaderSize [Internal]
1975 * Function to resize the header control
1977 * PARAMS
1978 * [I] hwnd : handle to a window
1979 * [I] nNewScrollPos : scroll pos to set
1981 * RETURNS
1982 * None.
1984 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1986 RECT winRect;
1987 POINT point[2];
1989 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1991 if (!infoPtr->hwndHeader) return;
1993 GetWindowRect(infoPtr->hwndHeader, &winRect);
1994 point[0].x = winRect.left;
1995 point[0].y = winRect.top;
1996 point[1].x = winRect.right;
1997 point[1].y = winRect.bottom;
1999 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
2000 point[0].x = -nNewScrollPos;
2001 point[1].x += nNewScrollPos;
2003 SetWindowPos(infoPtr->hwndHeader,0,
2004 point[0].x,point[0].y,point[1].x,point[1].y,
2005 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
2006 SWP_NOZORDER | SWP_NOACTIVATE);
2009 static INT LISTVIEW_UpdateHScroll(LISTVIEW_INFO *infoPtr)
2011 SCROLLINFO horzInfo;
2012 INT dx;
2014 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2015 horzInfo.cbSize = sizeof(SCROLLINFO);
2016 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2018 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2019 if (infoPtr->uView == LV_VIEW_LIST)
2021 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2022 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2024 /* scroll by at least one column per page */
2025 if(horzInfo.nPage < infoPtr->nItemWidth)
2026 horzInfo.nPage = infoPtr->nItemWidth;
2028 if (infoPtr->nItemWidth)
2029 horzInfo.nPage /= infoPtr->nItemWidth;
2031 else if (infoPtr->uView == LV_VIEW_DETAILS)
2033 horzInfo.nMax = infoPtr->nItemWidth;
2035 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2037 RECT rcView;
2039 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2042 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2044 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2046 RECT rcHeader;
2047 INT index;
2049 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2050 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2052 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2053 horzInfo.nMax = rcHeader.right;
2054 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2058 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2059 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2060 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2061 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2062 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2064 /* Update the Header Control */
2065 if (infoPtr->hwndHeader)
2067 horzInfo.fMask = SIF_POS;
2068 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2069 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2072 LISTVIEW_UpdateSize(infoPtr);
2073 return dx;
2076 static INT LISTVIEW_UpdateVScroll(LISTVIEW_INFO *infoPtr)
2078 SCROLLINFO vertInfo;
2079 INT dy;
2081 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2082 vertInfo.cbSize = sizeof(SCROLLINFO);
2083 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2085 if (infoPtr->uView == LV_VIEW_DETAILS)
2087 vertInfo.nMax = infoPtr->nItemCount;
2089 /* scroll by at least one page */
2090 if(vertInfo.nPage < infoPtr->nItemHeight)
2091 vertInfo.nPage = infoPtr->nItemHeight;
2093 if (infoPtr->nItemHeight > 0)
2094 vertInfo.nPage /= infoPtr->nItemHeight;
2096 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2098 RECT rcView;
2100 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2103 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2104 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2105 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2106 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2107 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2109 LISTVIEW_UpdateSize(infoPtr);
2110 return dy;
2113 /***
2114 * DESCRIPTION:
2115 * Update the scrollbars. This function should be called whenever
2116 * the content, size or view changes.
2118 * PARAMETER(S):
2119 * [I] infoPtr : valid pointer to the listview structure
2121 * RETURN:
2122 * None
2124 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
2126 INT dx, dy, pass;
2128 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2130 /* Setting the horizontal scroll can change the listview size
2131 * (and potentially everything else) so we need to recompute
2132 * everything again for the vertical scroll and vice-versa
2134 for (dx = 0, dy = 0, pass = 0; pass <= 1; pass++)
2136 dx += LISTVIEW_UpdateHScroll(infoPtr);
2137 dy += LISTVIEW_UpdateVScroll(infoPtr);
2140 /* Change of the range may have changed the scroll pos. If so move the content */
2141 if (dx != 0 || dy != 0)
2143 RECT listRect;
2144 listRect = infoPtr->rcList;
2145 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2146 SW_ERASE | SW_INVALIDATE);
2151 /***
2152 * DESCRIPTION:
2153 * Shows/hides the focus rectangle.
2155 * PARAMETER(S):
2156 * [I] infoPtr : valid pointer to the listview structure
2157 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2159 * RETURN:
2160 * None
2162 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2164 HDC hdc;
2166 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2168 if (infoPtr->nFocusedItem < 0) return;
2170 /* we need some gymnastics in ICON mode to handle large items */
2171 if (infoPtr->uView == LV_VIEW_ICON)
2173 RECT rcBox;
2175 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2176 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2178 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2179 return;
2183 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2185 /* for some reason, owner draw should work only in report mode */
2186 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2188 DRAWITEMSTRUCT dis;
2189 LVITEMW item;
2191 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2192 HFONT hOldFont = SelectObject(hdc, hFont);
2194 item.iItem = infoPtr->nFocusedItem;
2195 item.iSubItem = 0;
2196 item.mask = LVIF_PARAM;
2197 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2199 ZeroMemory(&dis, sizeof(dis));
2200 dis.CtlType = ODT_LISTVIEW;
2201 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2202 dis.itemID = item.iItem;
2203 dis.itemAction = ODA_FOCUS;
2204 if (fShow) dis.itemState |= ODS_FOCUS;
2205 dis.hwndItem = infoPtr->hwndSelf;
2206 dis.hDC = hdc;
2207 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2208 dis.itemData = item.lParam;
2210 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2212 SelectObject(hdc, hOldFont);
2214 else
2215 LISTVIEW_InvalidateItem(infoPtr, infoPtr->nFocusedItem);
2217 done:
2218 ReleaseDC(infoPtr->hwndSelf, hdc);
2221 /***
2222 * Invalidates all visible selected items.
2224 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2226 ITERATOR i;
2228 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2229 while(iterator_next(&i))
2231 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2232 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2234 iterator_destroy(&i);
2238 /***
2239 * DESCRIPTION: [INTERNAL]
2240 * Computes an item's (left,top) corner, relative to rcView.
2241 * That is, the position has NOT been made relative to the Origin.
2242 * This is deliberate, to avoid computing the Origin over, and
2243 * over again, when this function is called in a loop. Instead,
2244 * one can factor the computation of the Origin before the loop,
2245 * and offset the value returned by this function, on every iteration.
2247 * PARAMETER(S):
2248 * [I] infoPtr : valid pointer to the listview structure
2249 * [I] nItem : item number
2250 * [O] lpptOrig : item top, left corner
2252 * RETURN:
2253 * None.
2255 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2257 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2259 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2261 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2262 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2264 else if (infoPtr->uView == LV_VIEW_LIST)
2266 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2267 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2268 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2270 else /* LV_VIEW_DETAILS */
2272 lpptPosition->x = REPORT_MARGINX;
2273 /* item is always at zero indexed column */
2274 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2275 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2276 lpptPosition->y = nItem * infoPtr->nItemHeight;
2280 /***
2281 * DESCRIPTION: [INTERNAL]
2282 * Compute the rectangles of an item. This is to localize all
2283 * the computations in one place. If you are not interested in some
2284 * of these values, simply pass in a NULL -- the function is smart
2285 * enough to compute only what's necessary. The function computes
2286 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2287 * one, the BOX rectangle. This rectangle is very cheap to compute,
2288 * and is guaranteed to contain all the other rectangles. Computing
2289 * the ICON rect is also cheap, but all the others are potentially
2290 * expensive. This gives an easy and effective optimization when
2291 * searching (like point inclusion, or rectangle intersection):
2292 * first test against the BOX, and if TRUE, test against the desired
2293 * rectangle.
2294 * If the function does not have all the necessary information
2295 * to computed the requested rectangles, will crash with a
2296 * failed assertion. This is done so we catch all programming
2297 * errors, given that the function is called only from our code.
2299 * We have the following 'special' meanings for a few fields:
2300 * * If LVIS_FOCUSED is set, we assume the item has the focus
2301 * This is important in ICON mode, where it might get a larger
2302 * then usual rectangle
2304 * Please note that subitem support works only in REPORT mode.
2306 * PARAMETER(S):
2307 * [I] infoPtr : valid pointer to the listview structure
2308 * [I] lpLVItem : item to compute the measures for
2309 * [O] lprcBox : ptr to Box rectangle
2310 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2311 * [0] lprcSelectBox : ptr to select box rectangle
2312 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2313 * [O] lprcIcon : ptr to Icon rectangle
2314 * Same as LVM_GETITEMRECT with LVIR_ICON
2315 * [O] lprcStateIcon: ptr to State Icon rectangle
2316 * [O] lprcLabel : ptr to Label rectangle
2317 * Same as LVM_GETITEMRECT with LVIR_LABEL
2319 * RETURN:
2320 * None.
2322 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2323 LPRECT lprcBox, LPRECT lprcSelectBox,
2324 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2326 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2327 RECT Box, SelectBox, Icon, Label;
2328 COLUMN_INFO *lpColumnInfo = NULL;
2329 SIZE labelSize = { 0, 0 };
2331 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2333 /* Be smart and try to figure out the minimum we have to do */
2334 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2335 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2337 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2338 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2340 if (lprcSelectBox) doSelectBox = TRUE;
2341 if (lprcLabel) doLabel = TRUE;
2342 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2343 if (doSelectBox)
2345 doIcon = TRUE;
2346 doLabel = TRUE;
2349 /************************************************************/
2350 /* compute the box rectangle (it should be cheap to do) */
2351 /************************************************************/
2352 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2353 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2355 if (lpLVItem->iSubItem)
2357 Box = lpColumnInfo->rcHeader;
2359 else
2361 Box.left = 0;
2362 Box.right = infoPtr->nItemWidth;
2364 Box.top = 0;
2365 Box.bottom = infoPtr->nItemHeight;
2367 /******************************************************************/
2368 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2369 /******************************************************************/
2370 if (doIcon)
2372 LONG state_width = 0;
2374 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2375 state_width = infoPtr->iconStateSize.cx;
2377 if (infoPtr->uView == LV_VIEW_ICON)
2379 Icon.left = Box.left + state_width;
2380 if (infoPtr->himlNormal)
2381 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2382 Icon.top = Box.top + ICON_TOP_PADDING;
2383 Icon.right = Icon.left;
2384 Icon.bottom = Icon.top;
2385 if (infoPtr->himlNormal)
2387 Icon.right += infoPtr->iconSize.cx;
2388 Icon.bottom += infoPtr->iconSize.cy;
2391 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2393 Icon.left = Box.left + state_width;
2395 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2397 /* we need the indent in report mode */
2398 assert(lpLVItem->mask & LVIF_INDENT);
2399 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2402 Icon.top = Box.top;
2403 Icon.right = Icon.left;
2404 if (infoPtr->himlSmall &&
2405 (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2406 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2407 Icon.right += infoPtr->iconSize.cx;
2408 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2410 if(lprcIcon) *lprcIcon = Icon;
2411 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2413 /* TODO: is this correct? */
2414 if (lprcStateIcon)
2416 lprcStateIcon->left = Icon.left - state_width;
2417 lprcStateIcon->right = Icon.left;
2418 lprcStateIcon->top = Icon.top;
2419 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2420 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2423 else Icon.right = 0;
2425 /************************************************************/
2426 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2427 /************************************************************/
2428 if (doLabel)
2430 /* calculate how far to the right can the label stretch */
2431 Label.right = Box.right;
2432 if (infoPtr->uView == LV_VIEW_DETAILS)
2434 if (lpLVItem->iSubItem == 0)
2436 /* we need a zero based rect here */
2437 Label = lpColumnInfo->rcHeader;
2438 OffsetRect(&Label, -Label.left, 0);
2442 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2444 labelSize.cx = infoPtr->nItemWidth;
2445 labelSize.cy = infoPtr->nItemHeight;
2446 goto calc_label;
2449 /* we need the text in non owner draw mode */
2450 assert(lpLVItem->mask & LVIF_TEXT);
2451 if (is_text(lpLVItem->pszText))
2453 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2454 HDC hdc = GetDC(infoPtr->hwndSelf);
2455 HFONT hOldFont = SelectObject(hdc, hFont);
2456 UINT uFormat;
2457 RECT rcText;
2459 /* compute rough rectangle where the label will go */
2460 SetRectEmpty(&rcText);
2461 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2462 rcText.bottom = infoPtr->nItemHeight;
2463 if (infoPtr->uView == LV_VIEW_ICON)
2464 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2466 /* now figure out the flags */
2467 if (infoPtr->uView == LV_VIEW_ICON)
2468 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2469 else
2470 uFormat = LV_SL_DT_FLAGS;
2472 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2474 if (rcText.right != rcText.left)
2475 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2477 labelSize.cy = rcText.bottom - rcText.top;
2479 SelectObject(hdc, hOldFont);
2480 ReleaseDC(infoPtr->hwndSelf, hdc);
2483 calc_label:
2484 if (infoPtr->uView == LV_VIEW_ICON)
2486 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2487 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2488 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2489 Label.right = Label.left + labelSize.cx;
2490 Label.bottom = Label.top + infoPtr->nItemHeight;
2491 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2493 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2494 labelSize.cy /= infoPtr->ntmHeight;
2495 labelSize.cy = max(labelSize.cy, 1);
2496 labelSize.cy *= infoPtr->ntmHeight;
2498 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2500 else if (infoPtr->uView == LV_VIEW_DETAILS)
2502 Label.left = Icon.right;
2503 Label.top = Box.top;
2504 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2505 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2506 Label.bottom = Label.top + infoPtr->nItemHeight;
2508 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2510 Label.left = Icon.right;
2511 Label.top = Box.top;
2512 Label.right = min(Label.left + labelSize.cx, Label.right);
2513 Label.bottom = Label.top + infoPtr->nItemHeight;
2516 if (lprcLabel) *lprcLabel = Label;
2517 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2520 /************************************************************/
2521 /* compute SELECT bounding box */
2522 /************************************************************/
2523 if (doSelectBox)
2525 if (infoPtr->uView == LV_VIEW_DETAILS)
2527 SelectBox.left = Icon.left;
2528 SelectBox.top = Box.top;
2529 SelectBox.bottom = Box.bottom;
2531 if (labelSize.cx)
2532 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2533 else
2534 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2536 else
2538 UnionRect(&SelectBox, &Icon, &Label);
2540 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2541 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2544 /* Fix the Box if necessary */
2545 if (lprcBox)
2547 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2548 else *lprcBox = Box;
2550 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2553 /***
2554 * DESCRIPTION: [INTERNAL]
2556 * PARAMETER(S):
2557 * [I] infoPtr : valid pointer to the listview structure
2558 * [I] nItem : item number
2559 * [O] lprcBox : ptr to Box rectangle
2561 * RETURN:
2562 * None.
2564 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2566 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2567 POINT Position, Origin;
2568 LVITEMW lvItem;
2570 LISTVIEW_GetOrigin(infoPtr, &Origin);
2571 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2573 /* Be smart and try to figure out the minimum we have to do */
2574 lvItem.mask = 0;
2575 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2576 lvItem.mask |= LVIF_TEXT;
2577 lvItem.iItem = nItem;
2578 lvItem.iSubItem = 0;
2579 lvItem.pszText = szDispText;
2580 lvItem.cchTextMax = DISP_TEXT_SIZE;
2581 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2582 if (infoPtr->uView == LV_VIEW_ICON)
2584 lvItem.mask |= LVIF_STATE;
2585 lvItem.stateMask = LVIS_FOCUSED;
2586 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2588 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2590 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2591 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2593 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2595 else
2596 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2599 /* LISTVIEW_MapIdToIndex helper */
2600 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2602 ITEM_ID *id1 = (ITEM_ID*)p1;
2603 ITEM_ID *id2 = (ITEM_ID*)p2;
2605 if (id1->id == id2->id) return 0;
2607 return (id1->id < id2->id) ? -1 : 1;
2610 /***
2611 * DESCRIPTION:
2612 * Returns the item index for id specified.
2614 * PARAMETER(S):
2615 * [I] infoPtr : valid pointer to the listview structure
2616 * [I] iID : item id to get index for
2618 * RETURN:
2619 * Item index, or -1 on failure.
2621 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2623 ITEM_ID ID;
2624 INT index;
2626 TRACE("iID=%d\n", iID);
2628 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2629 if (infoPtr->nItemCount == 0) return -1;
2631 ID.id = iID;
2632 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2634 if (index != -1)
2636 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2637 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2640 return -1;
2643 /***
2644 * DESCRIPTION:
2645 * Returns the item id for index given.
2647 * PARAMETER(S):
2648 * [I] infoPtr : valid pointer to the listview structure
2649 * [I] iItem : item index to get id for
2651 * RETURN:
2652 * Item id.
2654 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2656 ITEM_INFO *lpItem;
2657 HDPA hdpaSubItems;
2659 TRACE("iItem=%d\n", iItem);
2661 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2662 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2664 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2665 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2667 return lpItem->id->id;
2670 /***
2671 * DESCRIPTION:
2672 * Returns the current icon position, and advances it along the top.
2673 * The returned position is not offset by Origin.
2675 * PARAMETER(S):
2676 * [I] infoPtr : valid pointer to the listview structure
2677 * [O] lpPos : will get the current icon position
2679 * RETURN:
2680 * None
2682 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2684 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2686 *lpPos = infoPtr->currIconPos;
2688 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2689 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2691 infoPtr->currIconPos.x = 0;
2692 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2696 /***
2697 * DESCRIPTION:
2698 * Returns the current icon position, and advances it down the left edge.
2699 * The returned position is not offset by Origin.
2701 * PARAMETER(S):
2702 * [I] infoPtr : valid pointer to the listview structure
2703 * [O] lpPos : will get the current icon position
2705 * RETURN:
2706 * None
2708 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2710 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2712 *lpPos = infoPtr->currIconPos;
2714 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2715 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2717 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2718 infoPtr->currIconPos.y = 0;
2722 /***
2723 * DESCRIPTION:
2724 * Moves an icon to the specified position.
2725 * It takes care of invalidating the item, etc.
2727 * PARAMETER(S):
2728 * [I] infoPtr : valid pointer to the listview structure
2729 * [I] nItem : the item to move
2730 * [I] lpPos : the new icon position
2731 * [I] isNew : flags the item as being new
2733 * RETURN:
2734 * Success: TRUE
2735 * Failure: FALSE
2737 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2739 POINT old;
2741 if (!isNew)
2743 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2744 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2746 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2747 LISTVIEW_InvalidateItem(infoPtr, nItem);
2750 /* Allocating a POINTER for every item is too resource intensive,
2751 * so we'll keep the (x,y) in different arrays */
2752 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2753 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2755 LISTVIEW_InvalidateItem(infoPtr, nItem);
2757 return TRUE;
2760 /***
2761 * DESCRIPTION:
2762 * Arranges listview items in icon display mode.
2764 * PARAMETER(S):
2765 * [I] infoPtr : valid pointer to the listview structure
2766 * [I] nAlignCode : alignment code
2768 * RETURN:
2769 * SUCCESS : TRUE
2770 * FAILURE : FALSE
2772 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2774 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2775 POINT pos;
2776 INT i;
2778 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2780 TRACE("nAlignCode=%d\n", nAlignCode);
2782 if (nAlignCode == LVA_DEFAULT)
2784 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2785 else nAlignCode = LVA_ALIGNTOP;
2788 switch (nAlignCode)
2790 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2791 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2792 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2793 default: return FALSE;
2796 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2797 for (i = 0; i < infoPtr->nItemCount; i++)
2799 next_pos(infoPtr, &pos);
2800 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2803 return TRUE;
2806 /***
2807 * DESCRIPTION:
2808 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2809 * For LVS_REPORT always returns empty rectangle.
2811 * PARAMETER(S):
2812 * [I] infoPtr : valid pointer to the listview structure
2813 * [O] lprcView : bounding rectangle
2815 * RETURN:
2816 * SUCCESS : TRUE
2817 * FAILURE : FALSE
2819 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2821 INT i, x, y;
2823 SetRectEmpty(lprcView);
2825 switch (infoPtr->uView)
2827 case LV_VIEW_ICON:
2828 case LV_VIEW_SMALLICON:
2829 for (i = 0; i < infoPtr->nItemCount; i++)
2831 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2832 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2833 lprcView->right = max(lprcView->right, x);
2834 lprcView->bottom = max(lprcView->bottom, y);
2836 if (infoPtr->nItemCount > 0)
2838 lprcView->right += infoPtr->nItemWidth;
2839 lprcView->bottom += infoPtr->nItemHeight;
2841 break;
2843 case LV_VIEW_LIST:
2844 y = LISTVIEW_GetCountPerColumn(infoPtr);
2845 x = infoPtr->nItemCount / y;
2846 if (infoPtr->nItemCount % y) x++;
2847 lprcView->right = x * infoPtr->nItemWidth;
2848 lprcView->bottom = y * infoPtr->nItemHeight;
2849 break;
2853 /***
2854 * DESCRIPTION:
2855 * Retrieves the bounding rectangle of all the items.
2857 * PARAMETER(S):
2858 * [I] infoPtr : valid pointer to the listview structure
2859 * [O] lprcView : bounding rectangle
2861 * RETURN:
2862 * SUCCESS : TRUE
2863 * FAILURE : FALSE
2865 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2867 POINT ptOrigin;
2869 TRACE("(lprcView=%p)\n", lprcView);
2871 if (!lprcView) return FALSE;
2873 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2875 if (infoPtr->uView != LV_VIEW_DETAILS)
2877 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2878 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2881 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2883 return TRUE;
2886 /***
2887 * DESCRIPTION:
2888 * Retrieves the subitem pointer associated with the subitem index.
2890 * PARAMETER(S):
2891 * [I] hdpaSubItems : DPA handle for a specific item
2892 * [I] nSubItem : index of subitem
2894 * RETURN:
2895 * SUCCESS : subitem pointer
2896 * FAILURE : NULL
2898 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2900 SUBITEM_INFO *lpSubItem;
2901 INT i;
2903 /* we should binary search here if need be */
2904 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2906 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2907 if (lpSubItem->iSubItem == nSubItem)
2908 return lpSubItem;
2911 return NULL;
2915 /***
2916 * DESCRIPTION:
2917 * Calculates the desired item width.
2919 * PARAMETER(S):
2920 * [I] infoPtr : valid pointer to the listview structure
2922 * RETURN:
2923 * The desired item width.
2925 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2927 INT nItemWidth = 0;
2929 TRACE("uView=%d\n", infoPtr->uView);
2931 if (infoPtr->uView == LV_VIEW_ICON)
2932 nItemWidth = infoPtr->iconSpacing.cx;
2933 else if (infoPtr->uView == LV_VIEW_DETAILS)
2935 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2937 RECT rcHeader;
2938 INT index;
2940 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2941 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2943 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2944 nItemWidth = rcHeader.right;
2947 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2949 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2950 LVITEMW lvItem;
2951 INT i;
2953 lvItem.mask = LVIF_TEXT;
2954 lvItem.iSubItem = 0;
2956 for (i = 0; i < infoPtr->nItemCount; i++)
2958 lvItem.iItem = i;
2959 lvItem.pszText = szDispText;
2960 lvItem.cchTextMax = DISP_TEXT_SIZE;
2961 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2962 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2963 nItemWidth);
2966 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2967 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2969 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2972 return nItemWidth;
2975 /***
2976 * DESCRIPTION:
2977 * Calculates the desired item height.
2979 * PARAMETER(S):
2980 * [I] infoPtr : valid pointer to the listview structure
2982 * RETURN:
2983 * The desired item height.
2985 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2987 INT nItemHeight;
2989 TRACE("uView=%d\n", infoPtr->uView);
2991 if (infoPtr->uView == LV_VIEW_ICON)
2992 nItemHeight = infoPtr->iconSpacing.cy;
2993 else
2995 nItemHeight = infoPtr->ntmHeight;
2996 if (infoPtr->himlState)
2997 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2998 if (infoPtr->himlSmall)
2999 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
3000 nItemHeight += HEIGHT_PADDING;
3001 if (infoPtr->nMeasureItemHeight > 0)
3002 nItemHeight = infoPtr->nMeasureItemHeight;
3005 return max(nItemHeight, 1);
3008 /***
3009 * DESCRIPTION:
3010 * Updates the width, and height of an item.
3012 * PARAMETER(S):
3013 * [I] infoPtr : valid pointer to the listview structure
3015 * RETURN:
3016 * None.
3018 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
3020 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
3021 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
3025 /***
3026 * DESCRIPTION:
3027 * Retrieves and saves important text metrics info for the current
3028 * Listview font.
3030 * PARAMETER(S):
3031 * [I] infoPtr : valid pointer to the listview structure
3034 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3036 HDC hdc = GetDC(infoPtr->hwndSelf);
3037 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3038 HFONT hOldFont = SelectObject(hdc, hFont);
3039 TEXTMETRICW tm;
3040 SIZE sz;
3042 if (GetTextMetricsW(hdc, &tm))
3044 infoPtr->ntmHeight = tm.tmHeight;
3045 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3048 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3049 infoPtr->nEllipsisWidth = sz.cx;
3051 SelectObject(hdc, hOldFont);
3052 ReleaseDC(infoPtr->hwndSelf, hdc);
3054 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3057 /***
3058 * DESCRIPTION:
3059 * A compare function for ranges
3061 * PARAMETER(S)
3062 * [I] range1 : pointer to range 1;
3063 * [I] range2 : pointer to range 2;
3064 * [I] flags : flags
3066 * RETURNS:
3067 * > 0 : if range 1 > range 2
3068 * < 0 : if range 2 > range 1
3069 * = 0 : if range intersects range 2
3071 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3073 INT cmp;
3075 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3076 cmp = -1;
3077 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3078 cmp = 1;
3079 else
3080 cmp = 0;
3082 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3084 return cmp;
3087 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3089 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3091 INT i;
3092 RANGE *prev, *curr;
3094 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3095 assert (ranges);
3096 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3097 ranges_dump(ranges);
3098 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3100 prev = DPA_GetPtr(ranges->hdpa, 0);
3101 assert (prev->lower >= 0 && prev->lower < prev->upper);
3102 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3104 curr = DPA_GetPtr(ranges->hdpa, i);
3105 assert (prev->upper <= curr->lower);
3106 assert (curr->lower < curr->upper);
3107 prev = curr;
3110 TRACE("--- Done checking---\n");
3113 static RANGES ranges_create(int count)
3115 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3116 if (!ranges) return NULL;
3117 ranges->hdpa = DPA_Create(count);
3118 if (ranges->hdpa) return ranges;
3119 Free(ranges);
3120 return NULL;
3123 static void ranges_clear(RANGES ranges)
3125 INT i;
3127 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3128 Free(DPA_GetPtr(ranges->hdpa, i));
3129 DPA_DeleteAllPtrs(ranges->hdpa);
3133 static void ranges_destroy(RANGES ranges)
3135 if (!ranges) return;
3136 ranges_clear(ranges);
3137 DPA_Destroy(ranges->hdpa);
3138 Free(ranges);
3141 static RANGES ranges_clone(RANGES ranges)
3143 RANGES clone;
3144 INT i;
3146 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3148 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3150 RANGE *newrng = Alloc(sizeof(RANGE));
3151 if (!newrng) goto fail;
3152 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3153 if (!DPA_SetPtr(clone->hdpa, i, newrng))
3155 Free(newrng);
3156 goto fail;
3159 return clone;
3161 fail:
3162 TRACE ("clone failed\n");
3163 ranges_destroy(clone);
3164 return NULL;
3167 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3169 INT i;
3171 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3172 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3174 return ranges;
3177 static void ranges_dump(RANGES ranges)
3179 INT i;
3181 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3182 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3185 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3187 RANGE srchrng = { nItem, nItem + 1 };
3189 TRACE("(nItem=%d)\n", nItem);
3190 ranges_check(ranges, "before contain");
3191 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3194 static INT ranges_itemcount(RANGES ranges)
3196 INT i, count = 0;
3198 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3200 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3201 count += sel->upper - sel->lower;
3204 return count;
3207 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3209 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3210 INT index;
3212 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3213 if (index == -1) return TRUE;
3215 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3217 chkrng = DPA_GetPtr(ranges->hdpa, index);
3218 if (chkrng->lower >= nItem)
3219 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3220 if (chkrng->upper > nItem)
3221 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3223 return TRUE;
3226 static BOOL ranges_add(RANGES ranges, RANGE range)
3228 RANGE srchrgn;
3229 INT index;
3231 TRACE("(%s)\n", debugrange(&range));
3232 ranges_check(ranges, "before add");
3234 /* try find overlapping regions first */
3235 srchrgn.lower = range.lower - 1;
3236 srchrgn.upper = range.upper + 1;
3237 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3239 if (index == -1)
3241 RANGE *newrgn;
3243 TRACE("Adding new range\n");
3245 /* create the brand new range to insert */
3246 newrgn = Alloc(sizeof(RANGE));
3247 if(!newrgn) goto fail;
3248 *newrgn = range;
3250 /* figure out where to insert it */
3251 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3252 TRACE("index=%d\n", index);
3253 if (index == -1) index = 0;
3255 /* and get it over with */
3256 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3258 Free(newrgn);
3259 goto fail;
3262 else
3264 RANGE *chkrgn, *mrgrgn;
3265 INT fromindex, mergeindex;
3267 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3268 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3270 chkrgn->lower = min(range.lower, chkrgn->lower);
3271 chkrgn->upper = max(range.upper, chkrgn->upper);
3273 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3275 /* merge now common ranges */
3276 fromindex = 0;
3277 srchrgn.lower = chkrgn->lower - 1;
3278 srchrgn.upper = chkrgn->upper + 1;
3282 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3283 if (mergeindex == -1) break;
3284 if (mergeindex == index)
3286 fromindex = index + 1;
3287 continue;
3290 TRACE("Merge with index %i\n", mergeindex);
3292 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3293 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3294 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3295 Free(mrgrgn);
3296 DPA_DeletePtr(ranges->hdpa, mergeindex);
3297 if (mergeindex < index) index --;
3298 } while(1);
3301 ranges_check(ranges, "after add");
3302 return TRUE;
3304 fail:
3305 ranges_check(ranges, "failed add");
3306 return FALSE;
3309 static BOOL ranges_del(RANGES ranges, RANGE range)
3311 RANGE *chkrgn;
3312 INT index;
3314 TRACE("(%s)\n", debugrange(&range));
3315 ranges_check(ranges, "before del");
3317 /* we don't use DPAS_SORTED here, since we need *
3318 * to find the first overlapping range */
3319 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3320 while(index != -1)
3322 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3324 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3326 /* case 1: Same range */
3327 if ( (chkrgn->upper == range.upper) &&
3328 (chkrgn->lower == range.lower) )
3330 DPA_DeletePtr(ranges->hdpa, index);
3331 Free(chkrgn);
3332 break;
3334 /* case 2: engulf */
3335 else if ( (chkrgn->upper <= range.upper) &&
3336 (chkrgn->lower >= range.lower) )
3338 DPA_DeletePtr(ranges->hdpa, index);
3339 Free(chkrgn);
3341 /* case 3: overlap upper */
3342 else if ( (chkrgn->upper <= range.upper) &&
3343 (chkrgn->lower < range.lower) )
3345 chkrgn->upper = range.lower;
3347 /* case 4: overlap lower */
3348 else if ( (chkrgn->upper > range.upper) &&
3349 (chkrgn->lower >= range.lower) )
3351 chkrgn->lower = range.upper;
3352 break;
3354 /* case 5: fully internal */
3355 else
3357 RANGE *newrgn;
3359 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3360 newrgn->lower = chkrgn->lower;
3361 newrgn->upper = range.lower;
3362 chkrgn->lower = range.upper;
3363 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3365 Free(newrgn);
3366 goto fail;
3368 break;
3371 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3374 ranges_check(ranges, "after del");
3375 return TRUE;
3377 fail:
3378 ranges_check(ranges, "failed del");
3379 return FALSE;
3382 /***
3383 * DESCRIPTION:
3384 * Removes all selection ranges
3386 * Parameters(s):
3387 * [I] infoPtr : valid pointer to the listview structure
3388 * [I] toSkip : item range to skip removing the selection
3390 * RETURNS:
3391 * SUCCESS : TRUE
3392 * FAILURE : FALSE
3394 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3396 LVITEMW lvItem;
3397 ITERATOR i;
3398 RANGES clone;
3400 TRACE("()\n");
3402 lvItem.state = 0;
3403 lvItem.stateMask = LVIS_SELECTED;
3405 /* need to clone the DPA because callbacks can change it */
3406 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3407 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3408 while(iterator_next(&i))
3409 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3410 /* note that the iterator destructor will free the cloned range */
3411 iterator_destroy(&i);
3413 return TRUE;
3416 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3418 RANGES toSkip;
3420 if (!(toSkip = ranges_create(1))) return FALSE;
3421 if (nItem != -1) ranges_additem(toSkip, nItem);
3422 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3423 ranges_destroy(toSkip);
3424 return TRUE;
3427 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3429 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3432 /***
3433 * DESCRIPTION:
3434 * Retrieves the number of items that are marked as selected.
3436 * PARAMETER(S):
3437 * [I] infoPtr : valid pointer to the listview structure
3439 * RETURN:
3440 * Number of items selected.
3442 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3444 INT nSelectedCount = 0;
3446 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3448 INT i;
3449 for (i = 0; i < infoPtr->nItemCount; i++)
3451 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3452 nSelectedCount++;
3455 else
3456 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3458 TRACE("nSelectedCount=%d\n", nSelectedCount);
3459 return nSelectedCount;
3462 /***
3463 * DESCRIPTION:
3464 * Manages the item focus.
3466 * PARAMETER(S):
3467 * [I] infoPtr : valid pointer to the listview structure
3468 * [I] nItem : item index
3470 * RETURN:
3471 * TRUE : focused item changed
3472 * FALSE : focused item has NOT changed
3474 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3476 INT oldFocus = infoPtr->nFocusedItem;
3477 LVITEMW lvItem;
3479 if (nItem == infoPtr->nFocusedItem) return FALSE;
3481 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3482 lvItem.stateMask = LVIS_FOCUSED;
3483 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3485 return oldFocus != infoPtr->nFocusedItem;
3488 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3490 if (nShiftItem < nItem) return nShiftItem;
3492 if (nShiftItem > nItem) return nShiftItem + direction;
3494 if (direction > 0) return nShiftItem + direction;
3496 return min(nShiftItem, infoPtr->nItemCount - 1);
3499 /* This function updates focus index.
3501 Parameters:
3502 focus : current focus index
3503 item : index of item to be added/removed
3504 direction : add/remove flag
3506 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3508 BOOL old_change = infoPtr->bDoChangeNotify;
3510 infoPtr->bDoChangeNotify = FALSE;
3511 focus = shift_item(infoPtr, focus, item, direction);
3512 if (focus != infoPtr->nFocusedItem)
3513 LISTVIEW_SetItemFocus(infoPtr, focus);
3514 infoPtr->bDoChangeNotify = old_change;
3518 * DESCRIPTION:
3519 * Updates the various indices after an item has been inserted or deleted.
3521 * PARAMETER(S):
3522 * [I] infoPtr : valid pointer to the listview structure
3523 * [I] nItem : item index
3524 * [I] direction : Direction of shift, +1 or -1.
3526 * RETURN:
3527 * None
3529 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3531 TRACE("Shifting %i, %i steps\n", nItem, direction);
3533 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3534 assert(abs(direction) == 1);
3535 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3537 /* But we are not supposed to modify nHotItem! */
3541 * DESCRIPTION:
3542 * Adds a block of selections.
3544 * PARAMETER(S):
3545 * [I] infoPtr : valid pointer to the listview structure
3546 * [I] nItem : item index
3548 * RETURN:
3549 * Whether the window is still valid.
3551 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3553 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3554 INT nLast = max(infoPtr->nSelectionMark, nItem);
3555 HWND hwndSelf = infoPtr->hwndSelf;
3556 NMLVODSTATECHANGE nmlv;
3557 LVITEMW item;
3558 BOOL bOldChange;
3559 INT i;
3561 /* Temporarily disable change notification
3562 * If the control is LVS_OWNERDATA, we need to send
3563 * only one LVN_ODSTATECHANGED notification.
3564 * See MSDN documentation for LVN_ITEMCHANGED.
3566 bOldChange = infoPtr->bDoChangeNotify;
3567 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3569 if (nFirst == -1) nFirst = nItem;
3571 item.state = LVIS_SELECTED;
3572 item.stateMask = LVIS_SELECTED;
3574 for (i = nFirst; i <= nLast; i++)
3575 LISTVIEW_SetItemState(infoPtr,i,&item);
3577 ZeroMemory(&nmlv, sizeof(nmlv));
3578 nmlv.iFrom = nFirst;
3579 nmlv.iTo = nLast;
3580 nmlv.uOldState = 0;
3581 nmlv.uNewState = item.state;
3583 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3584 if (!IsWindow(hwndSelf))
3585 return FALSE;
3586 infoPtr->bDoChangeNotify = bOldChange;
3587 return TRUE;
3591 /***
3592 * DESCRIPTION:
3593 * Sets a single group selection.
3595 * PARAMETER(S):
3596 * [I] infoPtr : valid pointer to the listview structure
3597 * [I] nItem : item index
3599 * RETURN:
3600 * None
3602 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3604 RANGES selection;
3605 LVITEMW item;
3606 ITERATOR i;
3607 BOOL bOldChange;
3609 if (!(selection = ranges_create(100))) return;
3611 item.state = LVIS_SELECTED;
3612 item.stateMask = LVIS_SELECTED;
3614 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3616 if (infoPtr->nSelectionMark == -1)
3618 infoPtr->nSelectionMark = nItem;
3619 ranges_additem(selection, nItem);
3621 else
3623 RANGE sel;
3625 sel.lower = min(infoPtr->nSelectionMark, nItem);
3626 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3627 ranges_add(selection, sel);
3630 else
3632 RECT rcItem, rcSel, rcSelMark;
3633 POINT ptItem;
3635 rcItem.left = LVIR_BOUNDS;
3636 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3637 ranges_destroy (selection);
3638 return;
3640 rcSelMark.left = LVIR_BOUNDS;
3641 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3642 ranges_destroy (selection);
3643 return;
3645 UnionRect(&rcSel, &rcItem, &rcSelMark);
3646 iterator_frameditems(&i, infoPtr, &rcSel);
3647 while(iterator_next(&i))
3649 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3650 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3652 iterator_destroy(&i);
3655 /* disable per item notifications on LVS_OWNERDATA style
3656 FIXME: single LVN_ODSTATECHANGED should be used */
3657 bOldChange = infoPtr->bDoChangeNotify;
3658 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3660 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3663 iterator_rangesitems(&i, selection);
3664 while(iterator_next(&i))
3665 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3666 /* this will also destroy the selection */
3667 iterator_destroy(&i);
3669 infoPtr->bDoChangeNotify = bOldChange;
3671 LISTVIEW_SetItemFocus(infoPtr, nItem);
3674 /***
3675 * DESCRIPTION:
3676 * Sets a single selection.
3678 * PARAMETER(S):
3679 * [I] infoPtr : valid pointer to the listview structure
3680 * [I] nItem : item index
3682 * RETURN:
3683 * None
3685 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3687 LVITEMW lvItem;
3689 TRACE("nItem=%d\n", nItem);
3691 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3693 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3694 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3695 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3697 infoPtr->nSelectionMark = nItem;
3700 /***
3701 * DESCRIPTION:
3702 * Set selection(s) with keyboard.
3704 * PARAMETER(S):
3705 * [I] infoPtr : valid pointer to the listview structure
3706 * [I] nItem : item index
3707 * [I] space : VK_SPACE code sent
3709 * RETURN:
3710 * SUCCESS : TRUE (needs to be repainted)
3711 * FAILURE : FALSE (nothing has changed)
3713 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3715 /* FIXME: pass in the state */
3716 WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3717 WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3718 BOOL bResult = FALSE;
3720 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3721 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3723 bResult = TRUE;
3725 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3726 LISTVIEW_SetSelection(infoPtr, nItem);
3727 else
3729 if (wShift)
3730 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3731 else if (wCtrl)
3733 LVITEMW lvItem;
3734 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3735 lvItem.stateMask = LVIS_SELECTED;
3736 if (space)
3738 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3739 if (lvItem.state & LVIS_SELECTED)
3740 infoPtr->nSelectionMark = nItem;
3742 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3745 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3748 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3749 return bResult;
3752 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3754 LVHITTESTINFO lvHitTestInfo;
3756 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3757 lvHitTestInfo.pt.x = pt.x;
3758 lvHitTestInfo.pt.y = pt.y;
3760 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3762 lpLVItem->mask = LVIF_PARAM;
3763 lpLVItem->iItem = lvHitTestInfo.iItem;
3764 lpLVItem->iSubItem = 0;
3766 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3769 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3771 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3772 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3773 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3776 /***
3777 * DESCRIPTION:
3778 * Called when the mouse is being actively tracked and has hovered for a specified
3779 * amount of time
3781 * PARAMETER(S):
3782 * [I] infoPtr : valid pointer to the listview structure
3783 * [I] fwKeys : key indicator
3784 * [I] x,y : mouse position
3786 * RETURN:
3787 * 0 if the message was processed, non-zero if there was an error
3789 * INFO:
3790 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3791 * over the item for a certain period of time.
3794 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3796 NMHDR hdr;
3798 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3800 if (LISTVIEW_IsHotTracking(infoPtr))
3802 LVITEMW item;
3803 POINT pt;
3805 pt.x = x;
3806 pt.y = y;
3808 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3809 LISTVIEW_SetSelection(infoPtr, item.iItem);
3811 SetFocus(infoPtr->hwndSelf);
3814 return 0;
3817 #define SCROLL_LEFT 0x1
3818 #define SCROLL_RIGHT 0x2
3819 #define SCROLL_UP 0x4
3820 #define SCROLL_DOWN 0x8
3822 /***
3823 * DESCRIPTION:
3824 * Utility routine to draw and highlight items within a marquee selection rectangle.
3826 * PARAMETER(S):
3827 * [I] infoPtr : valid pointer to the listview structure
3828 * [I] coords_orig : original co-ordinates of the cursor
3829 * [I] coords_offs : offsetted coordinates of the cursor
3830 * [I] offset : offset amount
3831 * [I] scroll : Bitmask of which directions we should scroll, if at all
3833 * RETURN:
3834 * None.
3836 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3837 INT scroll)
3839 BOOL controlDown = FALSE;
3840 LVITEMW item;
3841 ITERATOR old_elems, new_elems;
3842 RECT rect;
3843 POINT coords_offs, offset;
3845 /* Ensure coordinates are within client bounds */
3846 coords_offs.x = max(min(coords_orig->x, infoPtr->rcList.right), 0);
3847 coords_offs.y = max(min(coords_orig->y, infoPtr->rcList.bottom), 0);
3849 /* Get offset */
3850 LISTVIEW_GetOrigin(infoPtr, &offset);
3852 /* Offset coordinates by the appropriate amount */
3853 coords_offs.x -= offset.x;
3854 coords_offs.y -= offset.y;
3856 if (coords_offs.x > infoPtr->marqueeOrigin.x)
3858 rect.left = infoPtr->marqueeOrigin.x;
3859 rect.right = coords_offs.x;
3861 else
3863 rect.left = coords_offs.x;
3864 rect.right = infoPtr->marqueeOrigin.x;
3867 if (coords_offs.y > infoPtr->marqueeOrigin.y)
3869 rect.top = infoPtr->marqueeOrigin.y;
3870 rect.bottom = coords_offs.y;
3872 else
3874 rect.top = coords_offs.y;
3875 rect.bottom = infoPtr->marqueeOrigin.y;
3878 /* Cancel out the old marquee rectangle and draw the new one */
3879 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3881 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3882 the cursor is further away */
3884 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3885 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3887 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3888 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3890 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3891 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3893 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3894 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3896 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3898 infoPtr->marqueeRect = rect;
3899 infoPtr->marqueeDrawRect = rect;
3900 OffsetRect(&infoPtr->marqueeDrawRect, offset.x, offset.y);
3902 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3903 iterator_remove_common_items(&old_elems, &new_elems);
3905 /* Iterate over no longer selected items */
3906 while (iterator_next(&old_elems))
3908 if (old_elems.nItem > -1)
3910 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3911 item.state = 0;
3912 else
3913 item.state = LVIS_SELECTED;
3915 item.stateMask = LVIS_SELECTED;
3917 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3920 iterator_destroy(&old_elems);
3923 /* Iterate over newly selected items */
3924 if (GetKeyState(VK_CONTROL) & 0x8000)
3925 controlDown = TRUE;
3927 while (iterator_next(&new_elems))
3929 if (new_elems.nItem > -1)
3931 /* If CTRL is pressed, invert. If not, always select the item. */
3932 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3933 item.state = 0;
3934 else
3935 item.state = LVIS_SELECTED;
3937 item.stateMask = LVIS_SELECTED;
3939 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3942 iterator_destroy(&new_elems);
3944 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3947 /***
3948 * DESCRIPTION:
3949 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3950 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3952 * PARAMETER(S):
3953 * [I] hwnd : Handle to the listview
3954 * [I] uMsg : WM_TIMER (ignored)
3955 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3956 * [I] dwTimer : The elapsed time (ignored)
3958 * RETURN:
3959 * None.
3961 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3963 LISTVIEW_INFO *infoPtr;
3964 SCROLLINFO scrollInfo;
3965 POINT coords;
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);
3975 ScreenToClient(hWnd, &coords);
3977 scrollInfo.cbSize = sizeof(SCROLLINFO);
3978 scrollInfo.fMask = SIF_ALL;
3980 /* Work out in which directions we can scroll */
3981 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3983 if (scrollInfo.nPos != scrollInfo.nMin)
3984 scroll |= SCROLL_UP;
3986 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3987 scroll |= SCROLL_DOWN;
3990 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3992 if (scrollInfo.nPos != scrollInfo.nMin)
3993 scroll |= SCROLL_LEFT;
3995 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3996 scroll |= SCROLL_RIGHT;
3999 if (((coords.x <= 0) && (scroll & SCROLL_LEFT)) ||
4000 ((coords.y <= 0) && (scroll & SCROLL_UP)) ||
4001 ((coords.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
4002 ((coords.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
4004 LISTVIEW_MarqueeHighlight(infoPtr, &coords, scroll);
4008 /***
4009 * DESCRIPTION:
4010 * Called whenever WM_MOUSEMOVE is received.
4012 * PARAMETER(S):
4013 * [I] infoPtr : valid pointer to the listview structure
4014 * [I] fwKeys : key indicator
4015 * [I] x,y : mouse position
4017 * RETURN:
4018 * 0 if the message is processed, non-zero if there was an error
4020 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
4022 LVHITTESTINFO ht;
4023 RECT rect;
4024 POINT pt;
4026 pt.x = x;
4027 pt.y = y;
4029 if (!(fwKeys & MK_LBUTTON))
4030 infoPtr->bLButtonDown = FALSE;
4032 if (infoPtr->bLButtonDown)
4034 rect.left = rect.right = infoPtr->ptClickPos.x;
4035 rect.top = rect.bottom = infoPtr->ptClickPos.y;
4037 InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
4039 if (infoPtr->bMarqueeSelect)
4041 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4042 move the mouse again */
4044 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4045 (y >= infoPtr->rcList.bottom))
4047 if (!infoPtr->bScrolling)
4049 infoPtr->bScrolling = TRUE;
4050 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4053 else
4055 infoPtr->bScrolling = FALSE;
4056 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4059 LISTVIEW_MarqueeHighlight(infoPtr, &pt, 0);
4060 return 0;
4063 ht.pt = pt;
4064 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4066 /* reset item marker */
4067 if (infoPtr->nLButtonDownItem != ht.iItem)
4068 infoPtr->nLButtonDownItem = -1;
4070 if (!PtInRect(&rect, pt))
4072 /* this path covers the following:
4073 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4074 2. change focus with keys
4075 3. move mouse over item from step 1 selects it and moves focus on it */
4076 if (infoPtr->nLButtonDownItem != -1 &&
4077 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4079 LVITEMW lvItem;
4081 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4082 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4084 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4085 infoPtr->nLButtonDownItem = -1;
4088 if (!infoPtr->bDragging)
4090 ht.pt = infoPtr->ptClickPos;
4091 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4093 /* If the click is outside the range of an item, begin a
4094 highlight. If not, begin an item drag. */
4095 if (ht.iItem == -1)
4097 NMHDR hdr;
4099 /* If we're allowing multiple selections, send notification.
4100 If return value is non-zero, cancel. */
4101 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4103 /* Store the absolute coordinates of the click */
4104 POINT offset;
4105 LISTVIEW_GetOrigin(infoPtr, &offset);
4107 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4108 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4110 /* Begin selection and capture mouse */
4111 infoPtr->bMarqueeSelect = TRUE;
4112 SetCapture(infoPtr->hwndSelf);
4115 else
4117 NMLISTVIEW nmlv;
4119 ZeroMemory(&nmlv, sizeof(nmlv));
4120 nmlv.iItem = ht.iItem;
4121 nmlv.ptAction = infoPtr->ptClickPos;
4123 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4124 infoPtr->bDragging = TRUE;
4128 return 0;
4132 /* see if we are supposed to be tracking mouse hovering */
4133 if (LISTVIEW_IsHotTracking(infoPtr)) {
4134 TRACKMOUSEEVENT trackinfo;
4135 DWORD flags;
4137 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4138 trackinfo.dwFlags = TME_QUERY;
4140 /* see if we are already tracking this hwnd */
4141 _TrackMouseEvent(&trackinfo);
4143 flags = TME_LEAVE;
4144 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4145 flags |= TME_HOVER;
4147 if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4148 trackinfo.dwFlags = flags;
4149 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4150 trackinfo.hwndTrack = infoPtr->hwndSelf;
4152 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4153 _TrackMouseEvent(&trackinfo);
4157 return 0;
4161 /***
4162 * Tests whether the item is assignable to a list with style lStyle
4164 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4166 if ( (lpLVItem->mask & LVIF_TEXT) &&
4167 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4168 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4170 return TRUE;
4174 /***
4175 * DESCRIPTION:
4176 * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4178 * PARAMETER(S):
4179 * [I] infoPtr : valid pointer to the listview structure
4180 * [I] lpLVItem : valid pointer to new item attributes
4181 * [I] isNew : the item being set is being inserted
4182 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4183 * [O] bChanged : will be set to TRUE if the item really changed
4185 * RETURN:
4186 * SUCCESS : TRUE
4187 * FAILURE : FALSE
4189 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4191 ITEM_INFO *lpItem;
4192 NMLISTVIEW nmlv;
4193 UINT uChanged = 0;
4194 LVITEMW item;
4195 /* stateMask is ignored for LVM_INSERTITEM */
4196 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4198 TRACE("()\n");
4200 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4202 if (lpLVItem->mask == 0) return TRUE;
4204 if (infoPtr->dwStyle & LVS_OWNERDATA)
4206 /* a virtual listview only stores selection and focus */
4207 if (lpLVItem->mask & ~LVIF_STATE)
4208 return FALSE;
4209 lpItem = NULL;
4211 else
4213 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4214 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4215 assert (lpItem);
4218 /* we need to get the lParam and state of the item */
4219 item.iItem = lpLVItem->iItem;
4220 item.iSubItem = lpLVItem->iSubItem;
4221 item.mask = LVIF_STATE | LVIF_PARAM;
4222 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4224 item.state = 0;
4225 item.lParam = 0;
4226 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4228 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4229 /* determine what fields will change */
4230 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4231 uChanged |= LVIF_STATE;
4233 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4234 uChanged |= LVIF_IMAGE;
4236 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4237 uChanged |= LVIF_PARAM;
4239 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4240 uChanged |= LVIF_INDENT;
4242 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4243 uChanged |= LVIF_TEXT;
4245 TRACE("change mask=0x%x\n", uChanged);
4247 memset(&nmlv, 0, sizeof(NMLISTVIEW));
4248 nmlv.iItem = lpLVItem->iItem;
4249 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4250 nmlv.uOldState = item.state;
4251 nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4252 nmlv.lParam = item.lParam;
4254 /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4255 and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4256 are enabled. Even nothing really changed we still need to send this,
4257 in this case uChanged mask is just set to passed item mask. */
4258 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4260 HWND hwndSelf = infoPtr->hwndSelf;
4262 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4263 return FALSE;
4264 if (!IsWindow(hwndSelf))
4265 return FALSE;
4268 /* When item is inserted we need to shift existing focus index if new item has lower index. */
4269 if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4270 /* this means we won't hit a focus change path later */
4271 ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4273 if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4274 infoPtr->nFocusedItem++;
4277 if (!uChanged) return TRUE;
4278 *bChanged = TRUE;
4280 /* copy information */
4281 if (lpLVItem->mask & LVIF_TEXT)
4282 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4284 if (lpLVItem->mask & LVIF_IMAGE)
4285 lpItem->hdr.iImage = lpLVItem->iImage;
4287 if (lpLVItem->mask & LVIF_PARAM)
4288 lpItem->lParam = lpLVItem->lParam;
4290 if (lpLVItem->mask & LVIF_INDENT)
4291 lpItem->iIndent = lpLVItem->iIndent;
4293 if (uChanged & LVIF_STATE)
4295 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4297 lpItem->state &= ~stateMask;
4298 lpItem->state |= (lpLVItem->state & stateMask);
4300 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4302 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4303 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4305 else if (stateMask & LVIS_SELECTED)
4307 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4309 /* If we are asked to change focus, and we manage it, do it.
4310 It's important to have all new item data stored at this point,
4311 because changing existing focus could result in a redrawing operation,
4312 which in turn could ask for disp data, application should see all data
4313 for inserted item when processing LVN_GETDISPINFO.
4315 The way this works application will see nested item change notifications -
4316 changed item notifications interrupted by ones from item losing focus. */
4317 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4319 if (lpLVItem->state & LVIS_FOCUSED)
4321 /* update selection mark */
4322 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4323 infoPtr->nSelectionMark = lpLVItem->iItem;
4325 if (infoPtr->nFocusedItem != -1)
4327 /* remove current focus */
4328 item.mask = LVIF_STATE;
4329 item.state = 0;
4330 item.stateMask = LVIS_FOCUSED;
4332 /* recurse with redrawing an item */
4333 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4336 infoPtr->nFocusedItem = lpLVItem->iItem;
4337 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4339 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4341 infoPtr->nFocusedItem = -1;
4346 /* if we're inserting the item, we're done */
4347 if (isNew) return TRUE;
4349 /* send LVN_ITEMCHANGED notification */
4350 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4351 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4353 return TRUE;
4356 /***
4357 * DESCRIPTION:
4358 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4360 * PARAMETER(S):
4361 * [I] infoPtr : valid pointer to the listview structure
4362 * [I] lpLVItem : valid pointer to new subitem attributes
4363 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4364 * [O] bChanged : will be set to TRUE if the item really changed
4366 * RETURN:
4367 * SUCCESS : TRUE
4368 * FAILURE : FALSE
4370 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4372 HDPA hdpaSubItems;
4373 SUBITEM_INFO *lpSubItem;
4375 /* we do not support subitems for virtual listviews */
4376 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4378 /* set subitem only if column is present */
4379 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4381 /* First do some sanity checks */
4382 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4383 particularly useful. We currently do not actually do anything with
4384 the flag on subitems.
4386 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4387 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4389 /* get the subitem structure, and create it if not there */
4390 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4391 assert (hdpaSubItems);
4393 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4394 if (!lpSubItem)
4396 SUBITEM_INFO *tmpSubItem;
4397 INT i;
4399 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4400 if (!lpSubItem) return FALSE;
4401 /* we could binary search here, if need be...*/
4402 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4404 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4405 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4407 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4409 Free(lpSubItem);
4410 return FALSE;
4412 lpSubItem->iSubItem = lpLVItem->iSubItem;
4413 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4414 *bChanged = TRUE;
4417 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4419 lpSubItem->hdr.iImage = lpLVItem->iImage;
4420 *bChanged = TRUE;
4423 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4425 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4426 *bChanged = TRUE;
4429 return TRUE;
4432 /***
4433 * DESCRIPTION:
4434 * Sets item attributes.
4436 * PARAMETER(S):
4437 * [I] infoPtr : valid pointer to the listview structure
4438 * [I] lpLVItem : new item attributes
4439 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4441 * RETURN:
4442 * SUCCESS : TRUE
4443 * FAILURE : FALSE
4445 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4447 HWND hwndSelf = infoPtr->hwndSelf;
4448 LPWSTR pszText = NULL;
4449 BOOL bResult, bChanged = FALSE;
4450 RECT oldItemArea;
4452 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4454 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4455 return FALSE;
4457 /* Store old item area */
4458 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4460 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4461 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4463 pszText = lpLVItem->pszText;
4464 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4467 /* actually set the fields */
4468 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4470 if (lpLVItem->iSubItem)
4471 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4472 else
4473 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4474 if (!IsWindow(hwndSelf))
4475 return FALSE;
4477 /* redraw item, if necessary */
4478 if (bChanged && !infoPtr->bIsDrawing)
4480 /* this little optimization eliminates some nasty flicker */
4481 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4482 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4483 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4484 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4485 else
4487 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4488 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4491 /* restore text */
4492 if (pszText)
4494 textfreeT(lpLVItem->pszText, isW);
4495 lpLVItem->pszText = pszText;
4498 return bResult;
4501 /***
4502 * DESCRIPTION:
4503 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4505 * PARAMETER(S):
4506 * [I] infoPtr : valid pointer to the listview structure
4508 * RETURN:
4509 * item index
4511 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4513 INT nItem = 0;
4514 SCROLLINFO scrollInfo;
4516 scrollInfo.cbSize = sizeof(SCROLLINFO);
4517 scrollInfo.fMask = SIF_POS;
4519 if (infoPtr->uView == LV_VIEW_LIST)
4521 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4522 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4524 else if (infoPtr->uView == LV_VIEW_DETAILS)
4526 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4527 nItem = scrollInfo.nPos;
4529 else
4531 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4532 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4535 TRACE("nItem=%d\n", nItem);
4537 return nItem;
4541 /***
4542 * DESCRIPTION:
4543 * Erases the background of the given rectangle
4545 * PARAMETER(S):
4546 * [I] infoPtr : valid pointer to the listview structure
4547 * [I] hdc : device context handle
4548 * [I] lprcBox : clipping rectangle
4550 * RETURN:
4551 * Success: TRUE
4552 * Failure: FALSE
4554 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4556 if (!infoPtr->hBkBrush) return FALSE;
4558 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4560 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4563 /* Draw main item or subitem */
4564 static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const NMLVCUSTOMDRAW *nmlvcd, const POINT *pos)
4566 RECT rcSelect, rcLabel, rcBox, rcStateIcon, rcIcon;
4567 const RECT *background;
4568 HIMAGELIST himl;
4569 UINT format;
4570 RECT *focus;
4572 /* now check if we need to update the focus rectangle */
4573 focus = infoPtr->bFocus && (item->state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4574 if (!focus) item->state &= ~LVIS_FOCUSED;
4576 LISTVIEW_GetItemMetrics(infoPtr, item, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4577 OffsetRect(&rcBox, pos->x, pos->y);
4578 OffsetRect(&rcSelect, pos->x, pos->y);
4579 OffsetRect(&rcIcon, pos->x, pos->y);
4580 OffsetRect(&rcStateIcon, pos->x, pos->y);
4581 OffsetRect(&rcLabel, pos->x, pos->y);
4582 TRACE("%d: box=%s, select=%s, icon=%s. label=%s\n", item->iSubItem,
4583 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4584 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4586 /* FIXME: temporary hack */
4587 rcSelect.left = rcLabel.left;
4589 if (infoPtr->uView == LV_VIEW_DETAILS && item->iSubItem == 0)
4591 if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4592 OffsetRect(&rcSelect, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4593 OffsetRect(&rcIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4594 OffsetRect(&rcStateIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4595 OffsetRect(&rcLabel, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4598 /* in icon mode, the label rect is really what we want to draw the
4599 * background for */
4600 /* in detail mode, we want to paint background for label rect when
4601 * item is not selected or listview has full row select; otherwise paint
4602 * background for text only */
4603 if ( infoPtr->uView == LV_VIEW_ICON ||
4604 (infoPtr->uView == LV_VIEW_DETAILS && (!(item->state & LVIS_SELECTED) ||
4605 (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
4606 background = &rcLabel;
4607 else
4608 background = &rcSelect;
4610 if (nmlvcd->clrTextBk != CLR_NONE)
4611 ExtTextOutW(nmlvcd->nmcd.hdc, background->left, background->top, ETO_OPAQUE, background, NULL, 0, NULL);
4613 if (item->state & LVIS_FOCUSED)
4615 if (infoPtr->uView == LV_VIEW_DETAILS)
4617 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4619 /* we have to update left focus bound too if item isn't in leftmost column
4620 and reduce right box bound */
4621 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4623 INT leftmost;
4625 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4627 INT Originx = pos->x - LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left;
4628 INT rightmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4629 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4631 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, rightmost)->rcHeader.right + Originx;
4632 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4635 rcSelect.right = rcBox.right;
4637 infoPtr->rcFocus = rcSelect;
4639 else
4640 infoPtr->rcFocus = rcLabel;
4643 /* state icons */
4644 if (infoPtr->himlState && STATEIMAGEINDEX(item->state) && (item->iSubItem == 0))
4646 UINT stateimage = STATEIMAGEINDEX(item->state);
4647 if (stateimage)
4649 TRACE("stateimage=%d\n", stateimage);
4650 ImageList_Draw(infoPtr->himlState, stateimage-1, nmlvcd->nmcd.hdc, rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4654 /* item icons */
4655 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4656 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon))
4658 UINT style;
4660 TRACE("iImage=%d\n", item->iImage);
4662 if (item->state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4663 style = ILD_SELECTED;
4664 else
4665 style = ILD_NORMAL;
4667 ImageList_DrawEx(himl, item->iImage, nmlvcd->nmcd.hdc, rcIcon.left, rcIcon.top,
4668 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4669 item->state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4670 style | (item->state & LVIS_OVERLAYMASK));
4673 /* Don't bother painting item being edited */
4674 if (infoPtr->hwndEdit && item->iItem == infoPtr->nEditLabelItem && item->iSubItem == 0) return;
4676 /* figure out the text drawing flags */
4677 format = (infoPtr->uView == LV_VIEW_ICON ? (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4678 if (infoPtr->uView == LV_VIEW_ICON)
4679 format = (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4680 else if (item->iSubItem)
4682 switch (LISTVIEW_GetColumnInfo(infoPtr, item->iSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4684 case LVCFMT_RIGHT: format |= DT_RIGHT; break;
4685 case LVCFMT_CENTER: format |= DT_CENTER; break;
4686 default: format |= DT_LEFT;
4689 if (!(format & (DT_RIGHT | DT_CENTER)))
4691 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4692 else rcLabel.left += LABEL_HOR_PADDING;
4694 else if (format & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4696 /* for GRIDLINES reduce the bottom so the text formats correctly */
4697 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4698 rcLabel.bottom--;
4700 DrawTextW(nmlvcd->nmcd.hdc, item->pszText, -1, &rcLabel, format);
4703 /***
4704 * DESCRIPTION:
4705 * Draws an item.
4707 * PARAMETER(S):
4708 * [I] infoPtr : valid pointer to the listview structure
4709 * [I] hdc : device context handle
4710 * [I] nItem : item index
4711 * [I] nSubItem : subitem index
4712 * [I] pos : item position in client coordinates
4713 * [I] cdmode : custom draw mode
4715 * RETURN:
4716 * Success: TRUE
4717 * Failure: FALSE
4719 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERATOR *subitems, POINT pos, DWORD cdmode)
4721 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4722 static WCHAR callbackW[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4723 DWORD cdsubitemmode = CDRF_DODEFAULT;
4724 RECT *focus, rcBox;
4725 NMLVCUSTOMDRAW nmlvcd;
4726 LVITEMW lvItem;
4728 TRACE("(hdc=%p, nItem=%d, subitems=%p, pos=%s)\n", hdc, nItem, subitems, wine_dbgstr_point(&pos));
4730 /* get information needed for drawing the item */
4731 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
4732 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4733 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4734 lvItem.iItem = nItem;
4735 lvItem.iSubItem = 0;
4736 lvItem.state = 0;
4737 lvItem.lParam = 0;
4738 lvItem.cchTextMax = DISP_TEXT_SIZE;
4739 lvItem.pszText = szDispText;
4740 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4741 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4742 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4744 /* now check if we need to update the focus rectangle */
4745 focus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4746 if (!focus) lvItem.state &= ~LVIS_FOCUSED;
4748 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, NULL, NULL, NULL);
4749 OffsetRect(&rcBox, pos.x, pos.y);
4751 /* Full custom draw stage sequence looks like this:
4753 LV_VIEW_DETAILS:
4755 - CDDS_ITEMPREPAINT
4756 - CDDS_ITEMPREPAINT|CDDS_SUBITEM | => sent n times, where n is number of subitems,
4757 CDDS_ITEMPOSTPAINT|CDDS_SUBITEM | including item itself
4758 - CDDS_ITEMPOSTPAINT
4760 other styles:
4762 - CDDS_ITEMPREPAINT
4763 - CDDS_ITEMPOSTPAINT
4766 /* fill in the custom draw structure */
4767 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4768 if (cdmode & CDRF_NOTIFYITEMDRAW)
4769 cdsubitemmode = notify_customdraw(infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
4770 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4772 if (subitems)
4774 while (iterator_next(subitems))
4776 DWORD subitemstage = CDRF_DODEFAULT;
4778 /* We need to query for each subitem, item's data (subitem == 0) is already here at this point */
4779 if (subitems->nItem)
4781 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_INDENT;
4782 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4783 lvItem.iItem = nItem;
4784 lvItem.iSubItem = subitems->nItem;
4785 lvItem.state = 0;
4786 lvItem.lParam = 0;
4787 lvItem.cchTextMax = DISP_TEXT_SIZE;
4788 lvItem.pszText = szDispText;
4789 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4790 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4791 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4792 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4793 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4795 /* update custom draw data */
4796 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &nmlvcd.nmcd.rc, NULL, NULL, NULL, NULL);
4797 OffsetRect(&nmlvcd.nmcd.rc, pos.x, pos.y);
4798 nmlvcd.iSubItem = subitems->nItem;
4801 if (cdsubitemmode & CDRF_NOTIFYSUBITEMDRAW)
4802 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4803 else
4805 nmlvcd.clrTextBk = infoPtr->clrTextBk;
4806 nmlvcd.clrText = infoPtr->clrText;
4809 if (subitems->nItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4810 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4811 else if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4812 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4814 if (!(subitemstage & CDRF_SKIPDEFAULT))
4815 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4817 if (subitemstage & CDRF_NOTIFYPOSTPAINT)
4818 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPOSTPAINT, &nmlvcd);
4821 else
4823 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4824 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4827 postpaint:
4828 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4830 nmlvcd.iSubItem = 0;
4831 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
4834 return TRUE;
4837 /***
4838 * DESCRIPTION:
4839 * Draws listview items when in owner draw mode.
4841 * PARAMETER(S):
4842 * [I] infoPtr : valid pointer to the listview structure
4843 * [I] hdc : device context handle
4845 * RETURN:
4846 * None
4848 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4850 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4851 DWORD cditemmode = CDRF_DODEFAULT;
4852 NMLVCUSTOMDRAW nmlvcd;
4853 POINT Origin, Position;
4854 DRAWITEMSTRUCT dis;
4855 LVITEMW item;
4857 TRACE("()\n");
4859 ZeroMemory(&dis, sizeof(dis));
4861 /* Get scroll info once before loop */
4862 LISTVIEW_GetOrigin(infoPtr, &Origin);
4864 /* iterate through the invalidated rows */
4865 while(iterator_next(i))
4867 item.iItem = i->nItem;
4868 item.iSubItem = 0;
4869 item.mask = LVIF_PARAM | LVIF_STATE;
4870 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4871 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4873 dis.CtlType = ODT_LISTVIEW;
4874 dis.CtlID = uID;
4875 dis.itemID = item.iItem;
4876 dis.itemAction = ODA_DRAWENTIRE;
4877 dis.itemState = 0;
4878 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4879 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4880 dis.hwndItem = infoPtr->hwndSelf;
4881 dis.hDC = hdc;
4882 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4883 dis.rcItem.left = Position.x + Origin.x;
4884 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4885 dis.rcItem.top = Position.y + Origin.y;
4886 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4887 dis.itemData = item.lParam;
4889 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4892 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4893 * structure for the rest. of the paint cycle
4895 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4896 if (cdmode & CDRF_NOTIFYITEMDRAW)
4897 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4899 if (!(cditemmode & CDRF_SKIPDEFAULT))
4901 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4902 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4905 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4906 notify_postpaint(infoPtr, &nmlvcd);
4910 /***
4911 * DESCRIPTION:
4912 * Draws listview items when in report display mode.
4914 * PARAMETER(S):
4915 * [I] infoPtr : valid pointer to the listview structure
4916 * [I] hdc : device context handle
4917 * [I] cdmode : custom draw mode
4919 * RETURN:
4920 * None
4922 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4924 INT rgntype;
4925 RECT rcClip, rcItem;
4926 POINT Origin;
4927 RANGES colRanges;
4928 INT col;
4929 ITERATOR j;
4931 TRACE("()\n");
4933 /* figure out what to draw */
4934 rgntype = GetClipBox(hdc, &rcClip);
4935 if (rgntype == NULLREGION) return;
4937 /* Get scroll info once before loop */
4938 LISTVIEW_GetOrigin(infoPtr, &Origin);
4940 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4942 /* narrow down the columns we need to paint */
4943 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4945 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4947 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4948 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4949 ranges_additem(colRanges, index);
4951 iterator_rangesitems(&j, colRanges);
4953 /* in full row select, we _have_ to draw the main item */
4954 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4955 j.nSpecial = 0;
4957 /* iterate through the invalidated rows */
4958 while(iterator_next(i))
4960 RANGES subitems;
4961 POINT Position;
4962 ITERATOR k;
4964 SelectObject(hdc, infoPtr->hFont);
4965 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4966 Position.x = Origin.x;
4967 Position.y += Origin.y;
4969 subitems = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4971 /* iterate through the invalidated columns */
4972 while(iterator_next(&j))
4974 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
4976 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
4978 rcItem.top = 0;
4979 rcItem.bottom = infoPtr->nItemHeight;
4980 OffsetRect(&rcItem, Origin.x, Position.y);
4981 if (!RectVisible(hdc, &rcItem)) continue;
4984 ranges_additem(subitems, j.nItem);
4987 iterator_rangesitems(&k, subitems);
4988 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, &k, Position, cdmode);
4989 iterator_destroy(&k);
4991 iterator_destroy(&j);
4994 /***
4995 * DESCRIPTION:
4996 * Draws the gridlines if necessary when in report display mode.
4998 * PARAMETER(S):
4999 * [I] infoPtr : valid pointer to the listview structure
5000 * [I] hdc : device context handle
5002 * RETURN:
5003 * None
5005 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
5007 INT rgntype;
5008 INT y, itemheight;
5009 INT col, index;
5010 HPEN hPen, hOldPen;
5011 RECT rcClip, rcItem = {0};
5012 POINT Origin;
5013 RANGES colRanges;
5014 ITERATOR j;
5015 BOOL rmost = FALSE;
5017 TRACE("()\n");
5019 /* figure out what to draw */
5020 rgntype = GetClipBox(hdc, &rcClip);
5021 if (rgntype == NULLREGION) return;
5023 /* Get scroll info once before loop */
5024 LISTVIEW_GetOrigin(infoPtr, &Origin);
5026 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5028 /* narrow down the columns we need to paint */
5029 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
5031 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
5033 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5034 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
5035 ranges_additem(colRanges, index);
5038 /* is right most vertical line visible? */
5039 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
5041 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5042 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5043 rmost = (rcItem.right + Origin.x < rcClip.right);
5046 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
5048 hOldPen = SelectObject ( hdc, hPen );
5050 /* draw the vertical lines for the columns */
5051 iterator_rangesitems(&j, colRanges);
5052 while(iterator_next(&j))
5054 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5055 if (rcItem.left == 0) continue; /* skip leftmost column */
5056 rcItem.left += Origin.x;
5057 rcItem.right += Origin.x;
5058 rcItem.top = infoPtr->rcList.top;
5059 rcItem.bottom = infoPtr->rcList.bottom;
5060 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
5061 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5062 LineTo (hdc, rcItem.left, rcItem.bottom);
5064 iterator_destroy(&j);
5065 /* draw rightmost grid line if visible */
5066 if (rmost)
5068 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
5069 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5070 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5072 rcItem.right += Origin.x;
5074 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
5075 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
5078 /* draw the horizontal lines for the rows */
5079 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
5080 rcItem.left = infoPtr->rcList.left;
5081 rcItem.right = infoPtr->rcList.right;
5082 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
5084 rcItem.bottom = rcItem.top = y;
5085 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
5086 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5087 LineTo (hdc, rcItem.right, rcItem.top);
5090 SelectObject( hdc, hOldPen );
5091 DeleteObject( hPen );
5093 else
5094 ranges_destroy(colRanges);
5097 /***
5098 * DESCRIPTION:
5099 * Draws listview items when in list display mode.
5101 * PARAMETER(S):
5102 * [I] infoPtr : valid pointer to the listview structure
5103 * [I] hdc : device context handle
5104 * [I] cdmode : custom draw mode
5106 * RETURN:
5107 * None
5109 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
5111 POINT Origin, Position;
5113 /* Get scroll info once before loop */
5114 LISTVIEW_GetOrigin(infoPtr, &Origin);
5116 while(iterator_prev(i))
5118 SelectObject(hdc, infoPtr->hFont);
5119 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
5120 Position.x += Origin.x;
5121 Position.y += Origin.y;
5123 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, NULL, Position, cdmode);
5128 /***
5129 * DESCRIPTION:
5130 * Draws listview items.
5132 * PARAMETER(S):
5133 * [I] infoPtr : valid pointer to the listview structure
5134 * [I] hdc : device context handle
5135 * [I] prcErase : rect to be erased before refresh (may be NULL)
5137 * RETURN:
5138 * NoneX
5140 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5142 COLORREF oldTextColor = 0, oldBkColor = 0;
5143 NMLVCUSTOMDRAW nmlvcd;
5144 HFONT hOldFont = 0;
5145 DWORD cdmode;
5146 INT oldBkMode = 0;
5147 RECT rcClient;
5148 ITERATOR i;
5149 HDC hdcOrig = hdc;
5150 HBITMAP hbmp = NULL;
5151 RANGE range;
5153 LISTVIEW_DUMP(infoPtr);
5155 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5156 TRACE("double buffering\n");
5158 hdc = CreateCompatibleDC(hdcOrig);
5159 if (!hdc) {
5160 ERR("Failed to create DC for backbuffer\n");
5161 return;
5163 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5164 infoPtr->rcList.bottom);
5165 if (!hbmp) {
5166 ERR("Failed to create bitmap for backbuffer\n");
5167 DeleteDC(hdc);
5168 return;
5171 SelectObject(hdc, hbmp);
5172 SelectObject(hdc, infoPtr->hFont);
5174 if(GetClipBox(hdcOrig, &rcClient))
5175 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5176 } else {
5177 /* Save dc values we're gonna trash while drawing
5178 * FIXME: Should be done in LISTVIEW_DrawItem() */
5179 hOldFont = SelectObject(hdc, infoPtr->hFont);
5180 oldBkMode = GetBkMode(hdc);
5181 oldBkColor = GetBkColor(hdc);
5182 oldTextColor = GetTextColor(hdc);
5185 infoPtr->bIsDrawing = TRUE;
5187 if (prcErase) {
5188 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5189 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5190 /* If no erasing was done (usually because RedrawWindow was called
5191 * with RDW_INVALIDATE only) we need to copy the old contents into
5192 * the backbuffer before continuing. */
5193 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5194 infoPtr->rcList.right - infoPtr->rcList.left,
5195 infoPtr->rcList.bottom - infoPtr->rcList.top,
5196 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5199 GetClientRect(infoPtr->hwndSelf, &rcClient);
5200 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5201 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5202 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5204 /* nothing to draw */
5205 if(infoPtr->nItemCount == 0) goto enddraw;
5207 /* figure out what we need to draw */
5208 iterator_visibleitems(&i, infoPtr, hdc);
5209 range = iterator_range(&i);
5211 /* send cache hint notification */
5212 if (infoPtr->dwStyle & LVS_OWNERDATA)
5214 NMLVCACHEHINT nmlv;
5216 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5217 nmlv.iFrom = range.lower;
5218 nmlv.iTo = range.upper - 1;
5219 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5222 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5223 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5224 else
5226 if (infoPtr->uView == LV_VIEW_DETAILS)
5227 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5228 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5229 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5231 /* if we have a focus rect and it's visible, draw it */
5232 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5233 (range.upper - 1) >= infoPtr->nFocusedItem)
5234 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5236 iterator_destroy(&i);
5238 enddraw:
5239 /* For LVS_EX_GRIDLINES go and draw lines */
5240 /* This includes the case where there were *no* items */
5241 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5242 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5244 /* Draw marquee rectangle if appropriate */
5245 if (infoPtr->bMarqueeSelect)
5246 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5248 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5249 notify_postpaint(infoPtr, &nmlvcd);
5251 if(hbmp) {
5252 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5253 infoPtr->rcList.right - infoPtr->rcList.left,
5254 infoPtr->rcList.bottom - infoPtr->rcList.top,
5255 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5257 DeleteObject(hbmp);
5258 DeleteDC(hdc);
5259 } else {
5260 SelectObject(hdc, hOldFont);
5261 SetBkMode(hdc, oldBkMode);
5262 SetBkColor(hdc, oldBkColor);
5263 SetTextColor(hdc, oldTextColor);
5266 infoPtr->bIsDrawing = FALSE;
5270 /***
5271 * DESCRIPTION:
5272 * Calculates the approximate width and height of a given number of items.
5274 * PARAMETER(S):
5275 * [I] infoPtr : valid pointer to the listview structure
5276 * [I] nItemCount : number of items
5277 * [I] wWidth : width
5278 * [I] wHeight : height
5280 * RETURN:
5281 * Returns a DWORD. The width in the low word and the height in high word.
5283 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5284 WORD wWidth, WORD wHeight)
5286 DWORD dwViewRect = 0;
5288 if (nItemCount == -1)
5289 nItemCount = infoPtr->nItemCount;
5291 if (infoPtr->uView == LV_VIEW_LIST)
5293 INT nItemCountPerColumn = 1;
5294 INT nColumnCount = 0;
5296 if (wHeight == 0xFFFF)
5298 /* use current height */
5299 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5302 if (wHeight < infoPtr->nItemHeight)
5303 wHeight = infoPtr->nItemHeight;
5305 if (nItemCount > 0)
5307 if (infoPtr->nItemHeight > 0)
5309 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5310 if (nItemCountPerColumn == 0)
5311 nItemCountPerColumn = 1;
5313 if (nItemCount % nItemCountPerColumn != 0)
5314 nColumnCount = nItemCount / nItemCountPerColumn;
5315 else
5316 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5320 /* Microsoft padding magic */
5321 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5322 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5324 dwViewRect = MAKELONG(wWidth, wHeight);
5326 else if (infoPtr->uView == LV_VIEW_DETAILS)
5328 RECT rcBox;
5330 if (infoPtr->nItemCount > 0)
5332 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5333 wWidth = rcBox.right - rcBox.left;
5334 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5336 else
5338 /* use current height and width */
5339 if (wHeight == 0xffff)
5340 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5341 if (wWidth == 0xffff)
5342 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5345 dwViewRect = MAKELONG(wWidth, wHeight);
5347 else if (infoPtr->uView == LV_VIEW_ICON)
5349 UINT rows,cols;
5350 UINT nItemWidth;
5351 UINT nItemHeight;
5353 nItemWidth = infoPtr->iconSpacing.cx;
5354 nItemHeight = infoPtr->iconSpacing.cy;
5356 if (wWidth == 0xffff)
5357 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5359 if (wWidth < nItemWidth)
5360 wWidth = nItemWidth;
5362 cols = wWidth / nItemWidth;
5363 if (cols > nItemCount)
5364 cols = nItemCount;
5365 if (cols < 1)
5366 cols = 1;
5368 if (nItemCount)
5370 rows = nItemCount / cols;
5371 if (nItemCount % cols)
5372 rows++;
5374 else
5375 rows = 0;
5377 wHeight = (nItemHeight * rows)+2;
5378 wWidth = (nItemWidth * cols)+2;
5380 dwViewRect = MAKELONG(wWidth, wHeight);
5382 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5383 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5385 return dwViewRect;
5388 /***
5389 * DESCRIPTION:
5390 * Cancel edit label with saving item text.
5392 * PARAMETER(S):
5393 * [I] infoPtr : valid pointer to the listview structure
5395 * RETURN:
5396 * Always returns TRUE.
5398 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5400 if (infoPtr->hwndEdit)
5402 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5403 HWND edit = infoPtr->hwndEdit;
5405 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5406 SendMessageW(edit, WM_CLOSE, 0, 0);
5409 return TRUE;
5412 /***
5413 * DESCRIPTION:
5414 * Create a drag image list for the specified item.
5416 * PARAMETER(S):
5417 * [I] infoPtr : valid pointer to the listview structure
5418 * [I] iItem : index of item
5419 * [O] lppt : Upper-left corner of the image
5421 * RETURN:
5422 * Returns a handle to the image list if successful, NULL otherwise.
5424 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5426 RECT rcItem;
5427 SIZE size;
5428 POINT pos;
5429 HDC hdc, hdcOrig;
5430 HBITMAP hbmp, hOldbmp;
5431 HFONT hOldFont;
5432 HIMAGELIST dragList = 0;
5433 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5435 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5436 return 0;
5438 rcItem.left = LVIR_BOUNDS;
5439 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5440 return 0;
5442 lppt->x = rcItem.left;
5443 lppt->y = rcItem.top;
5445 size.cx = rcItem.right - rcItem.left;
5446 size.cy = rcItem.bottom - rcItem.top;
5448 hdcOrig = GetDC(infoPtr->hwndSelf);
5449 hdc = CreateCompatibleDC(hdcOrig);
5450 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5451 hOldbmp = SelectObject(hdc, hbmp);
5452 hOldFont = SelectObject(hdc, infoPtr->hFont);
5454 SetRect(&rcItem, 0, 0, size.cx, size.cy);
5455 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5457 pos.x = pos.y = 0;
5458 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, NULL, pos, CDRF_DODEFAULT))
5460 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5461 SelectObject(hdc, hOldbmp);
5462 ImageList_Add(dragList, hbmp, 0);
5464 else
5465 SelectObject(hdc, hOldbmp);
5467 SelectObject(hdc, hOldFont);
5468 DeleteObject(hbmp);
5469 DeleteDC(hdc);
5470 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5472 TRACE("ret=%p\n", dragList);
5474 return dragList;
5478 /***
5479 * DESCRIPTION:
5480 * Removes all listview items and subitems.
5482 * PARAMETER(S):
5483 * [I] infoPtr : valid pointer to the listview structure
5485 * RETURN:
5486 * SUCCESS : TRUE
5487 * FAILURE : FALSE
5489 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5491 HDPA hdpaSubItems = NULL;
5492 BOOL suppress = FALSE;
5493 ITEMHDR *hdrItem;
5494 ITEM_INFO *lpItem;
5495 ITEM_ID *lpID;
5496 INT i, j;
5498 TRACE("()\n");
5500 /* we do it directly, to avoid notifications */
5501 ranges_clear(infoPtr->selectionRanges);
5502 infoPtr->nSelectionMark = -1;
5503 infoPtr->nFocusedItem = -1;
5504 SetRectEmpty(&infoPtr->rcFocus);
5505 /* But we are supposed to leave nHotItem as is! */
5507 /* send LVN_DELETEALLITEMS notification */
5508 if (!(infoPtr->dwStyle & LVS_OWNERDATA) || !destroy)
5510 NMLISTVIEW nmlv;
5512 memset(&nmlv, 0, sizeof(NMLISTVIEW));
5513 nmlv.iItem = -1;
5514 suppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5517 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5519 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5521 /* send LVN_DELETEITEM notification, if not suppressed
5522 and if it is not a virtual listview */
5523 if (!suppress) notify_deleteitem(infoPtr, i);
5524 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5525 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5526 /* free id struct */
5527 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5528 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5529 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5530 Free(lpID);
5531 /* both item and subitem start with ITEMHDR header */
5532 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5534 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5535 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5536 Free(hdrItem);
5538 DPA_Destroy(hdpaSubItems);
5539 DPA_DeletePtr(infoPtr->hdpaItems, i);
5541 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5542 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5543 infoPtr->nItemCount --;
5546 if (!destroy)
5548 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5549 LISTVIEW_UpdateScroll(infoPtr);
5551 LISTVIEW_InvalidateList(infoPtr);
5553 return TRUE;
5556 /***
5557 * DESCRIPTION:
5558 * Scrolls, and updates the columns, when a column is changing width.
5560 * PARAMETER(S):
5561 * [I] infoPtr : valid pointer to the listview structure
5562 * [I] nColumn : column to scroll
5563 * [I] dx : amount of scroll, in pixels
5565 * RETURN:
5566 * None.
5568 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5570 COLUMN_INFO *lpColumnInfo;
5571 RECT rcOld, rcCol;
5572 POINT ptOrigin;
5573 INT nCol;
5574 HDITEMW hdi;
5576 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5577 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5578 rcCol = lpColumnInfo->rcHeader;
5579 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5580 rcCol.left = rcCol.right;
5582 /* adjust the other columns */
5583 hdi.mask = HDI_ORDER;
5584 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5586 INT nOrder = hdi.iOrder;
5587 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5589 hdi.mask = HDI_ORDER;
5590 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5591 if (hdi.iOrder >= nOrder) {
5592 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5593 lpColumnInfo->rcHeader.left += dx;
5594 lpColumnInfo->rcHeader.right += dx;
5599 /* do not update screen if not in report mode */
5600 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5602 /* Need to reset the item width when inserting a new column */
5603 infoPtr->nItemWidth += dx;
5605 LISTVIEW_UpdateScroll(infoPtr);
5606 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5608 /* scroll to cover the deleted column, and invalidate for redraw */
5609 rcOld = infoPtr->rcList;
5610 rcOld.left = ptOrigin.x + rcCol.left + dx;
5611 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5614 /***
5615 * DESCRIPTION:
5616 * Removes a column from the listview control.
5618 * PARAMETER(S):
5619 * [I] infoPtr : valid pointer to the listview structure
5620 * [I] nColumn : column index
5622 * RETURN:
5623 * SUCCESS : TRUE
5624 * FAILURE : FALSE
5626 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5628 RECT rcCol;
5630 TRACE("nColumn=%d\n", nColumn);
5632 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5633 return FALSE;
5635 /* While the MSDN specifically says that column zero should not be deleted,
5636 what actually happens is that the column itself is deleted but no items or subitems
5637 are removed.
5640 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5642 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5643 return FALSE;
5645 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5646 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5648 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5650 SUBITEM_INFO *lpSubItem, *lpDelItem;
5651 HDPA hdpaSubItems;
5652 INT nItem, nSubItem, i;
5654 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5656 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5657 nSubItem = 0;
5658 lpDelItem = 0;
5659 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5661 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5662 if (lpSubItem->iSubItem == nColumn)
5664 nSubItem = i;
5665 lpDelItem = lpSubItem;
5667 else if (lpSubItem->iSubItem > nColumn)
5669 lpSubItem->iSubItem--;
5673 /* if we found our subitem, zap it */
5674 if (nSubItem > 0)
5676 /* free string */
5677 if (is_text(lpDelItem->hdr.pszText))
5678 Free(lpDelItem->hdr.pszText);
5680 /* free item */
5681 Free(lpDelItem);
5683 /* free dpa memory */
5684 DPA_DeletePtr(hdpaSubItems, nSubItem);
5689 /* update the other column info */
5690 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5691 LISTVIEW_InvalidateList(infoPtr);
5692 else
5693 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5694 LISTVIEW_UpdateItemSize(infoPtr);
5696 return TRUE;
5699 /***
5700 * DESCRIPTION:
5701 * Invalidates the listview after an item's insertion or deletion.
5703 * PARAMETER(S):
5704 * [I] infoPtr : valid pointer to the listview structure
5705 * [I] nItem : item index
5706 * [I] dir : -1 if deleting, 1 if inserting
5708 * RETURN:
5709 * None
5711 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5713 INT nPerCol, nItemCol, nItemRow;
5714 RECT rcScroll;
5715 POINT Origin;
5717 /* if we don't refresh, what's the point of scrolling? */
5718 if (!is_redrawing(infoPtr)) return;
5720 assert (abs(dir) == 1);
5722 /* arrange icons if autoarrange is on */
5723 if (is_autoarrange(infoPtr))
5725 BOOL arrange = TRUE;
5726 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5727 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5728 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5731 /* scrollbars need updating */
5732 LISTVIEW_UpdateScroll(infoPtr);
5734 /* figure out the item's position */
5735 if (infoPtr->uView == LV_VIEW_DETAILS)
5736 nPerCol = infoPtr->nItemCount + 1;
5737 else if (infoPtr->uView == LV_VIEW_LIST)
5738 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5739 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5740 return;
5742 nItemCol = nItem / nPerCol;
5743 nItemRow = nItem % nPerCol;
5744 LISTVIEW_GetOrigin(infoPtr, &Origin);
5746 /* move the items below up a slot */
5747 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5748 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5749 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5750 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5751 OffsetRect(&rcScroll, Origin.x, Origin.y);
5752 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5753 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5755 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5756 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5759 /* report has only that column, so we're done */
5760 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5762 /* now for LISTs, we have to deal with the columns to the right */
5763 SetRect(&rcScroll, (nItemCol + 1) * infoPtr->nItemWidth, 0,
5764 (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth,
5765 nPerCol * infoPtr->nItemHeight);
5766 OffsetRect(&rcScroll, Origin.x, Origin.y);
5767 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5768 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5771 /***
5772 * DESCRIPTION:
5773 * Removes an item from the listview control.
5775 * PARAMETER(S):
5776 * [I] infoPtr : valid pointer to the listview structure
5777 * [I] nItem : item index
5779 * RETURN:
5780 * SUCCESS : TRUE
5781 * FAILURE : FALSE
5783 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5785 LVITEMW item;
5786 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5787 INT focus = infoPtr->nFocusedItem;
5789 TRACE("(nItem=%d)\n", nItem);
5791 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5793 /* remove selection, and focus */
5794 item.state = 0;
5795 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5796 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5798 /* send LVN_DELETEITEM notification. */
5799 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5801 /* we need to do this here, because we'll be deleting stuff */
5802 if (is_icon)
5803 LISTVIEW_InvalidateItem(infoPtr, nItem);
5805 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5807 HDPA hdpaSubItems;
5808 ITEMHDR *hdrItem;
5809 ITEM_INFO *lpItem;
5810 ITEM_ID *lpID;
5811 INT i;
5813 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5814 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5816 /* free id struct */
5817 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5818 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5819 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5820 Free(lpID);
5821 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5823 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5824 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5825 Free(hdrItem);
5827 DPA_Destroy(hdpaSubItems);
5830 if (is_icon)
5832 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5833 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5836 infoPtr->nItemCount--;
5837 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5838 LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
5840 /* now is the invalidation fun */
5841 if (!is_icon)
5842 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5843 return TRUE;
5847 /***
5848 * DESCRIPTION:
5849 * Callback implementation for editlabel control
5851 * PARAMETER(S):
5852 * [I] infoPtr : valid pointer to the listview structure
5853 * [I] storeText : store edit box text as item text
5854 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5856 * RETURN:
5857 * SUCCESS : TRUE
5858 * FAILURE : FALSE
5860 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5862 HWND hwndSelf = infoPtr->hwndSelf;
5863 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5864 NMLVDISPINFOW dispInfo;
5865 INT editedItem = infoPtr->nEditLabelItem;
5866 BOOL same;
5867 WCHAR *pszText = NULL;
5868 BOOL res;
5870 if (storeText)
5872 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5874 if (len++)
5876 if (!(pszText = Alloc(len * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5877 return FALSE;
5879 if (isW)
5880 GetWindowTextW(infoPtr->hwndEdit, pszText, len);
5881 else
5882 GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len);
5886 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5888 ZeroMemory(&dispInfo, sizeof(dispInfo));
5889 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5890 dispInfo.item.iItem = editedItem;
5891 dispInfo.item.iSubItem = 0;
5892 dispInfo.item.stateMask = ~0;
5893 dispInfo.item.pszText = szDispText;
5894 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5895 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5897 res = FALSE;
5898 goto cleanup;
5901 if (isW)
5902 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5903 else
5905 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5906 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5907 textfreeT(tmp, FALSE);
5910 /* add the text from the edit in */
5911 dispInfo.item.mask |= LVIF_TEXT;
5912 dispInfo.item.pszText = same ? NULL : pszText;
5913 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5915 /* Do we need to update the Item Text */
5916 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5918 infoPtr->nEditLabelItem = -1;
5919 infoPtr->hwndEdit = 0;
5921 if (!res) goto cleanup;
5923 if (!IsWindow(hwndSelf))
5925 res = FALSE;
5926 goto cleanup;
5928 if (!pszText) return TRUE;
5929 if (same)
5931 res = TRUE;
5932 goto cleanup;
5935 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5937 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5938 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5939 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5941 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5942 res = TRUE;
5943 goto cleanup;
5947 ZeroMemory(&dispInfo, sizeof(dispInfo));
5948 dispInfo.item.mask = LVIF_TEXT;
5949 dispInfo.item.iItem = editedItem;
5950 dispInfo.item.iSubItem = 0;
5951 dispInfo.item.pszText = pszText;
5952 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5953 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5955 cleanup:
5956 Free(pszText);
5958 return res;
5961 /***
5962 * DESCRIPTION:
5963 * Subclassed edit control windproc function
5965 * PARAMETER(S):
5966 * [I] hwnd : the edit window handle
5967 * [I] uMsg : the message that is to be processed
5968 * [I] wParam : first message parameter
5969 * [I] lParam : second message parameter
5970 * [I] isW : TRUE if input is Unicode
5972 * RETURN:
5973 * Zero.
5975 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
5977 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
5978 BOOL save = TRUE;
5980 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
5981 hwnd, uMsg, wParam, lParam, isW);
5983 switch (uMsg)
5985 case WM_GETDLGCODE:
5986 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
5988 case WM_DESTROY:
5990 WNDPROC editProc = infoPtr->EditWndProc;
5991 infoPtr->EditWndProc = 0;
5992 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
5993 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
5996 case WM_KEYDOWN:
5997 if (VK_ESCAPE == (INT)wParam)
5999 save = FALSE;
6000 break;
6002 else if (VK_RETURN == (INT)wParam)
6003 break;
6005 default:
6006 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
6009 /* kill the edit */
6010 if (infoPtr->hwndEdit)
6011 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
6013 SendMessageW(hwnd, WM_CLOSE, 0, 0);
6014 return 0;
6017 /***
6018 * DESCRIPTION:
6019 * Subclassed edit control Unicode windproc function
6021 * PARAMETER(S):
6022 * [I] hwnd : the edit window handle
6023 * [I] uMsg : the message that is to be processed
6024 * [I] wParam : first message parameter
6025 * [I] lParam : second message parameter
6027 * RETURN:
6029 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6031 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
6034 /***
6035 * DESCRIPTION:
6036 * Subclassed edit control ANSI windproc function
6038 * PARAMETER(S):
6039 * [I] hwnd : the edit window handle
6040 * [I] uMsg : the message that is to be processed
6041 * [I] wParam : first message parameter
6042 * [I] lParam : second message parameter
6044 * RETURN:
6046 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6048 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
6051 /***
6052 * DESCRIPTION:
6053 * Creates a subclassed edit control
6055 * PARAMETER(S):
6056 * [I] infoPtr : valid pointer to the listview structure
6057 * [I] text : initial text for the edit
6058 * [I] style : the window style
6059 * [I] isW : TRUE if input is Unicode
6061 * RETURN:
6063 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
6065 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
6066 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
6067 HWND hedit;
6069 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
6071 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
6072 if (isW)
6073 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6074 else
6075 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6077 if (!hedit) return 0;
6079 infoPtr->EditWndProc = (WNDPROC)
6080 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
6081 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
6083 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
6084 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
6086 return hedit;
6089 /***
6090 * DESCRIPTION:
6091 * Begin in place editing of specified list view item
6093 * PARAMETER(S):
6094 * [I] infoPtr : valid pointer to the listview structure
6095 * [I] nItem : item index
6096 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
6098 * RETURN:
6099 * SUCCESS : TRUE
6100 * FAILURE : FALSE
6102 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
6104 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
6105 HWND hwndSelf = infoPtr->hwndSelf;
6106 NMLVDISPINFOW dispInfo;
6107 HFONT hOldFont = NULL;
6108 TEXTMETRICW tm;
6109 RECT rect;
6110 SIZE sz;
6111 HDC hdc;
6113 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
6115 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
6117 /* remove existing edit box */
6118 if (infoPtr->hwndEdit)
6120 SetFocus(infoPtr->hwndSelf);
6121 infoPtr->hwndEdit = 0;
6124 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6126 infoPtr->nEditLabelItem = nItem;
6128 LISTVIEW_SetSelection(infoPtr, nItem);
6129 LISTVIEW_SetItemFocus(infoPtr, nItem);
6130 LISTVIEW_InvalidateItem(infoPtr, nItem);
6132 rect.left = LVIR_LABEL;
6133 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6135 ZeroMemory(&dispInfo, sizeof(dispInfo));
6136 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6137 dispInfo.item.iItem = nItem;
6138 dispInfo.item.iSubItem = 0;
6139 dispInfo.item.stateMask = ~0;
6140 dispInfo.item.pszText = disptextW;
6141 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6142 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6144 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6145 if (!infoPtr->hwndEdit) return 0;
6147 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6149 if (!IsWindow(hwndSelf))
6150 return 0;
6151 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6152 infoPtr->hwndEdit = 0;
6153 return 0;
6156 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6158 /* position and display edit box */
6159 hdc = GetDC(infoPtr->hwndSelf);
6161 /* select the font to get appropriate metric dimensions */
6162 if (infoPtr->hFont)
6163 hOldFont = SelectObject(hdc, infoPtr->hFont);
6165 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6166 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6167 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6169 /* get string length in pixels */
6170 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6172 /* add extra spacing for the next character */
6173 GetTextMetricsW(hdc, &tm);
6174 sz.cx += tm.tmMaxCharWidth * 2;
6176 if (infoPtr->hFont)
6177 SelectObject(hdc, hOldFont);
6179 ReleaseDC(infoPtr->hwndSelf, hdc);
6181 sz.cy = rect.bottom - rect.top + 2;
6182 rect.left -= 2;
6183 rect.top -= 1;
6184 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6185 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6186 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6187 SetFocus(infoPtr->hwndEdit);
6188 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6189 return infoPtr->hwndEdit;
6193 /***
6194 * DESCRIPTION:
6195 * Ensures the specified item is visible, scrolling into view if necessary.
6197 * PARAMETER(S):
6198 * [I] infoPtr : valid pointer to the listview structure
6199 * [I] nItem : item index
6200 * [I] bPartial : partially or entirely visible
6202 * RETURN:
6203 * SUCCESS : TRUE
6204 * FAILURE : FALSE
6206 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6208 INT nScrollPosHeight = 0;
6209 INT nScrollPosWidth = 0;
6210 INT nHorzAdjust = 0;
6211 INT nVertAdjust = 0;
6212 INT nHorzDiff = 0;
6213 INT nVertDiff = 0;
6214 RECT rcItem, rcTemp;
6216 rcItem.left = LVIR_BOUNDS;
6217 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6219 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6221 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6223 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6224 if (infoPtr->uView == LV_VIEW_LIST)
6225 nScrollPosWidth = infoPtr->nItemWidth;
6226 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6227 nScrollPosWidth = 1;
6229 if (rcItem.left < infoPtr->rcList.left)
6231 nHorzAdjust = -1;
6232 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6234 else
6236 nHorzAdjust = 1;
6237 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6241 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6243 /* scroll up/down, but not in LVS_LIST mode */
6244 if (infoPtr->uView == LV_VIEW_DETAILS)
6245 nScrollPosHeight = infoPtr->nItemHeight;
6246 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6247 nScrollPosHeight = 1;
6249 if (rcItem.top < infoPtr->rcList.top)
6251 nVertAdjust = -1;
6252 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6254 else
6256 nVertAdjust = 1;
6257 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6261 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6263 if (nScrollPosWidth)
6265 INT diff = nHorzDiff / nScrollPosWidth;
6266 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6267 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6270 if (nScrollPosHeight)
6272 INT diff = nVertDiff / nScrollPosHeight;
6273 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6274 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6277 return TRUE;
6280 /***
6281 * DESCRIPTION:
6282 * Searches for an item with specific characteristics.
6284 * PARAMETER(S):
6285 * [I] hwnd : window handle
6286 * [I] nStart : base item index
6287 * [I] lpFindInfo : item information to look for
6289 * RETURN:
6290 * SUCCESS : index of item
6291 * FAILURE : -1
6293 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6294 const LVFINDINFOW *lpFindInfo)
6296 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6297 BOOL bWrap = FALSE, bNearest = FALSE;
6298 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6299 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6300 POINT Position, Destination;
6301 LVITEMW lvItem;
6303 /* Search in virtual listviews should be done by application, not by
6304 listview control, so we just send LVN_ODFINDITEMW and return the result */
6305 if (infoPtr->dwStyle & LVS_OWNERDATA)
6307 NMLVFINDITEMW nmlv;
6309 nmlv.iStart = nStart;
6310 nmlv.lvfi = *lpFindInfo;
6311 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6314 if (!lpFindInfo || nItem < 0) return -1;
6316 lvItem.mask = 0;
6317 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING))
6319 lvItem.mask |= LVIF_TEXT;
6320 lvItem.pszText = szDispText;
6321 lvItem.cchTextMax = DISP_TEXT_SIZE;
6324 if (lpFindInfo->flags & LVFI_WRAP)
6325 bWrap = TRUE;
6327 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6328 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6330 POINT Origin;
6331 RECT rcArea;
6333 LISTVIEW_GetOrigin(infoPtr, &Origin);
6334 Destination.x = lpFindInfo->pt.x - Origin.x;
6335 Destination.y = lpFindInfo->pt.y - Origin.y;
6336 switch(lpFindInfo->vkDirection)
6338 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6339 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6340 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6341 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6342 case VK_HOME: Destination.x = Destination.y = 0; break;
6343 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6344 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6345 case VK_END:
6346 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6347 Destination.x = rcArea.right;
6348 Destination.y = rcArea.bottom;
6349 break;
6350 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6352 bNearest = TRUE;
6354 else Destination.x = Destination.y = 0;
6356 /* if LVFI_PARAM is specified, all other flags are ignored */
6357 if (lpFindInfo->flags & LVFI_PARAM)
6359 lvItem.mask |= LVIF_PARAM;
6360 bNearest = FALSE;
6361 lvItem.mask &= ~LVIF_TEXT;
6364 nItem = bNearest ? -1 : nStart + 1;
6366 again:
6367 for (; nItem < nLast; nItem++)
6369 lvItem.iItem = nItem;
6370 lvItem.iSubItem = 0;
6371 lvItem.pszText = szDispText;
6372 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6374 if (lvItem.mask & LVIF_PARAM)
6376 if (lpFindInfo->lParam == lvItem.lParam)
6377 return nItem;
6378 else
6379 continue;
6382 if (lvItem.mask & LVIF_TEXT)
6384 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6386 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6387 if (!p || p != lvItem.pszText) continue;
6389 else
6391 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6395 if (!bNearest) return nItem;
6397 /* This is very inefficient. To do a good job here,
6398 * we need a sorted array of (x,y) item positions */
6399 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6401 /* compute the distance^2 to the destination */
6402 xdist = Destination.x - Position.x;
6403 ydist = Destination.y - Position.y;
6404 dist = xdist * xdist + ydist * ydist;
6406 /* remember the distance, and item if it's closer */
6407 if (dist < mindist)
6409 mindist = dist;
6410 nNearestItem = nItem;
6414 if (bWrap)
6416 nItem = 0;
6417 nLast = min(nStart + 1, infoPtr->nItemCount);
6418 bWrap = FALSE;
6419 goto again;
6422 return nNearestItem;
6425 /***
6426 * DESCRIPTION:
6427 * Searches for an item with specific characteristics.
6429 * PARAMETER(S):
6430 * [I] hwnd : window handle
6431 * [I] nStart : base item index
6432 * [I] lpFindInfo : item information to look for
6434 * RETURN:
6435 * SUCCESS : index of item
6436 * FAILURE : -1
6438 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6439 const LVFINDINFOA *lpFindInfo)
6441 LVFINDINFOW fiw;
6442 INT res;
6443 LPWSTR strW = NULL;
6445 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6446 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING))
6447 fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6448 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6449 textfreeT(strW, FALSE);
6450 return res;
6453 /***
6454 * DESCRIPTION:
6455 * Retrieves column attributes.
6457 * PARAMETER(S):
6458 * [I] infoPtr : valid pointer to the listview structure
6459 * [I] nColumn : column index
6460 * [IO] lpColumn : column information
6461 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6462 * otherwise it is in fact a LPLVCOLUMNA
6464 * RETURN:
6465 * SUCCESS : TRUE
6466 * FAILURE : FALSE
6468 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6470 COLUMN_INFO *lpColumnInfo;
6471 HDITEMW hdi;
6473 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6474 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6476 /* initialize memory */
6477 ZeroMemory(&hdi, sizeof(hdi));
6479 if (lpColumn->mask & LVCF_TEXT)
6481 hdi.mask |= HDI_TEXT;
6482 hdi.pszText = lpColumn->pszText;
6483 hdi.cchTextMax = lpColumn->cchTextMax;
6486 if (lpColumn->mask & LVCF_IMAGE)
6487 hdi.mask |= HDI_IMAGE;
6489 if (lpColumn->mask & LVCF_ORDER)
6490 hdi.mask |= HDI_ORDER;
6492 if (lpColumn->mask & LVCF_SUBITEM)
6493 hdi.mask |= HDI_LPARAM;
6495 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6497 if (lpColumn->mask & LVCF_FMT)
6498 lpColumn->fmt = lpColumnInfo->fmt;
6500 if (lpColumn->mask & LVCF_WIDTH)
6501 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6503 if (lpColumn->mask & LVCF_IMAGE)
6504 lpColumn->iImage = hdi.iImage;
6506 if (lpColumn->mask & LVCF_ORDER)
6507 lpColumn->iOrder = hdi.iOrder;
6509 if (lpColumn->mask & LVCF_SUBITEM)
6510 lpColumn->iSubItem = hdi.lParam;
6512 if (lpColumn->mask & LVCF_MINWIDTH)
6513 lpColumn->cxMin = lpColumnInfo->cxMin;
6515 return TRUE;
6518 static inline BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6520 if (!infoPtr->hwndHeader) return FALSE;
6521 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6524 /***
6525 * DESCRIPTION:
6526 * Retrieves the column width.
6528 * PARAMETER(S):
6529 * [I] infoPtr : valid pointer to the listview structure
6530 * [I] int : column index
6532 * RETURN:
6533 * SUCCESS : column width
6534 * FAILURE : zero
6536 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6538 INT nColumnWidth = 0;
6539 HDITEMW hdItem;
6541 TRACE("nColumn=%d\n", nColumn);
6543 /* we have a 'column' in LIST and REPORT mode only */
6544 switch(infoPtr->uView)
6546 case LV_VIEW_LIST:
6547 nColumnWidth = infoPtr->nItemWidth;
6548 break;
6549 case LV_VIEW_DETAILS:
6550 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6551 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6552 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6554 hdItem.mask = HDI_WIDTH;
6555 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6557 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6558 return 0;
6560 nColumnWidth = hdItem.cxy;
6561 break;
6564 TRACE("nColumnWidth=%d\n", nColumnWidth);
6565 return nColumnWidth;
6568 /***
6569 * DESCRIPTION:
6570 * In list or report display mode, retrieves the number of items that can fit
6571 * vertically in the visible area. In icon or small icon display mode,
6572 * retrieves the total number of visible items.
6574 * PARAMETER(S):
6575 * [I] infoPtr : valid pointer to the listview structure
6577 * RETURN:
6578 * Number of fully visible items.
6580 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6582 switch (infoPtr->uView)
6584 case LV_VIEW_ICON:
6585 case LV_VIEW_SMALLICON:
6586 return infoPtr->nItemCount;
6587 case LV_VIEW_DETAILS:
6588 return LISTVIEW_GetCountPerColumn(infoPtr);
6589 case LV_VIEW_LIST:
6590 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6592 assert(FALSE);
6593 return 0;
6596 /***
6597 * DESCRIPTION:
6598 * Retrieves an image list handle.
6600 * PARAMETER(S):
6601 * [I] infoPtr : valid pointer to the listview structure
6602 * [I] nImageList : image list identifier
6604 * RETURN:
6605 * SUCCESS : image list handle
6606 * FAILURE : NULL
6608 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6610 switch (nImageList)
6612 case LVSIL_NORMAL: return infoPtr->himlNormal;
6613 case LVSIL_SMALL: return infoPtr->himlSmall;
6614 case LVSIL_STATE: return infoPtr->himlState;
6615 case LVSIL_GROUPHEADER:
6616 FIXME("LVSIL_GROUPHEADER not supported\n");
6617 break;
6618 default:
6619 WARN("got unknown imagelist index - %d\n", nImageList);
6621 return NULL;
6624 /* LISTVIEW_GetISearchString */
6626 /***
6627 * DESCRIPTION:
6628 * Retrieves item attributes.
6630 * PARAMETER(S):
6631 * [I] hwnd : window handle
6632 * [IO] lpLVItem : item info
6633 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6634 * if FALSE, then lpLVItem is a LPLVITEMA.
6636 * NOTE:
6637 * This is the internal 'GetItem' interface -- it tries to
6638 * be smart and avoid text copies, if possible, by modifying
6639 * lpLVItem->pszText to point to the text string. Please note
6640 * that this is not always possible (e.g. OWNERDATA), so on
6641 * entry you *must* supply valid values for pszText, and cchTextMax.
6642 * The only difference to the documented interface is that upon
6643 * return, you should use *only* the lpLVItem->pszText, rather than
6644 * the buffer pointer you provided on input. Most code already does
6645 * that, so it's not a problem.
6646 * For the two cases when the text must be copied (that is,
6647 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6649 * RETURN:
6650 * SUCCESS : TRUE
6651 * FAILURE : FALSE
6653 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6655 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6656 NMLVDISPINFOW dispInfo;
6657 ITEM_INFO *lpItem;
6658 ITEMHDR* pItemHdr;
6659 HDPA hdpaSubItems;
6660 INT isubitem;
6662 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6664 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6665 return FALSE;
6667 if (lpLVItem->mask == 0) return TRUE;
6668 TRACE("mask=%x\n", lpLVItem->mask);
6670 /* make a local copy */
6671 isubitem = lpLVItem->iSubItem;
6673 if (isubitem && (lpLVItem->mask & LVIF_STATE))
6674 lpLVItem->state = 0;
6676 /* a quick optimization if all we're asked is the focus state
6677 * these queries are worth optimising since they are common,
6678 * and can be answered in constant time, without the heavy accesses */
6679 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6680 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6682 lpLVItem->state = 0;
6683 if (infoPtr->nFocusedItem == lpLVItem->iItem && isubitem == 0)
6684 lpLVItem->state |= LVIS_FOCUSED;
6685 return TRUE;
6688 ZeroMemory(&dispInfo, sizeof(dispInfo));
6690 /* if the app stores all the data, handle it separately */
6691 if (infoPtr->dwStyle & LVS_OWNERDATA)
6693 dispInfo.item.state = 0;
6695 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6696 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6697 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6699 UINT mask = lpLVItem->mask;
6701 /* NOTE: copy only fields which we _know_ are initialized, some apps
6702 * depend on the uninitialized fields being 0 */
6703 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6704 dispInfo.item.iItem = lpLVItem->iItem;
6705 dispInfo.item.iSubItem = isubitem;
6706 if (lpLVItem->mask & LVIF_TEXT)
6708 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6709 /* reset mask */
6710 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6711 else
6713 dispInfo.item.pszText = lpLVItem->pszText;
6714 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6717 if (lpLVItem->mask & LVIF_STATE)
6718 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6719 /* could be zeroed on LVIF_NORECOMPUTE case */
6720 if (dispInfo.item.mask)
6722 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6723 dispInfo.item.stateMask = lpLVItem->stateMask;
6724 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6726 /* full size structure expected - _WIN32IE >= 0x560 */
6727 *lpLVItem = dispInfo.item;
6729 else if (lpLVItem->mask & LVIF_INDENT)
6731 /* indent member expected - _WIN32IE >= 0x300 */
6732 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6734 else
6736 /* minimal structure expected */
6737 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6739 lpLVItem->mask = mask;
6740 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6744 /* make sure lParam is zeroed out */
6745 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6747 /* callback marked pointer required here */
6748 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6749 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6751 /* we store only a little state, so if we're not asked, we're done */
6752 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6754 /* if focus is handled by us, report it */
6755 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6757 lpLVItem->state &= ~LVIS_FOCUSED;
6758 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6759 lpLVItem->state |= LVIS_FOCUSED;
6762 /* and do the same for selection, if we handle it */
6763 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6765 lpLVItem->state &= ~LVIS_SELECTED;
6766 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6767 lpLVItem->state |= LVIS_SELECTED;
6770 return TRUE;
6773 /* find the item and subitem structures before we proceed */
6774 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6775 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6776 assert (lpItem);
6778 if (isubitem)
6780 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6781 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6782 if (!lpSubItem)
6784 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6785 isubitem = 0;
6788 else
6789 pItemHdr = &lpItem->hdr;
6791 /* Do we need to query the state from the app? */
6792 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6794 dispInfo.item.mask |= LVIF_STATE;
6795 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6798 /* Do we need to enquire about the image? */
6799 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6800 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6802 dispInfo.item.mask |= LVIF_IMAGE;
6803 dispInfo.item.iImage = I_IMAGECALLBACK;
6806 /* Only items support indentation */
6807 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6808 (isubitem == 0))
6810 dispInfo.item.mask |= LVIF_INDENT;
6811 dispInfo.item.iIndent = I_INDENTCALLBACK;
6814 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6815 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6816 !is_text(pItemHdr->pszText))
6818 dispInfo.item.mask |= LVIF_TEXT;
6819 dispInfo.item.pszText = lpLVItem->pszText;
6820 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6821 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6822 *dispInfo.item.pszText = '\0';
6825 /* If we don't have all the requested info, query the application */
6826 if (dispInfo.item.mask)
6828 dispInfo.item.iItem = lpLVItem->iItem;
6829 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6830 dispInfo.item.lParam = lpItem->lParam;
6831 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6832 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6835 /* we should not store values for subitems */
6836 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6838 /* Now, handle the iImage field */
6839 if (dispInfo.item.mask & LVIF_IMAGE)
6841 lpLVItem->iImage = dispInfo.item.iImage;
6842 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6843 pItemHdr->iImage = dispInfo.item.iImage;
6845 else if (lpLVItem->mask & LVIF_IMAGE)
6847 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6848 lpLVItem->iImage = pItemHdr->iImage;
6849 else
6850 lpLVItem->iImage = 0;
6853 /* The pszText field */
6854 if (dispInfo.item.mask & LVIF_TEXT)
6856 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6857 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6859 lpLVItem->pszText = dispInfo.item.pszText;
6861 else if (lpLVItem->mask & LVIF_TEXT)
6863 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6864 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6865 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6868 /* Next is the lParam field */
6869 if (dispInfo.item.mask & LVIF_PARAM)
6871 lpLVItem->lParam = dispInfo.item.lParam;
6872 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6873 lpItem->lParam = dispInfo.item.lParam;
6875 else if (lpLVItem->mask & LVIF_PARAM)
6876 lpLVItem->lParam = lpItem->lParam;
6878 /* if this is a subitem, we're done */
6879 if (isubitem) return TRUE;
6881 /* ... the state field (this one is different due to uCallbackmask) */
6882 if (lpLVItem->mask & LVIF_STATE)
6884 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6885 if (dispInfo.item.mask & LVIF_STATE)
6887 lpLVItem->state &= ~dispInfo.item.stateMask;
6888 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6890 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6892 lpLVItem->state &= ~LVIS_FOCUSED;
6893 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6894 lpLVItem->state |= LVIS_FOCUSED;
6896 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6898 lpLVItem->state &= ~LVIS_SELECTED;
6899 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6900 lpLVItem->state |= LVIS_SELECTED;
6904 /* and last, but not least, the indent field */
6905 if (dispInfo.item.mask & LVIF_INDENT)
6907 lpLVItem->iIndent = dispInfo.item.iIndent;
6908 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6909 lpItem->iIndent = dispInfo.item.iIndent;
6911 else if (lpLVItem->mask & LVIF_INDENT)
6913 lpLVItem->iIndent = lpItem->iIndent;
6916 return TRUE;
6919 /***
6920 * DESCRIPTION:
6921 * Retrieves item attributes.
6923 * PARAMETER(S):
6924 * [I] hwnd : window handle
6925 * [IO] lpLVItem : item info
6926 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6927 * if FALSE, then lpLVItem is a LPLVITEMA.
6929 * NOTE:
6930 * This is the external 'GetItem' interface -- it properly copies
6931 * the text in the provided buffer.
6933 * RETURN:
6934 * SUCCESS : TRUE
6935 * FAILURE : FALSE
6937 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6939 LPWSTR pszText;
6940 BOOL bResult;
6942 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6943 return FALSE;
6945 pszText = lpLVItem->pszText;
6946 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6947 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6949 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6950 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6951 else
6952 pszText = LPSTR_TEXTCALLBACKW;
6954 lpLVItem->pszText = pszText;
6956 return bResult;
6960 /***
6961 * DESCRIPTION:
6962 * Retrieves the position (upper-left) of the listview control item.
6963 * Note that for LVS_ICON style, the upper-left is that of the icon
6964 * and not the bounding box.
6966 * PARAMETER(S):
6967 * [I] infoPtr : valid pointer to the listview structure
6968 * [I] nItem : item index
6969 * [O] lpptPosition : coordinate information
6971 * RETURN:
6972 * SUCCESS : TRUE
6973 * FAILURE : FALSE
6975 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
6977 POINT Origin;
6979 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
6981 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
6983 LISTVIEW_GetOrigin(infoPtr, &Origin);
6984 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
6986 if (infoPtr->uView == LV_VIEW_ICON)
6988 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
6989 lpptPosition->y += ICON_TOP_PADDING;
6991 lpptPosition->x += Origin.x;
6992 lpptPosition->y += Origin.y;
6994 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
6995 return TRUE;
6999 /***
7000 * DESCRIPTION:
7001 * Retrieves the bounding rectangle for a listview control item.
7003 * PARAMETER(S):
7004 * [I] infoPtr : valid pointer to the listview structure
7005 * [I] nItem : item index
7006 * [IO] lprc : bounding rectangle coordinates
7007 * lprc->left specifies the portion of the item for which the bounding
7008 * rectangle will be retrieved.
7010 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
7011 * including the icon and label.
7013 * * For LVS_ICON
7014 * * Experiment shows that native control returns:
7015 * * width = min (48, length of text line)
7016 * * .left = position.x - (width - iconsize.cx)/2
7017 * * .right = .left + width
7018 * * height = #lines of text * ntmHeight + icon height + 8
7019 * * .top = position.y - 2
7020 * * .bottom = .top + height
7021 * * separation between items .y = itemSpacing.cy - height
7022 * * .x = itemSpacing.cx - width
7023 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
7025 * * For LVS_ICON
7026 * * Experiment shows that native control returns:
7027 * * width = iconSize.cx + 16
7028 * * .left = position.x - (width - iconsize.cx)/2
7029 * * .right = .left + width
7030 * * height = iconSize.cy + 4
7031 * * .top = position.y - 2
7032 * * .bottom = .top + height
7033 * * separation between items .y = itemSpacing.cy - height
7034 * * .x = itemSpacing.cx - width
7035 * LVIR_LABEL Returns the bounding rectangle of the item text.
7037 * * For LVS_ICON
7038 * * Experiment shows that native control returns:
7039 * * width = text length
7040 * * .left = position.x - width/2
7041 * * .right = .left + width
7042 * * height = ntmH * linecount + 2
7043 * * .top = position.y + iconSize.cy + 6
7044 * * .bottom = .top + height
7045 * * separation between items .y = itemSpacing.cy - height
7046 * * .x = itemSpacing.cx - width
7047 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
7048 * rectangles, but excludes columns in report view.
7050 * RETURN:
7051 * SUCCESS : TRUE
7052 * FAILURE : FALSE
7054 * NOTES
7055 * Note that the bounding rectangle of the label in the LVS_ICON view depends
7056 * upon whether the window has the focus currently and on whether the item
7057 * is the one with the focus. Ensure that the control's record of which
7058 * item has the focus agrees with the items' records.
7060 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
7062 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7063 BOOL doLabel = TRUE, oversizedBox = FALSE;
7064 POINT Position, Origin;
7065 LVITEMW lvItem;
7066 LONG mode;
7068 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
7070 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7072 LISTVIEW_GetOrigin(infoPtr, &Origin);
7073 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7075 /* Be smart and try to figure out the minimum we have to do */
7076 if (lprc->left == LVIR_ICON) doLabel = FALSE;
7077 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
7078 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
7079 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
7080 oversizedBox = TRUE;
7082 /* get what we need from the item before hand, so we make
7083 * only one request. This can speed up things, if data
7084 * is stored on the app side */
7085 lvItem.mask = 0;
7086 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7087 if (doLabel) lvItem.mask |= LVIF_TEXT;
7088 lvItem.iItem = nItem;
7089 lvItem.iSubItem = 0;
7090 lvItem.pszText = szDispText;
7091 lvItem.cchTextMax = DISP_TEXT_SIZE;
7092 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
7093 /* we got the state already up, simulate it here, to avoid a reget */
7094 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
7096 lvItem.mask |= LVIF_STATE;
7097 lvItem.stateMask = LVIS_FOCUSED;
7098 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
7101 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
7102 lprc->left = LVIR_BOUNDS;
7104 mode = lprc->left;
7105 switch(lprc->left)
7107 case LVIR_ICON:
7108 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7109 break;
7111 case LVIR_LABEL:
7112 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
7113 break;
7115 case LVIR_BOUNDS:
7116 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7117 break;
7119 case LVIR_SELECTBOUNDS:
7120 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
7121 break;
7123 default:
7124 WARN("Unknown value: %d\n", lprc->left);
7125 return FALSE;
7128 if (infoPtr->uView == LV_VIEW_DETAILS)
7130 if (mode != LVIR_BOUNDS)
7131 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7132 Position.y + Origin.y);
7133 else
7134 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7136 else
7137 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7139 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7141 return TRUE;
7144 /***
7145 * DESCRIPTION:
7146 * Retrieves the spacing between listview control items.
7148 * PARAMETER(S):
7149 * [I] infoPtr : valid pointer to the listview structure
7150 * [IO] lprc : rectangle to receive the output
7151 * on input, lprc->top = nSubItem
7152 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7154 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7155 * not only those of the first column.
7157 * RETURN:
7158 * TRUE: success
7159 * FALSE: failure
7161 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7163 RECT rect = { 0, 0, 0, 0 };
7164 POINT origin;
7165 INT y;
7167 if (!lprc) return FALSE;
7169 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7170 /* Subitem of '0' means item itself, and this works for all control view modes */
7171 if (lprc->top == 0)
7172 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7174 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7176 LISTVIEW_GetOrigin(infoPtr, &origin);
7177 /* this works for any item index, no matter if it exists or not */
7178 y = item * infoPtr->nItemHeight + origin.y;
7180 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7182 rect.top = 0;
7183 rect.bottom = infoPtr->nItemHeight;
7185 else
7187 /* Native implementation is broken for this case and garbage is left for left and right fields,
7188 we zero them to get predictable output */
7189 lprc->left = lprc->right = lprc->top = 0;
7190 lprc->bottom = infoPtr->nItemHeight;
7191 OffsetRect(lprc, origin.x, y);
7192 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7193 return TRUE;
7196 switch (lprc->left)
7198 case LVIR_ICON:
7200 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7201 if (infoPtr->himlSmall)
7202 rect.right = rect.left + infoPtr->iconSize.cx;
7203 else
7204 rect.right = rect.left;
7206 rect.bottom = rect.top + infoPtr->iconSize.cy;
7207 break;
7209 case LVIR_LABEL:
7210 case LVIR_BOUNDS:
7211 break;
7213 default:
7214 ERR("Unknown bounds=%d\n", lprc->left);
7215 return FALSE;
7218 OffsetRect(&rect, origin.x, y);
7219 *lprc = rect;
7220 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7222 return TRUE;
7225 /***
7226 * DESCRIPTION:
7227 * Retrieves the spacing between listview control items.
7229 * PARAMETER(S):
7230 * [I] infoPtr : valid pointer to the listview structure
7231 * [I] bSmall : flag for small or large icon
7233 * RETURN:
7234 * Horizontal + vertical spacing
7236 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7238 LONG lResult;
7240 if (!bSmall)
7242 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7244 else
7246 if (infoPtr->uView == LV_VIEW_ICON)
7247 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7248 else
7249 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7251 return lResult;
7254 /***
7255 * DESCRIPTION:
7256 * Retrieves the state of a listview control item.
7258 * PARAMETER(S):
7259 * [I] infoPtr : valid pointer to the listview structure
7260 * [I] nItem : item index
7261 * [I] uMask : state mask
7263 * RETURN:
7264 * State specified by the mask.
7266 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7268 LVITEMW lvItem;
7270 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7272 lvItem.iItem = nItem;
7273 lvItem.iSubItem = 0;
7274 lvItem.mask = LVIF_STATE;
7275 lvItem.stateMask = uMask;
7276 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7278 return lvItem.state & uMask;
7281 /***
7282 * DESCRIPTION:
7283 * Retrieves the text of a listview control item or subitem.
7285 * PARAMETER(S):
7286 * [I] hwnd : window handle
7287 * [I] nItem : item index
7288 * [IO] lpLVItem : item information
7289 * [I] isW : TRUE if lpLVItem is Unicode
7291 * RETURN:
7292 * SUCCESS : string length
7293 * FAILURE : 0
7295 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7297 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7299 lpLVItem->mask = LVIF_TEXT;
7300 lpLVItem->iItem = nItem;
7301 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7303 return textlenT(lpLVItem->pszText, isW);
7306 /***
7307 * DESCRIPTION:
7308 * Searches for an item based on properties + relationships.
7310 * PARAMETER(S):
7311 * [I] infoPtr : valid pointer to the listview structure
7312 * [I] nItem : item index
7313 * [I] uFlags : relationship flag
7315 * RETURN:
7316 * SUCCESS : item index
7317 * FAILURE : -1
7319 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7321 UINT uMask = 0;
7322 LVFINDINFOW lvFindInfo;
7323 INT nCountPerColumn;
7324 INT nCountPerRow;
7325 INT i;
7327 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7328 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7330 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7332 if (uFlags & LVNI_CUT)
7333 uMask |= LVIS_CUT;
7335 if (uFlags & LVNI_DROPHILITED)
7336 uMask |= LVIS_DROPHILITED;
7338 if (uFlags & LVNI_FOCUSED)
7339 uMask |= LVIS_FOCUSED;
7341 if (uFlags & LVNI_SELECTED)
7342 uMask |= LVIS_SELECTED;
7344 /* if we're asked for the focused item, that's only one,
7345 * so it's worth optimizing */
7346 if (uFlags & LVNI_FOCUSED)
7348 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7349 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7352 if (uFlags & LVNI_ABOVE)
7354 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7356 while (nItem >= 0)
7358 nItem--;
7359 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7360 return nItem;
7363 else
7365 /* Special case for autoarrange - move 'til the top of a list */
7366 if (is_autoarrange(infoPtr))
7368 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7369 while (nItem - nCountPerRow >= 0)
7371 nItem -= nCountPerRow;
7372 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7373 return nItem;
7375 return -1;
7377 lvFindInfo.flags = LVFI_NEARESTXY;
7378 lvFindInfo.vkDirection = VK_UP;
7379 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7380 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7382 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7383 return nItem;
7387 else if (uFlags & LVNI_BELOW)
7389 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7391 while (nItem < infoPtr->nItemCount)
7393 nItem++;
7394 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7395 return nItem;
7398 else
7400 /* Special case for autoarrange - move 'til the bottom of a list */
7401 if (is_autoarrange(infoPtr))
7403 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7404 while (nItem + nCountPerRow < infoPtr->nItemCount )
7406 nItem += nCountPerRow;
7407 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7408 return nItem;
7410 return -1;
7412 lvFindInfo.flags = LVFI_NEARESTXY;
7413 lvFindInfo.vkDirection = VK_DOWN;
7414 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7415 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7417 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7418 return nItem;
7422 else if (uFlags & LVNI_TOLEFT)
7424 if (infoPtr->uView == LV_VIEW_LIST)
7426 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7427 while (nItem - nCountPerColumn >= 0)
7429 nItem -= nCountPerColumn;
7430 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7431 return nItem;
7434 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7436 /* Special case for autoarrange - move 'til the beginning of a row */
7437 if (is_autoarrange(infoPtr))
7439 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7440 while (nItem % nCountPerRow > 0)
7442 nItem --;
7443 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7444 return nItem;
7446 return -1;
7448 lvFindInfo.flags = LVFI_NEARESTXY;
7449 lvFindInfo.vkDirection = VK_LEFT;
7450 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7451 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7453 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7454 return nItem;
7458 else if (uFlags & LVNI_TORIGHT)
7460 if (infoPtr->uView == LV_VIEW_LIST)
7462 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7463 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7465 nItem += nCountPerColumn;
7466 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7467 return nItem;
7470 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7472 /* Special case for autoarrange - move 'til the end of a row */
7473 if (is_autoarrange(infoPtr))
7475 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7476 while (nItem % nCountPerRow < nCountPerRow - 1 )
7478 nItem ++;
7479 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7480 return nItem;
7482 return -1;
7484 lvFindInfo.flags = LVFI_NEARESTXY;
7485 lvFindInfo.vkDirection = VK_RIGHT;
7486 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7487 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7489 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7490 return nItem;
7494 else
7496 nItem++;
7498 /* search by index */
7499 for (i = nItem; i < infoPtr->nItemCount; i++)
7501 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7502 return i;
7506 return -1;
7509 /* LISTVIEW_GetNumberOfWorkAreas */
7511 /***
7512 * DESCRIPTION:
7513 * Retrieves the origin coordinates when in icon or small icon display mode.
7515 * PARAMETER(S):
7516 * [I] infoPtr : valid pointer to the listview structure
7517 * [O] lpptOrigin : coordinate information
7519 * RETURN:
7520 * None.
7522 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7524 INT nHorzPos = 0, nVertPos = 0;
7525 SCROLLINFO scrollInfo;
7527 scrollInfo.cbSize = sizeof(SCROLLINFO);
7528 scrollInfo.fMask = SIF_POS;
7530 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7531 nHorzPos = scrollInfo.nPos;
7532 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7533 nVertPos = scrollInfo.nPos;
7535 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7537 lpptOrigin->x = infoPtr->rcList.left;
7538 lpptOrigin->y = infoPtr->rcList.top;
7539 if (infoPtr->uView == LV_VIEW_LIST)
7540 nHorzPos *= infoPtr->nItemWidth;
7541 else if (infoPtr->uView == LV_VIEW_DETAILS)
7542 nVertPos *= infoPtr->nItemHeight;
7544 lpptOrigin->x -= nHorzPos;
7545 lpptOrigin->y -= nVertPos;
7547 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7550 /***
7551 * DESCRIPTION:
7552 * Retrieves the width of a string.
7554 * PARAMETER(S):
7555 * [I] hwnd : window handle
7556 * [I] lpszText : text string to process
7557 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7559 * RETURN:
7560 * SUCCESS : string width (in pixels)
7561 * FAILURE : zero
7563 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7565 SIZE stringSize;
7567 stringSize.cx = 0;
7568 if (is_text(lpszText))
7570 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7571 HDC hdc = GetDC(infoPtr->hwndSelf);
7572 HFONT hOldFont = SelectObject(hdc, hFont);
7574 if (isW)
7575 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7576 else
7577 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7578 SelectObject(hdc, hOldFont);
7579 ReleaseDC(infoPtr->hwndSelf, hdc);
7581 return stringSize.cx;
7584 /***
7585 * DESCRIPTION:
7586 * Determines which listview item is located at the specified position.
7588 * PARAMETER(S):
7589 * [I] infoPtr : valid pointer to the listview structure
7590 * [IO] lpht : hit test information
7591 * [I] subitem : fill out iSubItem.
7592 * [I] select : return the index only if the hit selects the item
7594 * NOTE:
7595 * (mm 20001022): We must not allow iSubItem to be touched, for
7596 * an app might pass only a structure with space up to iItem!
7597 * (MS Office 97 does that for instance in the file open dialog)
7599 * RETURN:
7600 * SUCCESS : item index
7601 * FAILURE : -1
7603 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7605 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7606 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7607 POINT Origin, Position, opt;
7608 BOOL is_fullrow;
7609 LVITEMW lvItem;
7610 ITERATOR i;
7611 INT iItem;
7613 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7615 lpht->flags = 0;
7616 lpht->iItem = -1;
7617 if (subitem) lpht->iSubItem = 0;
7619 LISTVIEW_GetOrigin(infoPtr, &Origin);
7621 /* set whole list relation flags */
7622 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7624 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7625 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7626 lpht->flags |= LVHT_TOLEFT;
7628 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7629 opt.y = lpht->pt.y + infoPtr->rcList.top;
7630 else
7631 opt.y = lpht->pt.y;
7633 if (infoPtr->rcList.bottom < opt.y)
7634 lpht->flags |= LVHT_BELOW;
7636 else
7638 if (infoPtr->rcList.left > lpht->pt.x)
7639 lpht->flags |= LVHT_TOLEFT;
7640 else if (infoPtr->rcList.right < lpht->pt.x)
7641 lpht->flags |= LVHT_TORIGHT;
7643 if (infoPtr->rcList.top > lpht->pt.y)
7644 lpht->flags |= LVHT_ABOVE;
7645 else if (infoPtr->rcList.bottom < lpht->pt.y)
7646 lpht->flags |= LVHT_BELOW;
7649 /* even if item is invalid try to find subitem */
7650 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7652 RECT *pRect;
7653 INT j;
7655 opt.x = lpht->pt.x - Origin.x;
7657 lpht->iSubItem = -1;
7658 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7660 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7662 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7664 lpht->iSubItem = j;
7665 break;
7668 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7670 /* if we're outside horizontal columns bounds there's nothing to test further */
7671 if (lpht->iSubItem == -1)
7673 lpht->iItem = -1;
7674 lpht->flags = LVHT_NOWHERE;
7675 return -1;
7679 TRACE("lpht->flags=0x%x\n", lpht->flags);
7680 if (lpht->flags) return -1;
7682 lpht->flags |= LVHT_NOWHERE;
7684 /* first deal with the large items */
7685 rcSearch.left = lpht->pt.x;
7686 rcSearch.top = lpht->pt.y;
7687 rcSearch.right = rcSearch.left + 1;
7688 rcSearch.bottom = rcSearch.top + 1;
7690 iterator_frameditems(&i, infoPtr, &rcSearch);
7691 iterator_next(&i); /* go to first item in the sequence */
7692 iItem = i.nItem;
7693 iterator_destroy(&i);
7695 TRACE("lpht->iItem=%d\n", iItem);
7696 if (iItem == -1) return -1;
7698 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7699 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7700 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7701 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7702 lvItem.iItem = iItem;
7703 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7704 lvItem.pszText = szDispText;
7705 lvItem.cchTextMax = DISP_TEXT_SIZE;
7706 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7707 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7709 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7710 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7711 opt.x = lpht->pt.x - Position.x - Origin.x;
7713 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7714 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7715 else
7716 opt.y = lpht->pt.y - Position.y - Origin.y;
7718 if (infoPtr->uView == LV_VIEW_DETAILS)
7720 rcBounds = rcBox;
7721 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7722 opt.x = lpht->pt.x - Origin.x;
7724 else
7726 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7727 UnionRect(&rcBounds, &rcBounds, &rcState);
7729 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7730 if (!PtInRect(&rcBounds, opt)) return -1;
7732 /* That's a special case - row rectangle is used as item rectangle and
7733 returned flags contain all item parts. */
7734 is_fullrow = (infoPtr->uView == LV_VIEW_DETAILS) && ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED));
7736 if (PtInRect(&rcIcon, opt))
7737 lpht->flags |= LVHT_ONITEMICON;
7738 else if (PtInRect(&rcLabel, opt))
7739 lpht->flags |= LVHT_ONITEMLABEL;
7740 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7741 lpht->flags |= LVHT_ONITEMSTATEICON;
7742 if (is_fullrow && !(lpht->flags & LVHT_ONITEM))
7744 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7746 if (lpht->flags & LVHT_ONITEM)
7747 lpht->flags &= ~LVHT_NOWHERE;
7748 TRACE("lpht->flags=0x%x\n", lpht->flags);
7750 if (select && !is_fullrow)
7752 if (infoPtr->uView == LV_VIEW_DETAILS)
7754 /* get main item bounds */
7755 lvItem.iSubItem = 0;
7756 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7757 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7758 UnionRect(&rcBounds, &rcBounds, &rcState);
7760 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7762 return lpht->iItem = iItem;
7765 /***
7766 * DESCRIPTION:
7767 * Inserts a new item in the listview control.
7769 * PARAMETER(S):
7770 * [I] infoPtr : valid pointer to the listview structure
7771 * [I] lpLVItem : item information
7772 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7774 * RETURN:
7775 * SUCCESS : new item index
7776 * FAILURE : -1
7778 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7780 INT nItem;
7781 HDPA hdpaSubItems;
7782 NMLISTVIEW nmlv;
7783 ITEM_INFO *lpItem;
7784 ITEM_ID *lpID;
7785 BOOL is_sorted, has_changed;
7786 LVITEMW item;
7787 HWND hwndSelf = infoPtr->hwndSelf;
7789 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7791 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7793 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7794 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7796 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7798 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7800 /* insert item in listview control data structure */
7801 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7802 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7804 /* link with id struct */
7805 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7806 lpItem->id = lpID;
7807 lpID->item = hdpaSubItems;
7808 lpID->id = get_next_itemid(infoPtr);
7809 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7811 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7812 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7814 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7816 /* calculate new item index */
7817 if (is_sorted)
7819 HDPA hItem;
7820 ITEM_INFO *item_s;
7821 INT i = 0, cmpv;
7822 WCHAR *textW;
7824 textW = textdupTtoW(lpLVItem->pszText, isW);
7826 while (i < infoPtr->nItemCount)
7828 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7829 item_s = DPA_GetPtr(hItem, 0);
7831 cmpv = textcmpWT(item_s->hdr.pszText, textW, TRUE);
7832 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7834 if (cmpv >= 0) break;
7835 i++;
7838 textfreeT(textW, isW);
7840 nItem = i;
7842 else
7843 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7845 TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7846 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7847 if (nItem == -1) goto fail;
7848 infoPtr->nItemCount++;
7850 /* shift indices first so they don't get tangled */
7851 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7853 /* set the item attributes */
7854 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7856 /* full size structure expected - _WIN32IE >= 0x560 */
7857 item = *lpLVItem;
7859 else if (lpLVItem->mask & LVIF_INDENT)
7861 /* indent member expected - _WIN32IE >= 0x300 */
7862 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7864 else
7866 /* minimal structure expected */
7867 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7869 item.iItem = nItem;
7870 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7872 if (item.mask & LVIF_STATE)
7874 item.stateMask |= LVIS_STATEIMAGEMASK;
7875 item.state &= ~LVIS_STATEIMAGEMASK;
7876 item.state |= INDEXTOSTATEIMAGEMASK(1);
7878 else
7880 item.mask |= LVIF_STATE;
7881 item.stateMask = LVIS_STATEIMAGEMASK;
7882 item.state = INDEXTOSTATEIMAGEMASK(1);
7886 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7888 /* make room for the position, if we are in the right mode */
7889 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7891 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7892 goto undo;
7893 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7895 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7896 goto undo;
7900 /* send LVN_INSERTITEM notification */
7901 memset(&nmlv, 0, sizeof(NMLISTVIEW));
7902 nmlv.iItem = nItem;
7903 nmlv.lParam = lpItem->lParam;
7904 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7905 if (!IsWindow(hwndSelf))
7906 return -1;
7908 /* align items (set position of each item) */
7909 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7911 POINT pt;
7913 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7914 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7915 else
7916 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7918 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7921 /* now is the invalidation fun */
7922 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7923 return nItem;
7925 undo:
7926 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7927 LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
7928 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7929 infoPtr->nItemCount--;
7930 fail:
7931 DPA_DeletePtr(hdpaSubItems, 0);
7932 DPA_Destroy (hdpaSubItems);
7933 Free (lpItem);
7934 return -1;
7937 /***
7938 * DESCRIPTION:
7939 * Checks item visibility.
7941 * PARAMETER(S):
7942 * [I] infoPtr : valid pointer to the listview structure
7943 * [I] nFirst : item index to check for
7945 * RETURN:
7946 * Item visible : TRUE
7947 * Item invisible or failure : FALSE
7949 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7951 POINT Origin, Position;
7952 RECT rcItem;
7953 HDC hdc;
7954 BOOL ret;
7956 TRACE("nItem=%d\n", nItem);
7958 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7960 LISTVIEW_GetOrigin(infoPtr, &Origin);
7961 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7962 rcItem.left = Position.x + Origin.x;
7963 rcItem.top = Position.y + Origin.y;
7964 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7965 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7967 hdc = GetDC(infoPtr->hwndSelf);
7968 if (!hdc) return FALSE;
7969 ret = RectVisible(hdc, &rcItem);
7970 ReleaseDC(infoPtr->hwndSelf, hdc);
7972 return ret;
7975 /***
7976 * DESCRIPTION:
7977 * Redraws a range of items.
7979 * PARAMETER(S):
7980 * [I] infoPtr : valid pointer to the listview structure
7981 * [I] nFirst : first item
7982 * [I] nLast : last item
7984 * RETURN:
7985 * SUCCESS : TRUE
7986 * FAILURE : FALSE
7988 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
7990 INT i;
7992 for (i = max(nFirst, 0); i <= min(nLast, infoPtr->nItemCount - 1); i++)
7993 LISTVIEW_InvalidateItem(infoPtr, i);
7995 return TRUE;
7998 /***
7999 * DESCRIPTION:
8000 * Scroll the content of a listview.
8002 * PARAMETER(S):
8003 * [I] infoPtr : valid pointer to the listview structure
8004 * [I] dx : horizontal scroll amount in pixels
8005 * [I] dy : vertical scroll amount in pixels
8007 * RETURN:
8008 * SUCCESS : TRUE
8009 * FAILURE : FALSE
8011 * COMMENTS:
8012 * If the control is in report view (LV_VIEW_DETAILS) the control can
8013 * be scrolled only in line increments. "dy" will be rounded to the
8014 * nearest number of pixels that are a whole line. Ex: if line height
8015 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
8016 * is passed, then the scroll will be 0. (per MSDN 7/2002)
8018 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8020 switch(infoPtr->uView) {
8021 case LV_VIEW_DETAILS:
8022 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
8023 dy /= infoPtr->nItemHeight;
8024 break;
8025 case LV_VIEW_LIST:
8026 if (dy != 0) return FALSE;
8027 break;
8028 default: /* icon */
8029 break;
8032 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
8033 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
8035 return TRUE;
8038 /***
8039 * DESCRIPTION:
8040 * Sets the background color.
8042 * PARAMETER(S):
8043 * [I] infoPtr : valid pointer to the listview structure
8044 * [I] color : background color
8046 * RETURN:
8047 * SUCCESS : TRUE
8048 * FAILURE : FALSE
8050 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8052 TRACE("(color=%x)\n", color);
8054 if(infoPtr->clrBk != color) {
8055 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8056 infoPtr->clrBk = color;
8057 if (color == CLR_NONE)
8058 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
8059 else
8061 infoPtr->hBkBrush = CreateSolidBrush(color);
8062 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
8066 return TRUE;
8069 /* LISTVIEW_SetBkImage */
8071 /*** Helper for {Insert,Set}ColumnT *only* */
8072 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
8073 const LVCOLUMNW *lpColumn, BOOL isW)
8075 if (lpColumn->mask & LVCF_FMT)
8077 /* format member is valid */
8078 lphdi->mask |= HDI_FORMAT;
8080 /* set text alignment (leftmost column must be left-aligned) */
8081 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8082 lphdi->fmt |= HDF_LEFT;
8083 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
8084 lphdi->fmt |= HDF_RIGHT;
8085 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
8086 lphdi->fmt |= HDF_CENTER;
8088 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
8089 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
8091 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
8093 lphdi->fmt |= HDF_IMAGE;
8094 lphdi->iImage = I_IMAGECALLBACK;
8097 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
8098 lphdi->fmt |= HDF_FIXEDWIDTH;
8101 if (lpColumn->mask & LVCF_WIDTH)
8103 lphdi->mask |= HDI_WIDTH;
8104 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
8106 /* make it fill the remainder of the controls width */
8107 RECT rcHeader;
8108 INT item_index;
8110 for(item_index = 0; item_index < (nColumn - 1); item_index++)
8112 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
8113 lphdi->cxy += rcHeader.right - rcHeader.left;
8116 /* retrieve the layout of the header */
8117 GetClientRect(infoPtr->hwndSelf, &rcHeader);
8118 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
8120 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
8122 else
8123 lphdi->cxy = lpColumn->cx;
8126 if (lpColumn->mask & LVCF_TEXT)
8128 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8129 lphdi->fmt |= HDF_STRING;
8130 lphdi->pszText = lpColumn->pszText;
8131 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8134 if (lpColumn->mask & LVCF_IMAGE)
8136 lphdi->mask |= HDI_IMAGE;
8137 lphdi->iImage = lpColumn->iImage;
8140 if (lpColumn->mask & LVCF_ORDER)
8142 lphdi->mask |= HDI_ORDER;
8143 lphdi->iOrder = lpColumn->iOrder;
8148 /***
8149 * DESCRIPTION:
8150 * Inserts a new column.
8152 * PARAMETER(S):
8153 * [I] infoPtr : valid pointer to the listview structure
8154 * [I] nColumn : column index
8155 * [I] lpColumn : column information
8156 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8158 * RETURN:
8159 * SUCCESS : new column index
8160 * FAILURE : -1
8162 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8163 const LVCOLUMNW *lpColumn, BOOL isW)
8165 COLUMN_INFO *lpColumnInfo;
8166 INT nNewColumn;
8167 HDITEMW hdi;
8169 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8171 if (!lpColumn || nColumn < 0) return -1;
8172 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8174 ZeroMemory(&hdi, sizeof(HDITEMW));
8175 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8178 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8179 * (can be seen in SPY) otherwise column never gets added.
8181 if (!(lpColumn->mask & LVCF_WIDTH)) {
8182 hdi.mask |= HDI_WIDTH;
8183 hdi.cxy = 10;
8187 * when the iSubItem is available Windows copies it to the header lParam. It seems
8188 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8190 if (lpColumn->mask & LVCF_SUBITEM)
8192 hdi.mask |= HDI_LPARAM;
8193 hdi.lParam = lpColumn->iSubItem;
8196 /* create header if not present */
8197 LISTVIEW_CreateHeader(infoPtr);
8198 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8199 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8201 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8204 /* insert item in header control */
8205 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8206 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8207 nColumn, (LPARAM)&hdi);
8208 if (nNewColumn == -1) return -1;
8209 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8211 /* create our own column info */
8212 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8213 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8215 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8216 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8217 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8218 goto fail;
8220 /* now we have to actually adjust the data */
8221 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8223 SUBITEM_INFO *lpSubItem;
8224 HDPA hdpaSubItems;
8225 INT nItem, i;
8226 LVITEMW item;
8227 BOOL changed;
8229 item.iSubItem = nNewColumn;
8230 item.mask = LVIF_TEXT | LVIF_IMAGE;
8231 item.iImage = I_IMAGECALLBACK;
8232 item.pszText = LPSTR_TEXTCALLBACKW;
8234 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8236 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8237 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8239 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8240 if (lpSubItem->iSubItem >= nNewColumn)
8241 lpSubItem->iSubItem++;
8244 /* add new subitem for each item */
8245 item.iItem = nItem;
8246 set_sub_item(infoPtr, &item, isW, &changed);
8250 /* make space for the new column */
8251 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8252 LISTVIEW_UpdateItemSize(infoPtr);
8254 return nNewColumn;
8256 fail:
8257 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8258 if (lpColumnInfo)
8260 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8261 Free(lpColumnInfo);
8263 return -1;
8266 /***
8267 * DESCRIPTION:
8268 * Sets the attributes of a header item.
8270 * PARAMETER(S):
8271 * [I] infoPtr : valid pointer to the listview structure
8272 * [I] nColumn : column index
8273 * [I] lpColumn : column attributes
8274 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8276 * RETURN:
8277 * SUCCESS : TRUE
8278 * FAILURE : FALSE
8280 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8281 const LVCOLUMNW *lpColumn, BOOL isW)
8283 HDITEMW hdi, hdiget;
8284 BOOL bResult;
8286 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8288 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8290 ZeroMemory(&hdi, sizeof(HDITEMW));
8291 if (lpColumn->mask & LVCF_FMT)
8293 hdi.mask |= HDI_FORMAT;
8294 hdiget.mask = HDI_FORMAT;
8295 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8296 hdi.fmt = hdiget.fmt & HDF_STRING;
8298 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8300 /* set header item attributes */
8301 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8302 if (!bResult) return FALSE;
8304 if (lpColumn->mask & LVCF_FMT)
8306 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8307 INT oldFmt = lpColumnInfo->fmt;
8309 lpColumnInfo->fmt = lpColumn->fmt;
8310 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8312 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8316 if (lpColumn->mask & LVCF_MINWIDTH)
8317 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8319 return TRUE;
8322 /***
8323 * DESCRIPTION:
8324 * Sets the column order array
8326 * PARAMETERS:
8327 * [I] infoPtr : valid pointer to the listview structure
8328 * [I] iCount : number of elements in column order array
8329 * [I] lpiArray : pointer to column order array
8331 * RETURN:
8332 * SUCCESS : TRUE
8333 * FAILURE : FALSE
8335 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8337 if (!infoPtr->hwndHeader) return FALSE;
8338 infoPtr->colRectsDirty = TRUE;
8339 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8342 /***
8343 * DESCRIPTION:
8344 * Sets the width of a column
8346 * PARAMETERS:
8347 * [I] infoPtr : valid pointer to the listview structure
8348 * [I] nColumn : column index
8349 * [I] cx : column width
8351 * RETURN:
8352 * SUCCESS : TRUE
8353 * FAILURE : FALSE
8355 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8357 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8358 INT max_cx = 0;
8359 HDITEMW hdi;
8361 TRACE("(nColumn=%d, cx=%d)\n", nColumn, cx);
8363 /* set column width only if in report or list mode */
8364 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8366 /* take care of invalid cx values - LVSCW_AUTOSIZE_* values are negative,
8367 with _USEHEADER being the lowest */
8368 if (infoPtr->uView == LV_VIEW_DETAILS && cx < LVSCW_AUTOSIZE_USEHEADER) cx = LVSCW_AUTOSIZE;
8369 else if (infoPtr->uView == LV_VIEW_LIST && cx <= 0) return FALSE;
8371 /* resize all columns if in LV_VIEW_LIST mode */
8372 if(infoPtr->uView == LV_VIEW_LIST)
8374 infoPtr->nItemWidth = cx;
8375 LISTVIEW_InvalidateList(infoPtr);
8376 return TRUE;
8379 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8381 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8383 INT nLabelWidth;
8384 LVITEMW lvItem;
8386 lvItem.mask = LVIF_TEXT;
8387 lvItem.iItem = 0;
8388 lvItem.iSubItem = nColumn;
8389 lvItem.cchTextMax = DISP_TEXT_SIZE;
8390 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8392 lvItem.pszText = szDispText;
8393 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8394 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8395 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8397 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8398 max_cx += infoPtr->iconSize.cx;
8399 max_cx += TRAILING_LABEL_PADDING;
8400 if (nColumn == 0 && (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES))
8401 max_cx += GetSystemMetrics(SM_CXSMICON);
8404 /* autosize based on listview items width */
8405 if(cx == LVSCW_AUTOSIZE)
8406 cx = max_cx;
8407 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8409 /* if iCol is the last column make it fill the remainder of the controls width */
8410 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8412 RECT rcHeader;
8413 POINT Origin;
8415 LISTVIEW_GetOrigin(infoPtr, &Origin);
8416 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8418 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8420 else
8422 /* Despite what the MS docs say, if this is not the last
8423 column, then MS resizes the column to the width of the
8424 largest text string in the column, including headers
8425 and items. This is different from LVSCW_AUTOSIZE in that
8426 LVSCW_AUTOSIZE ignores the header string length. */
8427 cx = 0;
8429 /* retrieve header text */
8430 hdi.mask = HDI_TEXT|HDI_FORMAT|HDI_IMAGE|HDI_BITMAP;
8431 hdi.cchTextMax = DISP_TEXT_SIZE;
8432 hdi.pszText = szDispText;
8433 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8435 HDC hdc = GetDC(infoPtr->hwndSelf);
8436 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8437 HIMAGELIST himl = (HIMAGELIST)SendMessageW(infoPtr->hwndHeader, HDM_GETIMAGELIST, 0, 0);
8438 INT bitmap_margin = 0;
8439 SIZE size;
8441 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8442 cx = size.cx + TRAILING_HEADER_PADDING;
8444 if (hdi.fmt & (HDF_IMAGE|HDF_BITMAP))
8445 bitmap_margin = SendMessageW(infoPtr->hwndHeader, HDM_GETBITMAPMARGIN, 0, 0);
8447 if ((hdi.fmt & HDF_IMAGE) && himl)
8449 INT icon_cx, icon_cy;
8451 if (!ImageList_GetIconSize(himl, &icon_cx, &icon_cy))
8452 cx += icon_cx + 2*bitmap_margin;
8454 else if (hdi.fmt & HDF_BITMAP)
8456 BITMAP bmp;
8458 GetObjectW(hdi.hbm, sizeof(BITMAP), &bmp);
8459 cx += bmp.bmWidth + 2*bitmap_margin;
8462 SelectObject(hdc, old_font);
8463 ReleaseDC(infoPtr->hwndSelf, hdc);
8465 cx = max (cx, max_cx);
8469 if (cx < 0) return FALSE;
8471 /* call header to update the column change */
8472 hdi.mask = HDI_WIDTH;
8473 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8474 TRACE("hdi.cxy=%d\n", hdi.cxy);
8475 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8478 /***
8479 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8482 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8484 HDC hdc_wnd, hdc;
8485 HBITMAP hbm_im, hbm_mask, hbm_orig;
8486 RECT rc;
8487 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8488 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8489 HIMAGELIST himl;
8491 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8492 ILC_COLOR | ILC_MASK, 2, 2);
8493 hdc_wnd = GetDC(infoPtr->hwndSelf);
8494 hdc = CreateCompatibleDC(hdc_wnd);
8495 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8496 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8497 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8499 SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8500 hbm_orig = SelectObject(hdc, hbm_mask);
8501 FillRect(hdc, &rc, hbr_white);
8502 InflateRect(&rc, -2, -2);
8503 FillRect(hdc, &rc, hbr_black);
8505 SelectObject(hdc, hbm_im);
8506 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8507 SelectObject(hdc, hbm_orig);
8508 ImageList_Add(himl, hbm_im, hbm_mask);
8510 SelectObject(hdc, hbm_im);
8511 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8512 SelectObject(hdc, hbm_orig);
8513 ImageList_Add(himl, hbm_im, hbm_mask);
8515 DeleteObject(hbm_mask);
8516 DeleteObject(hbm_im);
8517 DeleteDC(hdc);
8519 return himl;
8522 /***
8523 * DESCRIPTION:
8524 * Sets the extended listview style.
8526 * PARAMETERS:
8527 * [I] infoPtr : valid pointer to the listview structure
8528 * [I] dwMask : mask
8529 * [I] dwStyle : style
8531 * RETURN:
8532 * SUCCESS : previous style
8533 * FAILURE : 0
8535 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8537 DWORD old_ex_style = infoPtr->dwLvExStyle;
8539 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8541 /* set new style */
8542 if (mask)
8543 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8544 else
8545 infoPtr->dwLvExStyle = ex_style;
8547 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8549 HIMAGELIST himl = 0;
8550 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8552 LVITEMW item;
8553 item.mask = LVIF_STATE;
8554 item.stateMask = LVIS_STATEIMAGEMASK;
8555 item.state = INDEXTOSTATEIMAGEMASK(1);
8556 LISTVIEW_SetItemState(infoPtr, -1, &item);
8558 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8559 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8560 ImageList_Destroy(infoPtr->himlState);
8562 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8563 /* checkbox list replaces previous custom list or... */
8564 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8565 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8566 /* ...previous was checkbox list */
8567 (old_ex_style & LVS_EX_CHECKBOXES))
8568 ImageList_Destroy(himl);
8571 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8573 DWORD style;
8575 /* if not already created */
8576 LISTVIEW_CreateHeader(infoPtr);
8578 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8579 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8580 style |= HDS_DRAGDROP;
8581 else
8582 style &= ~HDS_DRAGDROP;
8583 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8586 /* GRIDLINES adds decoration at top so changes sizes */
8587 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8589 LISTVIEW_CreateHeader(infoPtr);
8590 LISTVIEW_UpdateSize(infoPtr);
8593 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8595 LISTVIEW_CreateHeader(infoPtr);
8598 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8600 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8601 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8604 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8606 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8607 LISTVIEW_CreateHeader(infoPtr);
8608 else
8609 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8610 LISTVIEW_UpdateSize(infoPtr);
8611 LISTVIEW_UpdateScroll(infoPtr);
8614 LISTVIEW_InvalidateList(infoPtr);
8615 return old_ex_style;
8618 /***
8619 * DESCRIPTION:
8620 * Sets the new hot cursor used during hot tracking and hover selection.
8622 * PARAMETER(S):
8623 * [I] infoPtr : valid pointer to the listview structure
8624 * [I] hCursor : the new hot cursor handle
8626 * RETURN:
8627 * Returns the previous hot cursor
8629 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8631 HCURSOR oldCursor = infoPtr->hHotCursor;
8633 infoPtr->hHotCursor = hCursor;
8635 return oldCursor;
8639 /***
8640 * DESCRIPTION:
8641 * Sets the hot item index.
8643 * PARAMETERS:
8644 * [I] infoPtr : valid pointer to the listview structure
8645 * [I] iIndex : index
8647 * RETURN:
8648 * SUCCESS : previous hot item index
8649 * FAILURE : -1 (no hot item)
8651 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8653 INT iOldIndex = infoPtr->nHotItem;
8655 infoPtr->nHotItem = iIndex;
8657 return iOldIndex;
8661 /***
8662 * DESCRIPTION:
8663 * Sets the amount of time the cursor must hover over an item before it is selected.
8665 * PARAMETER(S):
8666 * [I] infoPtr : valid pointer to the listview structure
8667 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8669 * RETURN:
8670 * Returns the previous hover time
8672 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8674 DWORD oldHoverTime = infoPtr->dwHoverTime;
8676 infoPtr->dwHoverTime = dwHoverTime;
8678 return oldHoverTime;
8681 /***
8682 * DESCRIPTION:
8683 * Sets spacing for icons of LVS_ICON style.
8685 * PARAMETER(S):
8686 * [I] infoPtr : valid pointer to the listview structure
8687 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8688 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8690 * RETURN:
8691 * MAKELONG(oldcx, oldcy)
8693 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8695 INT iconWidth = 0, iconHeight = 0;
8696 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8698 TRACE("requested=(%d,%d)\n", cx, cy);
8700 /* set to defaults, if instructed to */
8701 if (cx == -1 && cy == -1)
8703 infoPtr->autoSpacing = TRUE;
8704 if (infoPtr->himlNormal)
8705 ImageList_GetIconSize(infoPtr->himlNormal, &iconWidth, &iconHeight);
8706 cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON) + iconWidth;
8707 cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON) + iconHeight;
8709 else
8710 infoPtr->autoSpacing = FALSE;
8712 /* if 0 then keep width */
8713 if (cx != 0)
8714 infoPtr->iconSpacing.cx = cx;
8716 /* if 0 then keep height */
8717 if (cy != 0)
8718 infoPtr->iconSpacing.cy = cy;
8720 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8721 LOWORD(oldspacing), HIWORD(oldspacing), infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy,
8722 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8723 infoPtr->ntmHeight);
8725 /* these depend on the iconSpacing */
8726 LISTVIEW_UpdateItemSize(infoPtr);
8728 return oldspacing;
8731 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8733 INT cx, cy;
8735 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8737 size->cx = cx;
8738 size->cy = cy;
8740 else
8742 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8743 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8747 /***
8748 * DESCRIPTION:
8749 * Sets image lists.
8751 * PARAMETER(S):
8752 * [I] infoPtr : valid pointer to the listview structure
8753 * [I] nType : image list type
8754 * [I] himl : image list handle
8756 * RETURN:
8757 * SUCCESS : old image list
8758 * FAILURE : NULL
8760 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8762 INT oldHeight = infoPtr->nItemHeight;
8763 HIMAGELIST himlOld = 0;
8765 TRACE("(nType=%d, himl=%p)\n", nType, himl);
8767 switch (nType)
8769 case LVSIL_NORMAL:
8770 himlOld = infoPtr->himlNormal;
8771 infoPtr->himlNormal = himl;
8772 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8773 if (infoPtr->autoSpacing)
8774 LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
8775 break;
8777 case LVSIL_SMALL:
8778 himlOld = infoPtr->himlSmall;
8779 infoPtr->himlSmall = himl;
8780 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8781 if (infoPtr->hwndHeader)
8782 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
8783 break;
8785 case LVSIL_STATE:
8786 himlOld = infoPtr->himlState;
8787 infoPtr->himlState = himl;
8788 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8789 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8790 break;
8792 default:
8793 ERR("Unknown icon type=%d\n", nType);
8794 return NULL;
8797 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8798 if (infoPtr->nItemHeight != oldHeight)
8799 LISTVIEW_UpdateScroll(infoPtr);
8801 return himlOld;
8804 /***
8805 * DESCRIPTION:
8806 * Preallocates memory (does *not* set the actual count of items !)
8808 * PARAMETER(S):
8809 * [I] infoPtr : valid pointer to the listview structure
8810 * [I] nItems : item count (projected number of items to allocate)
8811 * [I] dwFlags : update flags
8813 * RETURN:
8814 * SUCCESS : TRUE
8815 * FAILURE : FALSE
8817 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8819 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8821 if (infoPtr->dwStyle & LVS_OWNERDATA)
8823 INT nOldCount = infoPtr->nItemCount;
8824 infoPtr->nItemCount = nItems;
8826 if (nItems < nOldCount)
8828 RANGE range = { nItems, nOldCount };
8829 ranges_del(infoPtr->selectionRanges, range);
8830 if (infoPtr->nFocusedItem >= nItems)
8832 LISTVIEW_SetItemFocus(infoPtr, -1);
8833 infoPtr->nFocusedItem = -1;
8834 SetRectEmpty(&infoPtr->rcFocus);
8838 LISTVIEW_UpdateScroll(infoPtr);
8840 /* the flags are valid only in ownerdata report and list modes */
8841 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8843 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8844 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8846 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8847 LISTVIEW_InvalidateList(infoPtr);
8848 else
8850 INT nFrom, nTo;
8851 POINT Origin;
8852 RECT rcErase;
8854 LISTVIEW_GetOrigin(infoPtr, &Origin);
8855 nFrom = min(nOldCount, nItems);
8856 nTo = max(nOldCount, nItems);
8858 if (infoPtr->uView == LV_VIEW_DETAILS)
8860 SetRect(&rcErase, 0, nFrom * infoPtr->nItemHeight, infoPtr->nItemWidth,
8861 nTo * infoPtr->nItemHeight);
8862 OffsetRect(&rcErase, Origin.x, Origin.y);
8863 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8864 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8866 else /* LV_VIEW_LIST */
8868 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8870 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8871 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8872 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8873 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8874 OffsetRect(&rcErase, Origin.x, Origin.y);
8875 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8876 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8878 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8879 rcErase.top = 0;
8880 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8881 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8882 OffsetRect(&rcErase, Origin.x, Origin.y);
8883 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8884 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8888 else
8890 /* According to MSDN for non-LVS_OWNERDATA this is just
8891 * a performance issue. The control allocates its internal
8892 * data structures for the number of items specified. It
8893 * cuts down on the number of memory allocations. Therefore
8894 * we will just issue a WARN here
8896 WARN("for non-ownerdata performance option not implemented.\n");
8899 return TRUE;
8902 /***
8903 * DESCRIPTION:
8904 * Sets the position of an item.
8906 * PARAMETER(S):
8907 * [I] infoPtr : valid pointer to the listview structure
8908 * [I] nItem : item index
8909 * [I] pt : coordinate
8911 * RETURN:
8912 * SUCCESS : TRUE
8913 * FAILURE : FALSE
8915 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8917 POINT Origin, Pt;
8919 TRACE("(nItem=%d, pt=%s)\n", nItem, wine_dbgstr_point(pt));
8921 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8922 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8924 Pt = *pt;
8925 LISTVIEW_GetOrigin(infoPtr, &Origin);
8927 /* This point value seems to be an undocumented feature.
8928 * The best guess is that it means either at the origin,
8929 * or at true beginning of the list. I will assume the origin. */
8930 if ((Pt.x == -1) && (Pt.y == -1))
8931 Pt = Origin;
8933 if (infoPtr->uView == LV_VIEW_ICON)
8935 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8936 Pt.y -= ICON_TOP_PADDING;
8938 Pt.x -= Origin.x;
8939 Pt.y -= Origin.y;
8941 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8944 /***
8945 * DESCRIPTION:
8946 * Sets the state of one or many items.
8948 * PARAMETER(S):
8949 * [I] infoPtr : valid pointer to the listview structure
8950 * [I] nItem : item index
8951 * [I] item : item or subitem info
8953 * RETURN:
8954 * SUCCESS : TRUE
8955 * FAILURE : FALSE
8957 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8959 BOOL ret = TRUE;
8960 LVITEMW lvItem;
8962 if (!item) return FALSE;
8964 lvItem.iItem = nItem;
8965 lvItem.iSubItem = 0;
8966 lvItem.mask = LVIF_STATE;
8967 lvItem.state = item->state;
8968 lvItem.stateMask = item->stateMask;
8969 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8971 if (nItem == -1)
8973 UINT oldstate = 0;
8974 BOOL notify;
8976 /* special case optimization for recurring attempt to deselect all */
8977 if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
8978 return TRUE;
8980 /* select all isn't allowed in LVS_SINGLESEL */
8981 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
8982 return FALSE;
8984 /* focus all isn't allowed */
8985 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
8987 notify = infoPtr->bDoChangeNotify;
8988 if (infoPtr->dwStyle & LVS_OWNERDATA)
8990 infoPtr->bDoChangeNotify = FALSE;
8991 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
8992 oldstate |= LVIS_SELECTED;
8993 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
8996 /* apply to all items */
8997 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8998 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
9000 if (infoPtr->dwStyle & LVS_OWNERDATA)
9002 NMLISTVIEW nmlv;
9004 infoPtr->bDoChangeNotify = notify;
9006 nmlv.iItem = -1;
9007 nmlv.iSubItem = 0;
9008 nmlv.uNewState = lvItem.state & lvItem.stateMask;
9009 nmlv.uOldState = oldstate & lvItem.stateMask;
9010 nmlv.uChanged = LVIF_STATE;
9011 nmlv.ptAction.x = nmlv.ptAction.y = 0;
9012 nmlv.lParam = 0;
9014 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
9017 else
9018 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
9020 return ret;
9023 /***
9024 * DESCRIPTION:
9025 * Sets the text of an item or subitem.
9027 * PARAMETER(S):
9028 * [I] hwnd : window handle
9029 * [I] nItem : item index
9030 * [I] lpLVItem : item or subitem info
9031 * [I] isW : TRUE if input is Unicode
9033 * RETURN:
9034 * SUCCESS : TRUE
9035 * FAILURE : FALSE
9037 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
9039 LVITEMW lvItem;
9041 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9042 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9044 lvItem.iItem = nItem;
9045 lvItem.iSubItem = lpLVItem->iSubItem;
9046 lvItem.mask = LVIF_TEXT;
9047 lvItem.pszText = lpLVItem->pszText;
9048 lvItem.cchTextMax = lpLVItem->cchTextMax;
9050 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
9052 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
9055 /***
9056 * DESCRIPTION:
9057 * Set item index that marks the start of a multiple selection.
9059 * PARAMETER(S):
9060 * [I] infoPtr : valid pointer to the listview structure
9061 * [I] nIndex : index
9063 * RETURN:
9064 * Index number or -1 if there is no selection mark.
9066 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
9068 INT nOldIndex = infoPtr->nSelectionMark;
9070 TRACE("(nIndex=%d)\n", nIndex);
9072 if (nIndex >= -1 && nIndex < infoPtr->nItemCount)
9073 infoPtr->nSelectionMark = nIndex;
9075 return nOldIndex;
9078 /***
9079 * DESCRIPTION:
9080 * Sets the text background color.
9082 * PARAMETER(S):
9083 * [I] infoPtr : valid pointer to the listview structure
9084 * [I] color : text background color
9086 * RETURN:
9087 * SUCCESS : TRUE
9088 * FAILURE : FALSE
9090 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
9092 TRACE("(color=%x)\n", color);
9094 infoPtr->clrTextBk = color;
9095 return TRUE;
9098 /***
9099 * DESCRIPTION:
9100 * Sets the text foreground color.
9102 * PARAMETER(S):
9103 * [I] infoPtr : valid pointer to the listview structure
9104 * [I] color : text color
9106 * RETURN:
9107 * SUCCESS : TRUE
9108 * FAILURE : FALSE
9110 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
9112 TRACE("(color=%x)\n", color);
9114 infoPtr->clrText = color;
9115 return TRUE;
9118 /***
9119 * DESCRIPTION:
9120 * Sets new ToolTip window to ListView control.
9122 * PARAMETER(S):
9123 * [I] infoPtr : valid pointer to the listview structure
9124 * [I] hwndNewToolTip : handle to new ToolTip
9126 * RETURN:
9127 * old tool tip
9129 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
9131 HWND hwndOldToolTip = infoPtr->hwndToolTip;
9132 infoPtr->hwndToolTip = hwndNewToolTip;
9133 return hwndOldToolTip;
9137 * DESCRIPTION:
9138 * sets the Unicode character format flag for the control
9139 * PARAMETER(S):
9140 * [I] infoPtr :valid pointer to the listview structure
9141 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9143 * RETURN:
9144 * Old Unicode Format
9146 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9148 SHORT rc = infoPtr->notifyFormat;
9149 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9150 return rc == NFR_UNICODE;
9154 * DESCRIPTION:
9155 * sets the control view mode
9156 * PARAMETER(S):
9157 * [I] infoPtr :valid pointer to the listview structure
9158 * [I] nView :new view mode value
9160 * RETURN:
9161 * SUCCESS: 1
9162 * FAILURE: -1
9164 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9166 HIMAGELIST himl;
9168 if (infoPtr->uView == nView) return 1;
9170 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9171 if (nView == LV_VIEW_TILE)
9173 FIXME("View LV_VIEW_TILE unimplemented\n");
9174 return -1;
9177 infoPtr->uView = nView;
9179 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9180 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9182 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9183 SetRectEmpty(&infoPtr->rcFocus);
9185 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9186 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9188 switch (nView)
9190 case LV_VIEW_ICON:
9191 case LV_VIEW_SMALLICON:
9192 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9193 break;
9194 case LV_VIEW_DETAILS:
9196 HDLAYOUT hl;
9197 WINDOWPOS wp;
9199 LISTVIEW_CreateHeader( infoPtr );
9201 hl.prc = &infoPtr->rcList;
9202 hl.pwpos = &wp;
9203 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9204 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9205 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9206 break;
9208 case LV_VIEW_LIST:
9209 break;
9212 LISTVIEW_UpdateItemSize(infoPtr);
9213 LISTVIEW_UpdateSize(infoPtr);
9214 LISTVIEW_UpdateScroll(infoPtr);
9215 LISTVIEW_InvalidateList(infoPtr);
9217 TRACE("nView=%d\n", nView);
9219 return 1;
9222 /* LISTVIEW_SetWorkAreas */
9224 /***
9225 * DESCRIPTION:
9226 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
9228 * PARAMETER(S):
9229 * [I] first : pointer to first ITEM_INFO to compare
9230 * [I] second : pointer to second ITEM_INFO to compare
9231 * [I] lParam : HWND of control
9233 * RETURN:
9234 * if first comes before second : negative
9235 * if first comes after second : positive
9236 * if first and second are equivalent : zero
9238 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9240 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9241 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9242 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9244 /* Forward the call to the client defined callback */
9245 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9248 /***
9249 * DESCRIPTION:
9250 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9252 * PARAMETER(S):
9253 * [I] first : pointer to first ITEM_INFO to compare
9254 * [I] second : pointer to second ITEM_INFO to compare
9255 * [I] lParam : HWND of control
9257 * RETURN:
9258 * if first comes before second : negative
9259 * if first comes after second : positive
9260 * if first and second are equivalent : zero
9262 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9264 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9265 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9266 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9268 /* Forward the call to the client defined callback */
9269 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9272 /***
9273 * DESCRIPTION:
9274 * Sorts the listview items.
9276 * PARAMETER(S):
9277 * [I] infoPtr : valid pointer to the listview structure
9278 * [I] pfnCompare : application-defined value
9279 * [I] lParamSort : pointer to comparison callback
9280 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9282 * RETURN:
9283 * SUCCESS : TRUE
9284 * FAILURE : FALSE
9286 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9287 LPARAM lParamSort, BOOL IsEx)
9289 HDPA hdpaSubItems;
9290 ITEM_INFO *lpItem;
9291 LPVOID selectionMarkItem = NULL;
9292 LPVOID focusedItem = NULL;
9293 int i;
9295 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9297 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9299 if (!pfnCompare) return FALSE;
9300 if (!infoPtr->hdpaItems) return FALSE;
9302 /* if there are 0 or 1 items, there is no need to sort */
9303 if (infoPtr->nItemCount < 2) return TRUE;
9305 /* clear selection */
9306 ranges_clear(infoPtr->selectionRanges);
9308 /* save selection mark and focused item */
9309 if (infoPtr->nSelectionMark >= 0)
9310 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9311 if (infoPtr->nFocusedItem >= 0)
9312 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9314 infoPtr->pfnCompare = pfnCompare;
9315 infoPtr->lParamSort = lParamSort;
9316 if (IsEx)
9317 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9318 else
9319 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9321 /* restore selection ranges */
9322 for (i=0; i < infoPtr->nItemCount; i++)
9324 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9325 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9327 if (lpItem->state & LVIS_SELECTED)
9328 ranges_additem(infoPtr->selectionRanges, i);
9330 /* restore selection mark and focused item */
9331 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9332 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9334 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9336 /* refresh the display */
9337 LISTVIEW_InvalidateList(infoPtr);
9338 return TRUE;
9341 /***
9342 * DESCRIPTION:
9343 * Update theme handle after a theme change.
9345 * PARAMETER(S):
9346 * [I] infoPtr : valid pointer to the listview structure
9348 * RETURN:
9349 * SUCCESS : 0
9350 * FAILURE : something else
9352 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9354 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9355 CloseThemeData(theme);
9356 OpenThemeData(infoPtr->hwndSelf, themeClass);
9357 return 0;
9360 /***
9361 * DESCRIPTION:
9362 * Updates an items or rearranges the listview control.
9364 * PARAMETER(S):
9365 * [I] infoPtr : valid pointer to the listview structure
9366 * [I] nItem : item index
9368 * RETURN:
9369 * SUCCESS : TRUE
9370 * FAILURE : FALSE
9372 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9374 TRACE("(nItem=%d)\n", nItem);
9376 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9378 /* rearrange with default alignment style */
9379 if (is_autoarrange(infoPtr))
9380 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9381 else
9382 LISTVIEW_InvalidateItem(infoPtr, nItem);
9384 return TRUE;
9387 /***
9388 * DESCRIPTION:
9389 * Draw the track line at the place defined in the infoPtr structure.
9390 * The line is drawn with a XOR pen so drawing the line for the second time
9391 * in the same place erases the line.
9393 * PARAMETER(S):
9394 * [I] infoPtr : valid pointer to the listview structure
9396 * RETURN:
9397 * SUCCESS : TRUE
9398 * FAILURE : FALSE
9400 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9402 HDC hdc;
9404 if (infoPtr->xTrackLine == -1)
9405 return FALSE;
9407 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9408 return FALSE;
9409 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9410 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9411 ReleaseDC(infoPtr->hwndSelf, hdc);
9412 return TRUE;
9415 /***
9416 * DESCRIPTION:
9417 * Called when an edit control should be displayed. This function is called after
9418 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9420 * PARAMETER(S):
9421 * [I] hwnd : Handle to the listview
9422 * [I] uMsg : WM_TIMER (ignored)
9423 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9424 * [I] dwTimer : The elapsed time (ignored)
9426 * RETURN:
9427 * None.
9429 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9431 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9432 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9434 KillTimer(hwnd, idEvent);
9435 editItem->fEnabled = FALSE;
9436 /* check if the item is still selected */
9437 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9438 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9441 /***
9442 * DESCRIPTION:
9443 * Creates the listview control - the WM_NCCREATE phase.
9445 * PARAMETER(S):
9446 * [I] hwnd : window handle
9447 * [I] lpcs : the create parameters
9449 * RETURN:
9450 * Success: TRUE
9451 * Failure: FALSE
9453 static LRESULT LISTVIEW_NCCreate(HWND hwnd, WPARAM wParam, const CREATESTRUCTW *lpcs)
9455 LISTVIEW_INFO *infoPtr;
9456 LOGFONTW logFont;
9458 TRACE("(lpcs=%p)\n", lpcs);
9460 /* initialize info pointer */
9461 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9462 if (!infoPtr) return FALSE;
9464 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9466 infoPtr->hwndSelf = hwnd;
9467 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9468 map_style_view(infoPtr);
9469 /* determine the type of structures to use */
9470 infoPtr->hwndNotify = lpcs->hwndParent;
9471 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9473 /* initialize color information */
9474 infoPtr->clrBk = CLR_NONE;
9475 infoPtr->clrText = CLR_DEFAULT;
9476 infoPtr->clrTextBk = CLR_DEFAULT;
9477 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9479 /* set default values */
9480 infoPtr->nFocusedItem = -1;
9481 infoPtr->nSelectionMark = -1;
9482 infoPtr->nHotItem = -1;
9483 infoPtr->redraw = TRUE;
9484 infoPtr->bNoItemMetrics = TRUE;
9485 infoPtr->bDoChangeNotify = TRUE;
9486 infoPtr->autoSpacing = TRUE;
9487 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON);
9488 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON);
9489 infoPtr->nEditLabelItem = -1;
9490 infoPtr->nLButtonDownItem = -1;
9491 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9492 infoPtr->cWheelRemainder = 0;
9493 infoPtr->nMeasureItemHeight = 0;
9494 infoPtr->xTrackLine = -1; /* no track line */
9495 infoPtr->itemEdit.fEnabled = FALSE;
9496 infoPtr->iVersion = COMCTL32_VERSION;
9497 infoPtr->colRectsDirty = FALSE;
9499 /* get default font (icon title) */
9500 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9501 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9502 infoPtr->hFont = infoPtr->hDefaultFont;
9503 LISTVIEW_SaveTextMetrics(infoPtr);
9505 /* allocate memory for the data structure */
9506 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9507 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9508 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9509 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9510 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9511 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9513 return DefWindowProcW(hwnd, WM_NCCREATE, wParam, (LPARAM)lpcs);
9515 fail:
9516 DestroyWindow(infoPtr->hwndHeader);
9517 ranges_destroy(infoPtr->selectionRanges);
9518 DPA_Destroy(infoPtr->hdpaItems);
9519 DPA_Destroy(infoPtr->hdpaItemIds);
9520 DPA_Destroy(infoPtr->hdpaPosX);
9521 DPA_Destroy(infoPtr->hdpaPosY);
9522 DPA_Destroy(infoPtr->hdpaColumns);
9523 Free(infoPtr);
9524 return FALSE;
9527 /***
9528 * DESCRIPTION:
9529 * Creates the listview control - the WM_CREATE phase. Most of the data is
9530 * already set up in LISTVIEW_NCCreate
9532 * PARAMETER(S):
9533 * [I] hwnd : window handle
9534 * [I] lpcs : the create parameters
9536 * RETURN:
9537 * Success: 0
9538 * Failure: -1
9540 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9542 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9544 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9546 infoPtr->dwStyle = lpcs->style;
9547 map_style_view(infoPtr);
9549 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9550 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9551 /* on error defaulting to ANSI notifications */
9552 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9553 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9555 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9557 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9559 else
9560 infoPtr->hwndHeader = 0;
9562 /* init item size to avoid division by 0 */
9563 LISTVIEW_UpdateItemSize (infoPtr);
9564 LISTVIEW_UpdateSize (infoPtr);
9566 if (infoPtr->uView == LV_VIEW_DETAILS)
9568 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9570 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9572 LISTVIEW_UpdateScroll(infoPtr);
9573 /* send WM_MEASUREITEM notification */
9574 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9577 OpenThemeData(hwnd, themeClass);
9579 /* initialize the icon sizes */
9580 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9581 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9582 return 0;
9585 /***
9586 * DESCRIPTION:
9587 * Destroys the listview control.
9589 * PARAMETER(S):
9590 * [I] infoPtr : valid pointer to the listview structure
9592 * RETURN:
9593 * Success: 0
9594 * Failure: -1
9596 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9598 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9599 CloseThemeData(theme);
9601 /* delete all items */
9602 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9604 return 0;
9607 /***
9608 * DESCRIPTION:
9609 * Enables the listview control.
9611 * PARAMETER(S):
9612 * [I] infoPtr : valid pointer to the listview structure
9613 * [I] bEnable : specifies whether to enable or disable the window
9615 * RETURN:
9616 * SUCCESS : TRUE
9617 * FAILURE : FALSE
9619 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9621 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9622 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9623 return TRUE;
9626 /***
9627 * DESCRIPTION:
9628 * Erases the background of the listview control.
9630 * PARAMETER(S):
9631 * [I] infoPtr : valid pointer to the listview structure
9632 * [I] hdc : device context handle
9634 * RETURN:
9635 * SUCCESS : TRUE
9636 * FAILURE : FALSE
9638 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9640 RECT rc;
9642 TRACE("(hdc=%p)\n", hdc);
9644 if (!GetClipBox(hdc, &rc)) return FALSE;
9646 if (infoPtr->clrBk == CLR_NONE)
9648 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9649 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9650 (WPARAM)hdc, PRF_ERASEBKGND);
9651 else
9652 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9655 /* for double buffered controls we need to do this during refresh */
9656 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9658 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9662 /***
9663 * DESCRIPTION:
9664 * Helper function for LISTVIEW_[HV]Scroll *only*.
9665 * Performs vertical/horizontal scrolling by a give amount.
9667 * PARAMETER(S):
9668 * [I] infoPtr : valid pointer to the listview structure
9669 * [I] dx : amount of horizontal scroll
9670 * [I] dy : amount of vertical scroll
9672 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9674 /* now we can scroll the list */
9675 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9676 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9677 /* if we have focus, adjust rect */
9678 OffsetRect(&infoPtr->rcFocus, dx, dy);
9679 UpdateWindow(infoPtr->hwndSelf);
9682 /***
9683 * DESCRIPTION:
9684 * Performs vertical scrolling.
9686 * PARAMETER(S):
9687 * [I] infoPtr : valid pointer to the listview structure
9688 * [I] nScrollCode : scroll code
9689 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9690 * [I] hScrollWnd : scrollbar control window handle
9692 * RETURN:
9693 * Zero
9695 * NOTES:
9696 * SB_LINEUP/SB_LINEDOWN:
9697 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9698 * for LVS_REPORT is 1 line
9699 * for LVS_LIST cannot occur
9702 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9703 INT nScrollDiff)
9705 INT nOldScrollPos, nNewScrollPos;
9706 SCROLLINFO scrollInfo;
9707 BOOL is_an_icon;
9709 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9710 debugscrollcode(nScrollCode), nScrollDiff);
9712 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9714 scrollInfo.cbSize = sizeof(SCROLLINFO);
9715 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9717 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9719 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9721 nOldScrollPos = scrollInfo.nPos;
9722 switch (nScrollCode)
9724 case SB_INTERNAL:
9725 break;
9727 case SB_LINEUP:
9728 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9729 break;
9731 case SB_LINEDOWN:
9732 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9733 break;
9735 case SB_PAGEUP:
9736 nScrollDiff = -scrollInfo.nPage;
9737 break;
9739 case SB_PAGEDOWN:
9740 nScrollDiff = scrollInfo.nPage;
9741 break;
9743 case SB_THUMBPOSITION:
9744 case SB_THUMBTRACK:
9745 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9746 break;
9748 default:
9749 nScrollDiff = 0;
9752 /* quit right away if pos isn't changing */
9753 if (nScrollDiff == 0) return 0;
9755 /* calculate new position, and handle overflows */
9756 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9757 if (nScrollDiff > 0) {
9758 if (nNewScrollPos < nOldScrollPos ||
9759 nNewScrollPos > scrollInfo.nMax)
9760 nNewScrollPos = scrollInfo.nMax;
9761 } else {
9762 if (nNewScrollPos > nOldScrollPos ||
9763 nNewScrollPos < scrollInfo.nMin)
9764 nNewScrollPos = scrollInfo.nMin;
9767 /* set the new position, and reread in case it changed */
9768 scrollInfo.fMask = SIF_POS;
9769 scrollInfo.nPos = nNewScrollPos;
9770 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9772 /* carry on only if it really changed */
9773 if (nNewScrollPos == nOldScrollPos) return 0;
9775 /* now adjust to client coordinates */
9776 nScrollDiff = nOldScrollPos - nNewScrollPos;
9777 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9779 /* and scroll the window */
9780 scroll_list(infoPtr, 0, nScrollDiff);
9782 return 0;
9785 /***
9786 * DESCRIPTION:
9787 * Performs horizontal scrolling.
9789 * PARAMETER(S):
9790 * [I] infoPtr : valid pointer to the listview structure
9791 * [I] nScrollCode : scroll code
9792 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9793 * [I] hScrollWnd : scrollbar control window handle
9795 * RETURN:
9796 * Zero
9798 * NOTES:
9799 * SB_LINELEFT/SB_LINERIGHT:
9800 * for LVS_ICON, LVS_SMALLICON 1 pixel
9801 * for LVS_REPORT is 1 pixel
9802 * for LVS_LIST is 1 column --> which is a 1 because the
9803 * scroll is based on columns not pixels
9806 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9807 INT nScrollDiff)
9809 INT nOldScrollPos, nNewScrollPos;
9810 SCROLLINFO scrollInfo;
9811 BOOL is_an_icon;
9813 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9814 debugscrollcode(nScrollCode), nScrollDiff);
9816 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9818 scrollInfo.cbSize = sizeof(SCROLLINFO);
9819 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9821 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9823 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9825 nOldScrollPos = scrollInfo.nPos;
9827 switch (nScrollCode)
9829 case SB_INTERNAL:
9830 break;
9832 case SB_LINELEFT:
9833 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9834 break;
9836 case SB_LINERIGHT:
9837 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9838 break;
9840 case SB_PAGELEFT:
9841 nScrollDiff = -scrollInfo.nPage;
9842 break;
9844 case SB_PAGERIGHT:
9845 nScrollDiff = scrollInfo.nPage;
9846 break;
9848 case SB_THUMBPOSITION:
9849 case SB_THUMBTRACK:
9850 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9851 break;
9853 default:
9854 nScrollDiff = 0;
9857 /* quit right away if pos isn't changing */
9858 if (nScrollDiff == 0) return 0;
9860 /* calculate new position, and handle overflows */
9861 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9862 if (nScrollDiff > 0) {
9863 if (nNewScrollPos < nOldScrollPos ||
9864 nNewScrollPos > scrollInfo.nMax)
9865 nNewScrollPos = scrollInfo.nMax;
9866 } else {
9867 if (nNewScrollPos > nOldScrollPos ||
9868 nNewScrollPos < scrollInfo.nMin)
9869 nNewScrollPos = scrollInfo.nMin;
9872 /* set the new position, and reread in case it changed */
9873 scrollInfo.fMask = SIF_POS;
9874 scrollInfo.nPos = nNewScrollPos;
9875 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9877 /* carry on only if it really changed */
9878 if (nNewScrollPos == nOldScrollPos) return 0;
9880 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9882 /* now adjust to client coordinates */
9883 nScrollDiff = nOldScrollPos - nNewScrollPos;
9884 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9886 /* and scroll the window */
9887 scroll_list(infoPtr, nScrollDiff, 0);
9889 return 0;
9892 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9894 UINT pulScrollLines = 3;
9896 TRACE("(wheelDelta=%d)\n", wheelDelta);
9898 switch(infoPtr->uView)
9900 case LV_VIEW_ICON:
9901 case LV_VIEW_SMALLICON:
9903 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9904 * should be fixed in the future.
9906 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (wheelDelta > 0) ?
9907 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9908 break;
9910 case LV_VIEW_DETAILS:
9911 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9913 /* if scrolling changes direction, ignore left overs */
9914 if ((wheelDelta < 0 && infoPtr->cWheelRemainder < 0) ||
9915 (wheelDelta > 0 && infoPtr->cWheelRemainder > 0))
9916 infoPtr->cWheelRemainder += wheelDelta;
9917 else
9918 infoPtr->cWheelRemainder = wheelDelta;
9919 if (infoPtr->cWheelRemainder && pulScrollLines)
9921 int cLineScroll;
9922 pulScrollLines = min((UINT)LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9923 cLineScroll = pulScrollLines * (float)infoPtr->cWheelRemainder / WHEEL_DELTA;
9924 infoPtr->cWheelRemainder -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
9925 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, -cLineScroll);
9927 break;
9929 case LV_VIEW_LIST:
9930 LISTVIEW_HScroll(infoPtr, (wheelDelta > 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9931 break;
9933 return 0;
9936 /***
9937 * DESCRIPTION:
9938 * ???
9940 * PARAMETER(S):
9941 * [I] infoPtr : valid pointer to the listview structure
9942 * [I] nVirtualKey : virtual key
9943 * [I] lKeyData : key data
9945 * RETURN:
9946 * Zero
9948 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9950 HWND hwndSelf = infoPtr->hwndSelf;
9951 INT nItem = -1;
9952 NMLVKEYDOWN nmKeyDown;
9954 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9956 /* send LVN_KEYDOWN notification */
9957 nmKeyDown.wVKey = nVirtualKey;
9958 nmKeyDown.flags = 0;
9959 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9960 if (!IsWindow(hwndSelf))
9961 return 0;
9963 switch (nVirtualKey)
9965 case VK_SPACE:
9966 nItem = infoPtr->nFocusedItem;
9967 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9968 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9969 break;
9971 case VK_RETURN:
9972 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9974 if (!notify(infoPtr, NM_RETURN)) return 0;
9975 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
9977 break;
9979 case VK_HOME:
9980 if (infoPtr->nItemCount > 0)
9981 nItem = 0;
9982 break;
9984 case VK_END:
9985 if (infoPtr->nItemCount > 0)
9986 nItem = infoPtr->nItemCount - 1;
9987 break;
9989 case VK_LEFT:
9990 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
9991 break;
9993 case VK_UP:
9994 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
9995 break;
9997 case VK_RIGHT:
9998 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
9999 break;
10001 case VK_DOWN:
10002 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
10003 break;
10005 case VK_PRIOR:
10006 if (infoPtr->uView == LV_VIEW_DETAILS)
10008 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10009 if (infoPtr->nFocusedItem == topidx)
10010 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
10011 else
10012 nItem = topidx;
10014 else
10015 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
10016 * LISTVIEW_GetCountPerRow(infoPtr);
10017 if(nItem < 0) nItem = 0;
10018 break;
10020 case VK_NEXT:
10021 if (infoPtr->uView == LV_VIEW_DETAILS)
10023 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10024 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
10025 if (infoPtr->nFocusedItem == topidx + cnt - 1)
10026 nItem = infoPtr->nFocusedItem + cnt - 1;
10027 else
10028 nItem = topidx + cnt - 1;
10030 else
10031 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
10032 * LISTVIEW_GetCountPerRow(infoPtr);
10033 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
10034 break;
10037 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
10038 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
10040 return 0;
10043 /***
10044 * DESCRIPTION:
10045 * Kills the focus.
10047 * PARAMETER(S):
10048 * [I] infoPtr : valid pointer to the listview structure
10050 * RETURN:
10051 * Zero
10053 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
10055 TRACE("()\n");
10057 /* drop any left over scroll amount */
10058 infoPtr->cWheelRemainder = 0;
10060 /* if we did not have the focus, there's nothing more to do */
10061 if (!infoPtr->bFocus) return 0;
10063 /* send NM_KILLFOCUS notification */
10064 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
10066 /* if we have a focus rectangle, get rid of it */
10067 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
10069 /* if have a marquee selection, stop it */
10070 if (infoPtr->bMarqueeSelect)
10072 /* Remove the marquee rectangle and release our mouse capture */
10073 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
10074 ReleaseCapture();
10076 SetRectEmpty(&infoPtr->marqueeRect);
10078 infoPtr->bMarqueeSelect = FALSE;
10079 infoPtr->bScrolling = FALSE;
10080 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10083 /* set window focus flag */
10084 infoPtr->bFocus = FALSE;
10086 /* invalidate the selected items before resetting focus flag */
10087 LISTVIEW_InvalidateSelectedItems(infoPtr);
10089 return 0;
10092 /***
10093 * DESCRIPTION:
10094 * Processes double click messages (left mouse button).
10096 * PARAMETER(S):
10097 * [I] infoPtr : valid pointer to the listview structure
10098 * [I] wKey : key flag
10099 * [I] x,y : mouse coordinate
10101 * RETURN:
10102 * Zero
10104 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10106 LVHITTESTINFO htInfo;
10108 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10110 /* Cancel the item edition if any */
10111 if (infoPtr->itemEdit.fEnabled)
10113 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
10114 infoPtr->itemEdit.fEnabled = FALSE;
10117 /* send NM_RELEASEDCAPTURE notification */
10118 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10120 htInfo.pt.x = x;
10121 htInfo.pt.y = y;
10123 /* send NM_DBLCLK notification */
10124 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
10125 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
10127 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
10128 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
10130 return 0;
10133 static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
10135 MSG msg;
10136 RECT r;
10138 r.top = r.bottom = pt.y;
10139 r.left = r.right = pt.x;
10141 InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
10143 SetCapture(infoPtr->hwndSelf);
10145 while (1)
10147 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
10149 if (msg.message == WM_MOUSEMOVE)
10151 pt.x = (short)LOWORD(msg.lParam);
10152 pt.y = (short)HIWORD(msg.lParam);
10153 if (PtInRect(&r, pt))
10154 continue;
10155 else
10157 ReleaseCapture();
10158 return 1;
10161 else if (msg.message >= WM_LBUTTONDOWN &&
10162 msg.message <= WM_RBUTTONDBLCLK)
10164 break;
10167 DispatchMessageW(&msg);
10170 if (GetCapture() != infoPtr->hwndSelf)
10171 return 0;
10174 ReleaseCapture();
10175 return 0;
10179 /***
10180 * DESCRIPTION:
10181 * Processes mouse down messages (left mouse button).
10183 * PARAMETERS:
10184 * infoPtr [I ] valid pointer to the listview structure
10185 * wKey [I ] key flag
10186 * x,y [I ] mouse coordinate
10188 * RETURN:
10189 * Zero
10191 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10193 LVHITTESTINFO lvHitTestInfo;
10194 static BOOL bGroupSelect = TRUE;
10195 POINT pt = { x, y };
10196 INT nItem;
10198 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10200 /* send NM_RELEASEDCAPTURE notification */
10201 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10203 /* set left button down flag and record the click position */
10204 infoPtr->bLButtonDown = TRUE;
10205 infoPtr->ptClickPos = pt;
10206 infoPtr->bDragging = FALSE;
10207 infoPtr->bMarqueeSelect = FALSE;
10208 infoPtr->bScrolling = FALSE;
10210 lvHitTestInfo.pt.x = x;
10211 lvHitTestInfo.pt.y = y;
10213 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10214 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10215 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10217 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10219 toggle_checkbox_state(infoPtr, nItem);
10220 return 0;
10223 if (infoPtr->dwStyle & LVS_SINGLESEL)
10225 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10226 infoPtr->nEditLabelItem = nItem;
10227 else
10228 LISTVIEW_SetSelection(infoPtr, nItem);
10230 else
10232 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10234 if (bGroupSelect)
10236 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10237 LISTVIEW_SetItemFocus(infoPtr, nItem);
10238 infoPtr->nSelectionMark = nItem;
10240 else
10242 LVITEMW item;
10244 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10245 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10247 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10248 infoPtr->nSelectionMark = nItem;
10251 else if (wKey & MK_CONTROL)
10253 LVITEMW item;
10255 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10257 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10258 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10259 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10260 infoPtr->nSelectionMark = nItem;
10262 else if (wKey & MK_SHIFT)
10264 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10266 else
10268 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10270 infoPtr->nEditLabelItem = nItem;
10271 infoPtr->nLButtonDownItem = nItem;
10273 LISTVIEW_SetItemFocus(infoPtr, nItem);
10275 else
10276 /* set selection (clears other pre-existing selections) */
10277 LISTVIEW_SetSelection(infoPtr, nItem);
10281 if (!infoPtr->bFocus)
10282 SetFocus(infoPtr->hwndSelf);
10284 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10285 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10287 else
10289 if (!infoPtr->bFocus)
10290 SetFocus(infoPtr->hwndSelf);
10292 /* remove all selections */
10293 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10294 LISTVIEW_DeselectAll(infoPtr);
10295 ReleaseCapture();
10298 return 0;
10301 /***
10302 * DESCRIPTION:
10303 * Processes mouse up messages (left mouse button).
10305 * PARAMETERS:
10306 * infoPtr [I ] valid pointer to the listview structure
10307 * wKey [I ] key flag
10308 * x,y [I ] mouse coordinate
10310 * RETURN:
10311 * Zero
10313 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10315 LVHITTESTINFO lvHitTestInfo;
10317 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10319 if (!infoPtr->bLButtonDown) return 0;
10321 lvHitTestInfo.pt.x = x;
10322 lvHitTestInfo.pt.y = y;
10324 /* send NM_CLICK notification */
10325 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10326 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10328 /* set left button flag */
10329 infoPtr->bLButtonDown = FALSE;
10331 /* set a single selection, reset others */
10332 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10333 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10334 infoPtr->nLButtonDownItem = -1;
10336 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10338 /* Remove the marquee rectangle and release our mouse capture */
10339 if (infoPtr->bMarqueeSelect)
10341 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10342 ReleaseCapture();
10345 SetRectEmpty(&infoPtr->marqueeRect);
10346 SetRectEmpty(&infoPtr->marqueeDrawRect);
10348 infoPtr->bDragging = FALSE;
10349 infoPtr->bMarqueeSelect = FALSE;
10350 infoPtr->bScrolling = FALSE;
10352 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10353 return 0;
10356 /* if we clicked on a selected item, edit the label */
10357 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10359 /* we want to make sure the user doesn't want to do a double click. So we will
10360 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10362 infoPtr->itemEdit.fEnabled = TRUE;
10363 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10364 SetTimer(infoPtr->hwndSelf,
10365 (UINT_PTR)&infoPtr->itemEdit,
10366 GetDoubleClickTime(),
10367 LISTVIEW_DelayedEditItem);
10370 return 0;
10373 /***
10374 * DESCRIPTION:
10375 * Destroys the listview control (called after WM_DESTROY).
10377 * PARAMETER(S):
10378 * [I] infoPtr : valid pointer to the listview structure
10380 * RETURN:
10381 * Zero
10383 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10385 INT i;
10387 TRACE("()\n");
10389 /* destroy data structure */
10390 DPA_Destroy(infoPtr->hdpaItems);
10391 DPA_Destroy(infoPtr->hdpaItemIds);
10392 DPA_Destroy(infoPtr->hdpaPosX);
10393 DPA_Destroy(infoPtr->hdpaPosY);
10394 /* columns */
10395 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10396 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10397 DPA_Destroy(infoPtr->hdpaColumns);
10398 ranges_destroy(infoPtr->selectionRanges);
10400 /* destroy image lists */
10401 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10403 ImageList_Destroy(infoPtr->himlNormal);
10404 ImageList_Destroy(infoPtr->himlSmall);
10405 ImageList_Destroy(infoPtr->himlState);
10408 /* destroy font, bkgnd brush */
10409 infoPtr->hFont = 0;
10410 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10411 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10413 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10415 /* free listview info pointer*/
10416 Free(infoPtr);
10418 return 0;
10421 /***
10422 * DESCRIPTION:
10423 * Handles notifications.
10425 * PARAMETER(S):
10426 * [I] infoPtr : valid pointer to the listview structure
10427 * [I] lpnmhdr : notification information
10429 * RETURN:
10430 * Zero
10432 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10434 NMHEADERW *lpnmh;
10436 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10438 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10440 /* remember: HDN_LAST < HDN_FIRST */
10441 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10442 lpnmh = (NMHEADERW *)lpnmhdr;
10444 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10446 switch (lpnmhdr->code)
10448 case HDN_TRACKW:
10449 case HDN_TRACKA:
10451 COLUMN_INFO *lpColumnInfo;
10452 POINT ptOrigin;
10453 INT x;
10455 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10456 break;
10458 /* remove the old line (if any) */
10459 LISTVIEW_DrawTrackLine(infoPtr);
10461 /* compute & draw the new line */
10462 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10463 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10464 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10465 infoPtr->xTrackLine = x + ptOrigin.x;
10466 LISTVIEW_DrawTrackLine(infoPtr);
10467 return notify_forward_header(infoPtr, lpnmh);
10470 case HDN_ENDTRACKA:
10471 case HDN_ENDTRACKW:
10472 /* remove the track line (if any) */
10473 LISTVIEW_DrawTrackLine(infoPtr);
10474 infoPtr->xTrackLine = -1;
10475 return notify_forward_header(infoPtr, lpnmh);
10477 case HDN_BEGINDRAG:
10478 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10479 return notify_forward_header(infoPtr, lpnmh);
10481 case HDN_ENDDRAG:
10482 infoPtr->colRectsDirty = TRUE;
10483 LISTVIEW_InvalidateList(infoPtr);
10484 return notify_forward_header(infoPtr, lpnmh);
10486 case HDN_ITEMCHANGEDW:
10487 case HDN_ITEMCHANGEDA:
10489 COLUMN_INFO *lpColumnInfo;
10490 HDITEMW hdi;
10491 INT dx, cxy;
10493 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10495 hdi.mask = HDI_WIDTH;
10496 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10497 cxy = hdi.cxy;
10499 else
10500 cxy = lpnmh->pitem->cxy;
10502 /* determine how much we change since the last know position */
10503 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10504 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10505 if (dx != 0)
10507 lpColumnInfo->rcHeader.right += dx;
10509 hdi.mask = HDI_ORDER;
10510 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10512 /* not the rightmost one */
10513 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10515 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10516 hdi.iOrder + 1, 0);
10517 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10519 else
10521 /* only needs to update the scrolls */
10522 infoPtr->nItemWidth += dx;
10523 LISTVIEW_UpdateScroll(infoPtr);
10525 LISTVIEW_UpdateItemSize(infoPtr);
10526 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10528 POINT ptOrigin;
10529 RECT rcCol = lpColumnInfo->rcHeader;
10531 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10532 OffsetRect(&rcCol, ptOrigin.x, 0);
10534 rcCol.top = infoPtr->rcList.top;
10535 rcCol.bottom = infoPtr->rcList.bottom;
10537 /* resizing left-aligned columns leaves most of the left side untouched */
10538 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10540 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10541 if (dx > 0)
10542 nMaxDirty += dx;
10543 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10546 /* when shrinking the last column clear the now unused field */
10547 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10549 RECT right;
10551 rcCol.right -= dx;
10553 /* deal with right from rightmost column area */
10554 right.left = rcCol.right;
10555 right.top = rcCol.top;
10556 right.bottom = rcCol.bottom;
10557 right.right = infoPtr->rcList.right;
10559 LISTVIEW_InvalidateRect(infoPtr, &right);
10562 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10565 break;
10568 case HDN_ITEMCLICKW:
10569 case HDN_ITEMCLICKA:
10571 /* Handle sorting by Header Column */
10572 NMLISTVIEW nmlv;
10574 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10575 nmlv.iItem = -1;
10576 nmlv.iSubItem = lpnmh->iItem;
10577 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10578 return notify_forward_header(infoPtr, lpnmh);
10581 case HDN_DIVIDERDBLCLICKW:
10582 case HDN_DIVIDERDBLCLICKA:
10583 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10584 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10585 split needed for that */
10586 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10587 return notify_forward_header(infoPtr, lpnmh);
10589 return 0;
10592 /***
10593 * DESCRIPTION:
10594 * Paint non-client area of control.
10596 * PARAMETER(S):
10597 * [I] infoPtr : valid pointer to the listview structureof the sender
10598 * [I] region : update region
10600 * RETURN:
10601 * TRUE - frame was painted
10602 * FALSE - call default window proc
10604 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10606 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10607 HDC dc;
10608 RECT r;
10609 HRGN cliprgn;
10610 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10611 cyEdge = GetSystemMetrics (SM_CYEDGE);
10613 if (!theme)
10614 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10616 GetWindowRect(infoPtr->hwndSelf, &r);
10618 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10619 r.right - cxEdge, r.bottom - cyEdge);
10620 if (region != (HRGN)1)
10621 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10622 OffsetRect(&r, -r.left, -r.top);
10624 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10625 OffsetRect(&r, -r.left, -r.top);
10627 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10628 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10629 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10630 ReleaseDC(infoPtr->hwndSelf, dc);
10632 /* Call default proc to get the scrollbars etc. painted */
10633 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10635 return FALSE;
10638 /***
10639 * DESCRIPTION:
10640 * Determines the type of structure to use.
10642 * PARAMETER(S):
10643 * [I] infoPtr : valid pointer to the listview structureof the sender
10644 * [I] hwndFrom : listview window handle
10645 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10647 * RETURN:
10648 * Zero
10650 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10652 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10654 if (nCommand == NF_REQUERY)
10655 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10657 return infoPtr->notifyFormat;
10660 /***
10661 * DESCRIPTION:
10662 * Paints/Repaints the listview control. Internal use.
10664 * PARAMETER(S):
10665 * [I] infoPtr : valid pointer to the listview structure
10666 * [I] hdc : device context handle
10668 * RETURN:
10669 * Zero
10671 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10673 TRACE("(hdc=%p)\n", hdc);
10675 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10677 infoPtr->bNoItemMetrics = FALSE;
10678 LISTVIEW_UpdateItemSize(infoPtr);
10679 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10680 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10681 LISTVIEW_UpdateScroll(infoPtr);
10684 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10686 if (hdc)
10687 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10688 else
10690 PAINTSTRUCT ps;
10692 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10693 if (!hdc) return 1;
10694 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10695 EndPaint(infoPtr->hwndSelf, &ps);
10698 return 0;
10701 /***
10702 * DESCRIPTION:
10703 * Paints/Repaints the listview control, WM_PAINT handler.
10705 * PARAMETER(S):
10706 * [I] infoPtr : valid pointer to the listview structure
10707 * [I] hdc : device context handle
10709 * RETURN:
10710 * Zero
10712 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10714 TRACE("(hdc=%p)\n", hdc);
10716 if (!is_redrawing(infoPtr))
10717 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10719 return LISTVIEW_Paint(infoPtr, hdc);
10722 /***
10723 * DESCRIPTION:
10724 * Paints/Repaints the listview control.
10726 * PARAMETER(S):
10727 * [I] infoPtr : valid pointer to the listview structure
10728 * [I] hdc : device context handle
10729 * [I] options : drawing options
10731 * RETURN:
10732 * Zero
10734 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10736 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10737 return 0;
10739 if (options & ~(PRF_ERASEBKGND|PRF_CLIENT))
10740 FIXME("(hdc=%p options=0x%08x) partial stub\n", hdc, options);
10742 if (options & PRF_ERASEBKGND)
10743 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10745 if (options & PRF_CLIENT)
10746 LISTVIEW_Paint(infoPtr, hdc);
10748 return 0;
10752 /***
10753 * DESCRIPTION:
10754 * Processes double click messages (right mouse button).
10756 * PARAMETER(S):
10757 * [I] infoPtr : valid pointer to the listview structure
10758 * [I] wKey : key flag
10759 * [I] x,y : mouse coordinate
10761 * RETURN:
10762 * Zero
10764 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10766 LVHITTESTINFO lvHitTestInfo;
10768 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10770 /* send NM_RELEASEDCAPTURE notification */
10771 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10773 /* send NM_RDBLCLK notification */
10774 lvHitTestInfo.pt.x = x;
10775 lvHitTestInfo.pt.y = y;
10776 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10777 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10779 return 0;
10782 /***
10783 * DESCRIPTION:
10784 * Processes WM_RBUTTONDOWN message and corresponding drag operation.
10786 * PARAMETER(S):
10787 * [I] infoPtr : valid pointer to the listview structure
10788 * [I] wKey : key flag
10789 * [I] x, y : mouse coordinate
10791 * RETURN:
10792 * Zero
10794 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10796 LVHITTESTINFO ht;
10797 INT item;
10799 TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
10801 /* send NM_RELEASEDCAPTURE notification */
10802 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10804 /* determine the index of the selected item */
10805 ht.pt.x = x;
10806 ht.pt.y = y;
10807 item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
10809 /* make sure the listview control window has the focus */
10810 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10812 if ((item >= 0) && (item < infoPtr->nItemCount))
10814 LISTVIEW_SetItemFocus(infoPtr, item);
10815 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10816 !LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
10817 LISTVIEW_SetSelection(infoPtr, item);
10819 else
10820 LISTVIEW_DeselectAll(infoPtr);
10822 if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
10824 if (ht.iItem != -1)
10826 NMLISTVIEW nmlv;
10828 memset(&nmlv, 0, sizeof(nmlv));
10829 nmlv.iItem = ht.iItem;
10830 nmlv.ptAction = ht.pt;
10832 notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
10835 else
10837 SetFocus(infoPtr->hwndSelf);
10839 ht.pt.x = x;
10840 ht.pt.y = y;
10841 LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
10843 if (notify_click(infoPtr, NM_RCLICK, &ht))
10845 /* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
10846 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10847 (WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
10851 return 0;
10854 /***
10855 * DESCRIPTION:
10856 * Sets the cursor.
10858 * PARAMETER(S):
10859 * [I] infoPtr : valid pointer to the listview structure
10860 * [I] hwnd : window handle of window containing the cursor
10861 * [I] nHittest : hit-test code
10862 * [I] wMouseMsg : ideintifier of the mouse message
10864 * RETURN:
10865 * TRUE if cursor is set
10866 * FALSE otherwise
10868 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10870 LVHITTESTINFO lvHitTestInfo;
10872 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10874 if (!infoPtr->hHotCursor) goto forward;
10876 GetCursorPos(&lvHitTestInfo.pt);
10877 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10879 SetCursor(infoPtr->hHotCursor);
10881 return TRUE;
10883 forward:
10885 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10888 /***
10889 * DESCRIPTION:
10890 * Sets the focus.
10892 * PARAMETER(S):
10893 * [I] infoPtr : valid pointer to the listview structure
10894 * [I] hwndLoseFocus : handle of previously focused window
10896 * RETURN:
10897 * Zero
10899 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10901 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10903 /* if we have the focus already, there's nothing to do */
10904 if (infoPtr->bFocus) return 0;
10906 /* send NM_SETFOCUS notification */
10907 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10909 /* set window focus flag */
10910 infoPtr->bFocus = TRUE;
10912 /* put the focus rect back on */
10913 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10915 /* redraw all visible selected items */
10916 LISTVIEW_InvalidateSelectedItems(infoPtr);
10918 return 0;
10921 /***
10922 * DESCRIPTION:
10923 * Sets the font.
10925 * PARAMETER(S):
10926 * [I] infoPtr : valid pointer to the listview structure
10927 * [I] fRedraw : font handle
10928 * [I] fRedraw : redraw flag
10930 * RETURN:
10931 * Zero
10933 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10935 HFONT oldFont = infoPtr->hFont;
10936 INT oldHeight = infoPtr->nItemHeight;
10938 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10940 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10941 if (infoPtr->hFont == oldFont) return 0;
10943 LISTVIEW_SaveTextMetrics(infoPtr);
10945 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10947 if (infoPtr->uView == LV_VIEW_DETAILS)
10949 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10950 LISTVIEW_UpdateSize(infoPtr);
10951 LISTVIEW_UpdateScroll(infoPtr);
10953 else if (infoPtr->nItemHeight != oldHeight)
10954 LISTVIEW_UpdateScroll(infoPtr);
10956 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10958 return 0;
10961 /***
10962 * DESCRIPTION:
10963 * Message handling for WM_SETREDRAW.
10964 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10966 * PARAMETER(S):
10967 * [I] infoPtr : valid pointer to the listview structure
10968 * [I] redraw: state of redraw flag
10970 * RETURN:
10971 * Zero.
10973 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL redraw)
10975 TRACE("old=%d, new=%d\n", infoPtr->redraw, redraw);
10977 if (infoPtr->redraw == !!redraw)
10978 return 0;
10980 if (!(infoPtr->redraw = !!redraw))
10981 return 0;
10983 if (is_autoarrange(infoPtr))
10984 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10985 LISTVIEW_UpdateScroll(infoPtr);
10987 /* despite what the WM_SETREDRAW docs says, apps expect us
10988 * to invalidate the listview here... stupid! */
10989 LISTVIEW_InvalidateList(infoPtr);
10991 return 0;
10994 /***
10995 * DESCRIPTION:
10996 * Resizes the listview control. This function processes WM_SIZE
10997 * messages. At this time, the width and height are not used.
10999 * PARAMETER(S):
11000 * [I] infoPtr : valid pointer to the listview structure
11001 * [I] Width : new width
11002 * [I] Height : new height
11004 * RETURN:
11005 * Zero
11007 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
11009 RECT rcOld = infoPtr->rcList;
11011 TRACE("(width=%d, height=%d)\n", Width, Height);
11013 LISTVIEW_UpdateSize(infoPtr);
11014 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
11016 /* do not bother with display related stuff if we're not redrawing */
11017 if (!is_redrawing(infoPtr)) return 0;
11019 if (is_autoarrange(infoPtr))
11020 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11022 LISTVIEW_UpdateScroll(infoPtr);
11024 /* refresh all only for lists whose height changed significantly */
11025 if ((infoPtr->uView == LV_VIEW_LIST) &&
11026 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
11027 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
11028 LISTVIEW_InvalidateList(infoPtr);
11030 return 0;
11033 /***
11034 * DESCRIPTION:
11035 * Sets the size information.
11037 * PARAMETER(S):
11038 * [I] infoPtr : valid pointer to the listview structure
11040 * RETURN:
11041 * None
11043 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
11045 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
11047 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
11049 if (infoPtr->uView == LV_VIEW_LIST)
11051 /* Apparently the "LIST" style is supposed to have the same
11052 * number of items in a column even if there is no scroll bar.
11053 * Since if a scroll bar already exists then the bottom is already
11054 * reduced, only reduce if the scroll bar does not currently exist.
11055 * The "2" is there to mimic the native control. I think it may be
11056 * related to either padding or edges. (GLA 7/2002)
11058 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
11059 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
11060 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
11063 /* if control created invisible header isn't created */
11064 if (infoPtr->hwndHeader)
11066 HDLAYOUT hl;
11067 WINDOWPOS wp;
11069 hl.prc = &infoPtr->rcList;
11070 hl.pwpos = &wp;
11071 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11072 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
11074 if (LISTVIEW_IsHeaderEnabled(infoPtr))
11075 wp.flags |= SWP_SHOWWINDOW;
11076 else
11078 wp.flags |= SWP_HIDEWINDOW;
11079 wp.cy = 0;
11082 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
11083 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
11085 infoPtr->rcList.top = max(wp.cy, 0);
11087 /* extra padding for grid */
11088 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
11089 infoPtr->rcList.top += 2;
11091 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
11094 /***
11095 * DESCRIPTION:
11096 * Processes WM_STYLECHANGED messages.
11098 * PARAMETER(S):
11099 * [I] infoPtr : valid pointer to the listview structure
11100 * [I] wStyleType : window style type (normal or extended)
11101 * [I] lpss : window style information
11103 * RETURN:
11104 * Zero
11106 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
11107 const STYLESTRUCT *lpss)
11109 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
11110 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
11111 UINT style;
11113 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11114 wStyleType, lpss->styleOld, lpss->styleNew);
11116 if (wStyleType != GWL_STYLE) return 0;
11118 infoPtr->dwStyle = lpss->styleNew;
11120 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
11121 ((lpss->styleNew & WS_HSCROLL) == 0))
11122 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
11124 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
11125 ((lpss->styleNew & WS_VSCROLL) == 0))
11126 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
11128 if (uNewView != uOldView)
11130 HIMAGELIST himl;
11132 /* LVM_SETVIEW doesn't change window style bits within LVS_TYPEMASK,
11133 changing style updates current view only when view bits change. */
11134 map_style_view(infoPtr);
11135 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
11136 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
11138 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
11139 SetRectEmpty(&infoPtr->rcFocus);
11141 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
11142 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
11144 if (uNewView == LVS_REPORT)
11146 HDLAYOUT hl;
11147 WINDOWPOS wp;
11149 LISTVIEW_CreateHeader( infoPtr );
11151 hl.prc = &infoPtr->rcList;
11152 hl.pwpos = &wp;
11153 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11154 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
11155 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
11156 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
11159 LISTVIEW_UpdateItemSize(infoPtr);
11162 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
11164 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
11166 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
11168 /* Turn off the header control */
11169 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
11170 TRACE("Hide header control, was 0x%08x\n", style);
11171 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11172 } else {
11173 /* Turn on the header control */
11174 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11176 TRACE("Show header control, was 0x%08x\n", style);
11177 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11183 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11184 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11185 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11187 /* update the size of the client area */
11188 LISTVIEW_UpdateSize(infoPtr);
11190 /* add scrollbars if needed */
11191 LISTVIEW_UpdateScroll(infoPtr);
11193 /* invalidate client area + erase background */
11194 LISTVIEW_InvalidateList(infoPtr);
11196 return 0;
11199 /***
11200 * DESCRIPTION:
11201 * Processes WM_STYLECHANGING messages.
11203 * PARAMETER(S):
11204 * [I] wStyleType : window style type (normal or extended)
11205 * [I0] lpss : window style information
11207 * RETURN:
11208 * Zero
11210 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11211 STYLESTRUCT *lpss)
11213 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11214 wStyleType, lpss->styleOld, lpss->styleNew);
11216 /* don't forward LVS_OWNERDATA only if not already set to */
11217 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11219 if (lpss->styleOld & LVS_OWNERDATA)
11220 lpss->styleNew |= LVS_OWNERDATA;
11221 else
11222 lpss->styleNew &= ~LVS_OWNERDATA;
11225 return 0;
11228 /***
11229 * DESCRIPTION:
11230 * Processes WM_SHOWWINDOW messages.
11232 * PARAMETER(S):
11233 * [I] infoPtr : valid pointer to the listview structure
11234 * [I] bShown : window is being shown (FALSE when hidden)
11235 * [I] iStatus : window show status
11237 * RETURN:
11238 * Zero
11240 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11242 /* header delayed creation */
11243 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11245 LISTVIEW_CreateHeader(infoPtr);
11247 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11248 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11251 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11254 /***
11255 * DESCRIPTION:
11256 * Processes CCM_GETVERSION messages.
11258 * PARAMETER(S):
11259 * [I] infoPtr : valid pointer to the listview structure
11261 * RETURN:
11262 * Current version
11264 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11266 return infoPtr->iVersion;
11269 /***
11270 * DESCRIPTION:
11271 * Processes CCM_SETVERSION messages.
11273 * PARAMETER(S):
11274 * [I] infoPtr : valid pointer to the listview structure
11275 * [I] iVersion : version to be set
11277 * RETURN:
11278 * -1 when requested version is greater than DLL version;
11279 * previous version otherwise
11281 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11283 INT iOldVersion = infoPtr->iVersion;
11285 if (iVersion > COMCTL32_VERSION)
11286 return -1;
11288 infoPtr->iVersion = iVersion;
11290 TRACE("new version %d\n", iVersion);
11292 return iOldVersion;
11295 /***
11296 * DESCRIPTION:
11297 * Window procedure of the listview control.
11300 static LRESULT WINAPI
11301 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11303 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11305 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11307 if (!infoPtr && (uMsg != WM_NCCREATE))
11308 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11310 switch (uMsg)
11312 case LVM_APPROXIMATEVIEWRECT:
11313 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11314 LOWORD(lParam), HIWORD(lParam));
11315 case LVM_ARRANGE:
11316 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11318 case LVM_CANCELEDITLABEL:
11319 return LISTVIEW_CancelEditLabel(infoPtr);
11321 case LVM_CREATEDRAGIMAGE:
11322 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11324 case LVM_DELETEALLITEMS:
11325 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11327 case LVM_DELETECOLUMN:
11328 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11330 case LVM_DELETEITEM:
11331 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11333 case LVM_EDITLABELA:
11334 case LVM_EDITLABELW:
11335 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11336 uMsg == LVM_EDITLABELW);
11337 /* case LVM_ENABLEGROUPVIEW: */
11339 case LVM_ENSUREVISIBLE:
11340 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11342 case LVM_FINDITEMW:
11343 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11345 case LVM_FINDITEMA:
11346 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11348 case LVM_GETBKCOLOR:
11349 return infoPtr->clrBk;
11351 /* case LVM_GETBKIMAGE: */
11353 case LVM_GETCALLBACKMASK:
11354 return infoPtr->uCallbackMask;
11356 case LVM_GETCOLUMNA:
11357 case LVM_GETCOLUMNW:
11358 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11359 uMsg == LVM_GETCOLUMNW);
11361 case LVM_GETCOLUMNORDERARRAY:
11362 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11364 case LVM_GETCOLUMNWIDTH:
11365 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11367 case LVM_GETCOUNTPERPAGE:
11368 return LISTVIEW_GetCountPerPage(infoPtr);
11370 case LVM_GETEDITCONTROL:
11371 return (LRESULT)infoPtr->hwndEdit;
11373 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11374 return infoPtr->dwLvExStyle;
11376 /* case LVM_GETGROUPINFO: */
11378 /* case LVM_GETGROUPMETRICS: */
11380 case LVM_GETHEADER:
11381 return (LRESULT)infoPtr->hwndHeader;
11383 case LVM_GETHOTCURSOR:
11384 return (LRESULT)infoPtr->hHotCursor;
11386 case LVM_GETHOTITEM:
11387 return infoPtr->nHotItem;
11389 case LVM_GETHOVERTIME:
11390 return infoPtr->dwHoverTime;
11392 case LVM_GETIMAGELIST:
11393 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11395 /* case LVM_GETINSERTMARK: */
11397 /* case LVM_GETINSERTMARKCOLOR: */
11399 /* case LVM_GETINSERTMARKRECT: */
11401 case LVM_GETISEARCHSTRINGA:
11402 case LVM_GETISEARCHSTRINGW:
11403 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11404 return FALSE;
11406 case LVM_GETITEMA:
11407 case LVM_GETITEMW:
11408 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11410 case LVM_GETITEMCOUNT:
11411 return infoPtr->nItemCount;
11413 case LVM_GETITEMPOSITION:
11414 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11416 case LVM_GETITEMRECT:
11417 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11419 case LVM_GETITEMSPACING:
11420 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11422 case LVM_GETITEMSTATE:
11423 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11425 case LVM_GETITEMTEXTA:
11426 case LVM_GETITEMTEXTW:
11427 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11428 uMsg == LVM_GETITEMTEXTW);
11430 case LVM_GETNEXTITEM:
11431 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11433 case LVM_GETNUMBEROFWORKAREAS:
11434 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11435 return 1;
11437 case LVM_GETORIGIN:
11438 if (!lParam) return FALSE;
11439 if (infoPtr->uView == LV_VIEW_DETAILS ||
11440 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11441 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11442 return TRUE;
11444 /* case LVM_GETOUTLINECOLOR: */
11446 /* case LVM_GETSELECTEDCOLUMN: */
11448 case LVM_GETSELECTEDCOUNT:
11449 return LISTVIEW_GetSelectedCount(infoPtr);
11451 case LVM_GETSELECTIONMARK:
11452 return infoPtr->nSelectionMark;
11454 case LVM_GETSTRINGWIDTHA:
11455 case LVM_GETSTRINGWIDTHW:
11456 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11457 uMsg == LVM_GETSTRINGWIDTHW);
11459 case LVM_GETSUBITEMRECT:
11460 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11462 case LVM_GETTEXTBKCOLOR:
11463 return infoPtr->clrTextBk;
11465 case LVM_GETTEXTCOLOR:
11466 return infoPtr->clrText;
11468 /* case LVM_GETTILEINFO: */
11470 /* case LVM_GETTILEVIEWINFO: */
11472 case LVM_GETTOOLTIPS:
11473 if( !infoPtr->hwndToolTip )
11474 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11475 return (LRESULT)infoPtr->hwndToolTip;
11477 case LVM_GETTOPINDEX:
11478 return LISTVIEW_GetTopIndex(infoPtr);
11480 case LVM_GETUNICODEFORMAT:
11481 return (infoPtr->notifyFormat == NFR_UNICODE);
11483 case LVM_GETVIEW:
11484 return infoPtr->uView;
11486 case LVM_GETVIEWRECT:
11487 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11489 case LVM_GETWORKAREAS:
11490 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11491 return FALSE;
11493 /* case LVM_HASGROUP: */
11495 case LVM_HITTEST:
11496 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11498 case LVM_INSERTCOLUMNA:
11499 case LVM_INSERTCOLUMNW:
11500 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11501 uMsg == LVM_INSERTCOLUMNW);
11503 /* case LVM_INSERTGROUP: */
11505 /* case LVM_INSERTGROUPSORTED: */
11507 case LVM_INSERTITEMA:
11508 case LVM_INSERTITEMW:
11509 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11511 /* case LVM_INSERTMARKHITTEST: */
11513 /* case LVM_ISGROUPVIEWENABLED: */
11515 case LVM_ISITEMVISIBLE:
11516 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11518 case LVM_MAPIDTOINDEX:
11519 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11521 case LVM_MAPINDEXTOID:
11522 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11524 /* case LVM_MOVEGROUP: */
11526 /* case LVM_MOVEITEMTOGROUP: */
11528 case LVM_REDRAWITEMS:
11529 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11531 /* case LVM_REMOVEALLGROUPS: */
11533 /* case LVM_REMOVEGROUP: */
11535 case LVM_SCROLL:
11536 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11538 case LVM_SETBKCOLOR:
11539 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11541 /* case LVM_SETBKIMAGE: */
11543 case LVM_SETCALLBACKMASK:
11544 infoPtr->uCallbackMask = (UINT)wParam;
11545 return TRUE;
11547 case LVM_SETCOLUMNA:
11548 case LVM_SETCOLUMNW:
11549 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11550 uMsg == LVM_SETCOLUMNW);
11552 case LVM_SETCOLUMNORDERARRAY:
11553 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11555 case LVM_SETCOLUMNWIDTH:
11556 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11558 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11559 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11561 /* case LVM_SETGROUPINFO: */
11563 /* case LVM_SETGROUPMETRICS: */
11565 case LVM_SETHOTCURSOR:
11566 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11568 case LVM_SETHOTITEM:
11569 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11571 case LVM_SETHOVERTIME:
11572 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11574 case LVM_SETICONSPACING:
11575 if(lParam == -1)
11576 return LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
11577 return LISTVIEW_SetIconSpacing(infoPtr, LOWORD(lParam), HIWORD(lParam));
11579 case LVM_SETIMAGELIST:
11580 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11582 /* case LVM_SETINFOTIP: */
11584 /* case LVM_SETINSERTMARK: */
11586 /* case LVM_SETINSERTMARKCOLOR: */
11588 case LVM_SETITEMA:
11589 case LVM_SETITEMW:
11591 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11592 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11595 case LVM_SETITEMCOUNT:
11596 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11598 case LVM_SETITEMPOSITION:
11600 POINT pt;
11601 pt.x = (short)LOWORD(lParam);
11602 pt.y = (short)HIWORD(lParam);
11603 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11606 case LVM_SETITEMPOSITION32:
11607 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11609 case LVM_SETITEMSTATE:
11610 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11612 case LVM_SETITEMTEXTA:
11613 case LVM_SETITEMTEXTW:
11614 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11615 uMsg == LVM_SETITEMTEXTW);
11617 /* case LVM_SETOUTLINECOLOR: */
11619 /* case LVM_SETSELECTEDCOLUMN: */
11621 case LVM_SETSELECTIONMARK:
11622 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11624 case LVM_SETTEXTBKCOLOR:
11625 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11627 case LVM_SETTEXTCOLOR:
11628 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11630 /* case LVM_SETTILEINFO: */
11632 /* case LVM_SETTILEVIEWINFO: */
11634 /* case LVM_SETTILEWIDTH: */
11636 case LVM_SETTOOLTIPS:
11637 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11639 case LVM_SETUNICODEFORMAT:
11640 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11642 case LVM_SETVIEW:
11643 return LISTVIEW_SetView(infoPtr, wParam);
11645 /* case LVM_SETWORKAREAS: */
11647 /* case LVM_SORTGROUPS: */
11649 case LVM_SORTITEMS:
11650 case LVM_SORTITEMSEX:
11651 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11652 uMsg == LVM_SORTITEMSEX);
11653 case LVM_SUBITEMHITTEST:
11654 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11656 case LVM_UPDATE:
11657 return LISTVIEW_Update(infoPtr, (INT)wParam);
11659 case CCM_GETVERSION:
11660 return LISTVIEW_GetVersion(infoPtr);
11662 case CCM_SETVERSION:
11663 return LISTVIEW_SetVersion(infoPtr, wParam);
11665 case WM_CHAR:
11666 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11668 case WM_COMMAND:
11669 return LISTVIEW_Command(infoPtr, wParam, lParam);
11671 case WM_NCCREATE:
11672 return LISTVIEW_NCCreate(hwnd, wParam, (LPCREATESTRUCTW)lParam);
11674 case WM_CREATE:
11675 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11677 case WM_DESTROY:
11678 return LISTVIEW_Destroy(infoPtr);
11680 case WM_ENABLE:
11681 return LISTVIEW_Enable(infoPtr);
11683 case WM_ERASEBKGND:
11684 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11686 case WM_GETDLGCODE:
11687 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11689 case WM_GETFONT:
11690 return (LRESULT)infoPtr->hFont;
11692 case WM_HSCROLL:
11693 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11695 case WM_KEYDOWN:
11696 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11698 case WM_KILLFOCUS:
11699 return LISTVIEW_KillFocus(infoPtr);
11701 case WM_LBUTTONDBLCLK:
11702 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11704 case WM_LBUTTONDOWN:
11705 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11707 case WM_LBUTTONUP:
11708 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11710 case WM_MOUSEMOVE:
11711 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11713 case WM_MOUSEHOVER:
11714 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11716 case WM_NCDESTROY:
11717 return LISTVIEW_NCDestroy(infoPtr);
11719 case WM_NCPAINT:
11720 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11722 case WM_NOTIFY:
11723 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11725 case WM_NOTIFYFORMAT:
11726 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11728 case WM_PRINTCLIENT:
11729 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11731 case WM_PAINT:
11732 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11734 case WM_RBUTTONDBLCLK:
11735 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11737 case WM_RBUTTONDOWN:
11738 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11740 case WM_SETCURSOR:
11741 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11743 case WM_SETFOCUS:
11744 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11746 case WM_SETFONT:
11747 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11749 case WM_SETREDRAW:
11750 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11752 case WM_SHOWWINDOW:
11753 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11755 case WM_STYLECHANGED:
11756 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11758 case WM_STYLECHANGING:
11759 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11761 case WM_SYSCOLORCHANGE:
11762 COMCTL32_RefreshSysColors();
11763 return 0;
11765 /* case WM_TIMER: */
11766 case WM_THEMECHANGED:
11767 return LISTVIEW_ThemeChanged(infoPtr);
11769 case WM_VSCROLL:
11770 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11772 case WM_MOUSEWHEEL:
11773 if (wParam & (MK_SHIFT | MK_CONTROL))
11774 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11775 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11777 case WM_WINDOWPOSCHANGED:
11778 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11780 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11781 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11783 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11785 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11787 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11789 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11791 /* case WM_WININICHANGE: */
11793 default:
11794 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11795 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11797 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11802 /***
11803 * DESCRIPTION:
11804 * Registers the window class.
11806 * PARAMETER(S):
11807 * None
11809 * RETURN:
11810 * None
11812 void LISTVIEW_Register(void)
11814 WNDCLASSW wndClass;
11816 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11817 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11818 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11819 wndClass.cbClsExtra = 0;
11820 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11821 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11822 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11823 wndClass.lpszClassName = WC_LISTVIEWW;
11824 RegisterClassW(&wndClass);
11827 /***
11828 * DESCRIPTION:
11829 * Unregisters the window class.
11831 * PARAMETER(S):
11832 * None
11834 * RETURN:
11835 * None
11837 void LISTVIEW_Unregister(void)
11839 UnregisterClassW(WC_LISTVIEWW, NULL);
11842 /***
11843 * DESCRIPTION:
11844 * Handle any WM_COMMAND messages
11846 * PARAMETER(S):
11847 * [I] infoPtr : valid pointer to the listview structure
11848 * [I] wParam : the first message parameter
11849 * [I] lParam : the second message parameter
11851 * RETURN:
11852 * Zero.
11854 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11857 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11859 if (!infoPtr->hwndEdit) return 0;
11861 switch (HIWORD(wParam))
11863 case EN_UPDATE:
11866 * Adjust the edit window size
11868 WCHAR buffer[1024];
11869 HDC hdc = GetDC(infoPtr->hwndEdit);
11870 HFONT hFont, hOldFont = 0;
11871 RECT rect;
11872 SIZE sz;
11874 if (!infoPtr->hwndEdit || !hdc) return 0;
11875 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11876 GetWindowRect(infoPtr->hwndEdit, &rect);
11878 /* Select font to get the right dimension of the string */
11879 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11880 if (hFont)
11882 hOldFont = SelectObject(hdc, hFont);
11885 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11887 TEXTMETRICW textMetric;
11889 /* Add Extra spacing for the next character */
11890 GetTextMetricsW(hdc, &textMetric);
11891 sz.cx += (textMetric.tmMaxCharWidth * 2);
11893 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11894 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11896 if (hFont)
11897 SelectObject(hdc, hOldFont);
11899 ReleaseDC(infoPtr->hwndEdit, hdc);
11901 break;
11903 case EN_KILLFOCUS:
11905 LISTVIEW_CancelEditLabel(infoPtr);
11906 break;
11909 default:
11910 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
11913 return 0;