comctl32/listview: When adding a scrollbar, update the other one.
[wine.git] / dlls / comctl32 / listview.c
blobc681451942143eea1db3a3f83d0f0ed954b4f91b
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
9 * Copyright 2009-2015 Nikolay Sivov
10 * Copyright 2009 Owen Rudge for CodeWeavers
11 * Copyright 2012-2013 Daniel Jelinski
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 * NOTES
29 * This code was audited for completeness against the documented features
30 * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
32 * Unless otherwise noted, we believe this code to be complete, as per
33 * the specification mentioned above.
34 * If you discover missing features, or bugs, please note them below.
36 * TODO:
38 * Default Message Processing
39 * -- WM_CREATE: create the icon and small icon image lists at this point only if
40 * the LVS_SHAREIMAGELISTS style is not specified.
41 * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42 * or small icon and the LVS_AUTOARRANGE style is specified.
43 * -- WM_TIMER
44 * -- WM_WININICHANGE
46 * Features
47 * -- Hot item handling, mouse hovering
48 * -- Workareas support
49 * -- Tilemode support
50 * -- Groups support
52 * Bugs
53 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54 * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55 * -- LVA_SNAPTOGRID not implemented
56 * -- LISTVIEW_ApproximateViewRect partially implemented
57 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
59 * Speedups
60 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61 * linear in the number of items in the list, and this is
62 * unacceptable for large lists.
63 * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64 * binary search to calculate item index (e.g. DPA_Search()).
65 * This requires sorted state to be reliably tracked in item modifiers.
66 * -- we should keep an ordered array of coordinates in iconic mode.
67 * This would allow framing items (iterator_frameditems),
68 * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
70 * Flags
71 * -- LVIF_COLUMNS
72 * -- LVIF_GROUPID
74 * States
75 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVIS_DROPHILITED
78 * Styles
79 * -- LVS_NOLABELWRAP
80 * -- LVS_NOSCROLL (see Q137520)
81 * -- LVS_ALIGNTOP
83 * Extended Styles
84 * -- LVS_EX_BORDERSELECT
85 * -- LVS_EX_FLATSB
86 * -- LVS_EX_INFOTIP
87 * -- LVS_EX_LABELTIP
88 * -- LVS_EX_MULTIWORKAREAS
89 * -- LVS_EX_REGIONAL
90 * -- LVS_EX_SIMPLESELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
95 * Notifications:
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
97 * -- LVN_GETINFOTIP
98 * -- LVN_HOTTRACK
99 * -- LVN_SETDISPINFO
101 * Messages:
102 * -- LVM_ENABLEGROUPVIEW
103 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
104 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
105 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
106 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
107 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
108 * -- LVM_GETINSERTMARKRECT
109 * -- LVM_GETNUMBEROFWORKAREAS
110 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
111 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
112 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
113 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
114 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
115 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
116 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
117 * -- LVM_INSERTGROUPSORTED
118 * -- LVM_INSERTMARKHITTEST
119 * -- LVM_ISGROUPVIEWENABLED
120 * -- LVM_MOVEGROUP
121 * -- LVM_MOVEITEMTOGROUP
122 * -- LVM_SETINFOTIP
123 * -- LVM_SETTILEWIDTH
124 * -- LVM_SORTGROUPS
126 * Macros:
127 * -- ListView_GetHoverTime, ListView_SetHoverTime
128 * -- ListView_GetISearchString
129 * -- ListView_GetNumberOfWorkAreas
130 * -- ListView_GetWorkAreas, ListView_SetWorkAreas
132 * Functions:
133 * -- LVGroupComparE
136 #include "config.h"
137 #include "wine/port.h"
139 #include <assert.h>
140 #include <ctype.h>
141 #include <string.h>
142 #include <stdlib.h>
143 #include <stdarg.h>
144 #include <stdio.h>
146 #include "windef.h"
147 #include "winbase.h"
148 #include "winnt.h"
149 #include "wingdi.h"
150 #include "winuser.h"
151 #include "winnls.h"
152 #include "commctrl.h"
153 #include "comctl32.h"
154 #include "uxtheme.h"
156 #include "wine/debug.h"
157 #include "wine/unicode.h"
159 WINE_DEFAULT_DEBUG_CHANNEL(listview);
161 typedef struct tagCOLUMN_INFO
163 RECT rcHeader; /* tracks the header's rectangle */
164 INT fmt; /* same as LVCOLUMN.fmt */
165 INT cxMin;
166 } COLUMN_INFO;
168 typedef struct tagITEMHDR
170 LPWSTR pszText;
171 INT iImage;
172 } ITEMHDR, *LPITEMHDR;
174 typedef struct tagSUBITEM_INFO
176 ITEMHDR hdr;
177 INT iSubItem;
178 } SUBITEM_INFO;
180 typedef struct tagITEM_ID ITEM_ID;
182 typedef struct tagITEM_INFO
184 ITEMHDR hdr;
185 UINT state;
186 LPARAM lParam;
187 INT iIndent;
188 ITEM_ID *id;
189 } ITEM_INFO;
191 struct tagITEM_ID
193 UINT id; /* item id */
194 HDPA item; /* link to item data */
197 typedef struct tagRANGE
199 INT lower;
200 INT upper;
201 } RANGE;
203 typedef struct tagRANGES
205 HDPA hdpa;
206 } *RANGES;
208 typedef struct tagITERATOR
210 INT nItem;
211 INT nSpecial;
212 RANGE range;
213 RANGES ranges;
214 INT index;
215 } ITERATOR;
217 typedef struct tagDELAYED_ITEM_EDIT
219 BOOL fEnabled;
220 INT iItem;
221 } DELAYED_ITEM_EDIT;
223 typedef struct tagLISTVIEW_INFO
225 /* control window */
226 HWND hwndSelf;
227 RECT rcList; /* This rectangle is really the window
228 * client rectangle possibly reduced by the
229 * horizontal scroll bar and/or header - see
230 * LISTVIEW_UpdateSize. This rectangle offset
231 * by the LISTVIEW_GetOrigin value is in
232 * client coordinates */
234 /* notification window */
235 SHORT notifyFormat;
236 HWND hwndNotify;
237 BOOL bDoChangeNotify; /* send change notification messages? */
238 UINT uCallbackMask;
240 /* tooltips */
241 HWND hwndToolTip;
243 /* items */
244 INT nItemCount; /* the number of items in the list */
245 HDPA hdpaItems; /* array ITEM_INFO pointers */
246 HDPA hdpaItemIds; /* array of ITEM_ID pointers */
247 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
248 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
249 RANGES selectionRanges;
250 INT nSelectionMark; /* item to start next multiselection from */
251 INT nHotItem;
253 /* columns */
254 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
255 BOOL colRectsDirty; /* trigger column rectangles requery from header */
257 /* item metrics */
258 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
259 INT nItemHeight;
260 INT nItemWidth;
262 /* sorting */
263 PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
264 LPARAM lParamSort;
266 /* style */
267 DWORD dwStyle; /* the cached window GWL_STYLE */
268 DWORD dwLvExStyle; /* extended listview style */
269 DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
271 /* edit item */
272 HWND hwndEdit;
273 WNDPROC EditWndProc;
274 INT nEditLabelItem;
275 DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
277 /* icons */
278 HIMAGELIST himlNormal;
279 HIMAGELIST himlSmall;
280 HIMAGELIST himlState;
281 SIZE iconSize;
282 BOOL autoSpacing;
283 SIZE iconSpacing;
284 SIZE iconStateSize;
285 POINT currIconPos; /* this is the position next icon will be placed */
287 /* header */
288 HWND hwndHeader;
289 INT xTrackLine; /* The x coefficient of the track line or -1 if none */
291 /* marquee selection */
292 BOOL bMarqueeSelect; /* marquee selection/highlight underway */
293 BOOL bScrolling;
294 RECT marqueeRect; /* absolute coordinates of marquee selection */
295 RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
296 POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
298 /* focus drawing */
299 BOOL bFocus; /* control has focus */
300 INT nFocusedItem;
301 RECT rcFocus; /* focus bounds */
303 /* colors */
304 HBRUSH hBkBrush;
305 COLORREF clrBk;
306 COLORREF clrText;
307 COLORREF clrTextBk;
309 /* font */
310 HFONT hDefaultFont;
311 HFONT hFont;
312 INT ntmHeight; /* Some cached metrics of the font used */
313 INT ntmMaxCharWidth; /* by the listview to draw items */
314 INT nEllipsisWidth;
316 /* mouse operation */
317 BOOL bLButtonDown;
318 BOOL bDragging;
319 POINT ptClickPos; /* point where the user clicked */
320 INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
321 DWORD dwHoverTime;
322 HCURSOR hHotCursor;
323 INT cWheelRemainder;
325 /* keyboard operation */
326 DWORD lastKeyPressTimestamp;
327 WPARAM charCode;
328 INT nSearchParamLength;
329 WCHAR szSearchParam[ MAX_PATH ];
331 /* painting */
332 BOOL bIsDrawing; /* Drawing in progress */
333 INT nMeasureItemHeight; /* WM_MEASUREITEM result */
334 BOOL redraw; /* WM_SETREDRAW switch */
336 /* misc */
337 DWORD iVersion; /* CCM_[G,S]ETVERSION */
338 } LISTVIEW_INFO;
341 * constants
343 /* How many we debug buffer to allocate */
344 #define DEBUG_BUFFERS 20
345 /* The size of a single debug buffer */
346 #define DEBUG_BUFFER_SIZE 256
348 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
349 #define SB_INTERNAL -1
351 /* maximum size of a label */
352 #define DISP_TEXT_SIZE 260
354 /* padding for items in list and small icon display modes */
355 #define WIDTH_PADDING 12
357 /* padding for items in list, report and small icon display modes */
358 #define HEIGHT_PADDING 1
360 /* offset of items in report display mode */
361 #define REPORT_MARGINX 2
363 /* padding for icon in large icon display mode
364 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
365 * that HITTEST will see.
366 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
367 * ICON_TOP_PADDING - sum of the two above.
368 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
369 * LABEL_HOR_PADDING - between text and sides of box
370 * LABEL_VERT_PADDING - between bottom of text and end of box
372 * ICON_LR_PADDING - additional width above icon size.
373 * ICON_LR_HALF - half of the above value
375 #define ICON_TOP_PADDING_NOTHITABLE 2
376 #define ICON_TOP_PADDING_HITABLE 2
377 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
378 #define ICON_BOTTOM_PADDING 4
379 #define LABEL_HOR_PADDING 5
380 #define LABEL_VERT_PADDING 7
381 #define ICON_LR_PADDING 16
382 #define ICON_LR_HALF (ICON_LR_PADDING/2)
384 /* default label width for items in list and small icon display modes */
385 #define DEFAULT_LABEL_WIDTH 40
386 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
387 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
389 /* default column width for items in list display mode */
390 #define DEFAULT_COLUMN_WIDTH 128
392 /* Size of "line" scroll for V & H scrolls */
393 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
395 /* Padding between image and label */
396 #define IMAGE_PADDING 2
398 /* Padding behind the label */
399 #define TRAILING_LABEL_PADDING 12
400 #define TRAILING_HEADER_PADDING 11
402 /* Border for the icon caption */
403 #define CAPTION_BORDER 2
405 /* Standard DrawText flags */
406 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
407 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
408 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
410 /* Image index from state */
411 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
413 /* The time in milliseconds to reset the search in the list */
414 #define KEY_DELAY 450
416 /* Dump the LISTVIEW_INFO structure to the debug channel */
417 #define LISTVIEW_DUMP(iP) do { \
418 TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
419 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
420 iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
421 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
422 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
423 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
424 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
425 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
426 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
427 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
428 } while(0)
430 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
433 * forward declarations
435 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
436 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
437 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
438 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
439 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
440 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
441 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
442 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
443 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
444 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
445 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
446 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
447 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
448 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
449 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
450 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
451 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
452 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
453 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
454 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *, INT, INT);
456 /******** Text handling functions *************************************/
458 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
459 * text string. The string may be ANSI or Unicode, in which case
460 * the boolean isW tells us the type of the string.
462 * The name of the function tell what type of strings it expects:
463 * W: Unicode, T: ANSI/Unicode - function of isW
466 static inline BOOL is_text(LPCWSTR text)
468 return text != NULL && text != LPSTR_TEXTCALLBACKW;
471 static inline int textlenT(LPCWSTR text, BOOL isW)
473 return !is_text(text) ? 0 :
474 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
477 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
479 if (isDestW)
480 if (isSrcW) lstrcpynW(dest, src, max);
481 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
482 else
483 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
484 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
487 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
489 LPWSTR wstr = (LPWSTR)text;
491 if (!isW && is_text(text))
493 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
494 wstr = Alloc(len * sizeof(WCHAR));
495 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
497 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
498 return wstr;
501 static inline void textfreeT(LPWSTR wstr, BOOL isW)
503 if (!isW && is_text(wstr)) Free (wstr);
507 * dest is a pointer to a Unicode string
508 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
510 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
512 BOOL bResult = TRUE;
514 if (src == LPSTR_TEXTCALLBACKW)
516 if (is_text(*dest)) Free(*dest);
517 *dest = LPSTR_TEXTCALLBACKW;
519 else
521 LPWSTR pszText = textdupTtoW(src, isW);
522 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
523 bResult = Str_SetPtrW(dest, pszText);
524 textfreeT(pszText, isW);
526 return bResult;
530 * compares a Unicode to a Unicode/ANSI text string
532 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
534 if (!aw) return bt ? -1 : 0;
535 if (!bt) return 1;
536 if (aw == LPSTR_TEXTCALLBACKW)
537 return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
538 if (bt != LPSTR_TEXTCALLBACKW)
540 LPWSTR bw = textdupTtoW(bt, isW);
541 int r = bw ? lstrcmpW(aw, bw) : 1;
542 textfreeT(bw, isW);
543 return r;
546 return 1;
549 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
551 n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
552 return CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n) - CSTR_EQUAL;
555 /******** Debugging functions *****************************************/
557 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
559 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
560 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
563 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
565 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
566 n = min(textlenT(text, isW), n);
567 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
570 static char* debug_getbuf(void)
572 static int index = 0;
573 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
574 return buffers[index++ % DEBUG_BUFFERS];
577 static inline const char* debugrange(const RANGE *lprng)
579 if (!lprng) return "(null)";
580 return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
583 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
585 char* buf = debug_getbuf(), *text = buf;
586 int len, size = DEBUG_BUFFER_SIZE;
588 if (pScrollInfo == NULL) return "(null)";
589 len = snprintf(buf, size, "{cbSize=%u, ", pScrollInfo->cbSize);
590 if (len == -1) goto end;
591 buf += len; size -= len;
592 if (pScrollInfo->fMask & SIF_RANGE)
593 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
594 else len = 0;
595 if (len == -1) goto end;
596 buf += len; size -= len;
597 if (pScrollInfo->fMask & SIF_PAGE)
598 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
599 else len = 0;
600 if (len == -1) goto end;
601 buf += len; size -= len;
602 if (pScrollInfo->fMask & SIF_POS)
603 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
604 else len = 0;
605 if (len == -1) goto end;
606 buf += len; size -= len;
607 if (pScrollInfo->fMask & SIF_TRACKPOS)
608 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
609 else len = 0;
610 if (len == -1) goto end;
611 buf += len;
612 goto undo;
613 end:
614 buf = text + strlen(text);
615 undo:
616 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
617 return text;
620 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
622 if (!plvnm) return "(null)";
623 return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
624 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
625 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
626 plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
629 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
631 char* buf = debug_getbuf(), *text = buf;
632 int len, size = DEBUG_BUFFER_SIZE;
634 if (lpLVItem == NULL) return "(null)";
635 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
636 if (len == -1) goto end;
637 buf += len; size -= len;
638 if (lpLVItem->mask & LVIF_STATE)
639 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
640 else len = 0;
641 if (len == -1) goto end;
642 buf += len; size -= len;
643 if (lpLVItem->mask & LVIF_TEXT)
644 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
645 else len = 0;
646 if (len == -1) goto end;
647 buf += len; size -= len;
648 if (lpLVItem->mask & LVIF_IMAGE)
649 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
650 else len = 0;
651 if (len == -1) goto end;
652 buf += len; size -= len;
653 if (lpLVItem->mask & LVIF_PARAM)
654 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
655 else len = 0;
656 if (len == -1) goto end;
657 buf += len; size -= len;
658 if (lpLVItem->mask & LVIF_INDENT)
659 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
660 else len = 0;
661 if (len == -1) goto end;
662 buf += len;
663 goto undo;
664 end:
665 buf = text + strlen(text);
666 undo:
667 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
668 return text;
671 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
673 char* buf = debug_getbuf(), *text = buf;
674 int len, size = DEBUG_BUFFER_SIZE;
676 if (lpColumn == NULL) return "(null)";
677 len = snprintf(buf, size, "{");
678 if (len == -1) goto end;
679 buf += len; size -= len;
680 if (lpColumn->mask & LVCF_SUBITEM)
681 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
682 else len = 0;
683 if (len == -1) goto end;
684 buf += len; size -= len;
685 if (lpColumn->mask & LVCF_FMT)
686 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
687 else len = 0;
688 if (len == -1) goto end;
689 buf += len; size -= len;
690 if (lpColumn->mask & LVCF_WIDTH)
691 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
692 else len = 0;
693 if (len == -1) goto end;
694 buf += len; size -= len;
695 if (lpColumn->mask & LVCF_TEXT)
696 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
697 else len = 0;
698 if (len == -1) goto end;
699 buf += len; size -= len;
700 if (lpColumn->mask & LVCF_IMAGE)
701 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
702 else len = 0;
703 if (len == -1) goto end;
704 buf += len; size -= len;
705 if (lpColumn->mask & LVCF_ORDER)
706 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
707 else len = 0;
708 if (len == -1) goto end;
709 buf += len;
710 goto undo;
711 end:
712 buf = text + strlen(text);
713 undo:
714 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
715 return text;
718 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
720 if (!lpht) return "(null)";
722 return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
723 wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
726 /* Return the corresponding text for a given scroll value */
727 static inline LPCSTR debugscrollcode(int nScrollCode)
729 switch(nScrollCode)
731 case SB_LINELEFT: return "SB_LINELEFT";
732 case SB_LINERIGHT: return "SB_LINERIGHT";
733 case SB_PAGELEFT: return "SB_PAGELEFT";
734 case SB_PAGERIGHT: return "SB_PAGERIGHT";
735 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
736 case SB_THUMBTRACK: return "SB_THUMBTRACK";
737 case SB_ENDSCROLL: return "SB_ENDSCROLL";
738 case SB_INTERNAL: return "SB_INTERNAL";
739 default: return "unknown";
744 /******** Notification functions ************************************/
746 static int get_ansi_notification(UINT unicodeNotificationCode)
748 switch (unicodeNotificationCode)
750 case LVN_BEGINLABELEDITA:
751 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
752 case LVN_ENDLABELEDITA:
753 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
754 case LVN_GETDISPINFOA:
755 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
756 case LVN_SETDISPINFOA:
757 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
758 case LVN_ODFINDITEMA:
759 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
760 case LVN_GETINFOTIPA:
761 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
762 /* header forwards */
763 case HDN_TRACKA:
764 case HDN_TRACKW: return HDN_TRACKA;
765 case HDN_ENDTRACKA:
766 case HDN_ENDTRACKW: return HDN_ENDTRACKA;
767 case HDN_BEGINDRAG: return HDN_BEGINDRAG;
768 case HDN_ENDDRAG: return HDN_ENDDRAG;
769 case HDN_ITEMCHANGINGA:
770 case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
771 case HDN_ITEMCHANGEDA:
772 case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
773 case HDN_ITEMCLICKA:
774 case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
775 case HDN_DIVIDERDBLCLICKA:
776 case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
777 default: break;
779 FIXME("unknown notification %x\n", unicodeNotificationCode);
780 return unicodeNotificationCode;
783 /* forwards header notifications to listview parent */
784 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
786 LPCWSTR text = NULL, filter = NULL;
787 LRESULT ret;
788 NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
790 /* on unicode format exit earlier */
791 if (infoPtr->notifyFormat == NFR_UNICODE)
792 return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
793 (LPARAM)lpnmh);
795 /* header always supplies unicode notifications,
796 all we have to do is to convert strings to ANSI */
797 if (lpnmh->pitem)
799 /* convert item text */
800 if (lpnmh->pitem->mask & HDI_TEXT)
802 text = (LPCWSTR)lpnmh->pitem->pszText;
803 lpnmh->pitem->pszText = NULL;
804 Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
806 /* convert filter text */
807 if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
808 lpnmh->pitem->pvFilter)
810 filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
811 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = NULL;
812 Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
815 lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
817 ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
818 (LPARAM)lpnmh);
820 /* cleanup */
821 if(text)
823 Free(lpnmh->pitem->pszText);
824 lpnmh->pitem->pszText = (LPSTR)text;
826 if(filter)
828 Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
829 ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
832 return ret;
835 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
837 LRESULT result;
839 TRACE("(code=%d)\n", code);
841 pnmh->hwndFrom = infoPtr->hwndSelf;
842 pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
843 pnmh->code = code;
844 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
846 TRACE(" <= %ld\n", result);
848 return result;
851 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
853 NMHDR nmh;
854 HWND hwnd = infoPtr->hwndSelf;
855 notify_hdr(infoPtr, code, &nmh);
856 return IsWindow(hwnd);
859 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
861 NMITEMACTIVATE nmia;
862 LVITEMW item;
864 nmia.uNewState = 0;
865 nmia.uOldState = 0;
866 nmia.uChanged = 0;
867 nmia.uKeyFlags = 0;
869 item.mask = LVIF_PARAM|LVIF_STATE;
870 item.iItem = htInfo->iItem;
871 item.iSubItem = 0;
872 item.stateMask = (UINT)-1;
873 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
874 nmia.lParam = item.lParam;
875 nmia.uOldState = item.state;
876 nmia.uNewState = item.state | LVIS_ACTIVATING;
877 nmia.uChanged = LVIF_STATE;
880 nmia.iItem = htInfo->iItem;
881 nmia.iSubItem = htInfo->iSubItem;
882 nmia.ptAction = htInfo->pt;
884 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
885 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
886 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
888 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
891 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
893 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
894 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
897 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
898 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
900 NMITEMACTIVATE nmia;
901 LVITEMW item;
902 HWND hwnd = infoPtr->hwndSelf;
903 LRESULT ret;
905 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
906 ZeroMemory(&nmia, sizeof(nmia));
907 nmia.iItem = lvht->iItem;
908 nmia.iSubItem = lvht->iSubItem;
909 nmia.ptAction = lvht->pt;
910 item.mask = LVIF_PARAM;
911 item.iItem = lvht->iItem;
912 item.iSubItem = 0;
913 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
914 ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
915 return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
918 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
920 NMLISTVIEW nmlv;
921 LVITEMW item;
922 HWND hwnd = infoPtr->hwndSelf;
924 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
925 nmlv.iItem = nItem;
926 item.mask = LVIF_PARAM;
927 item.iItem = nItem;
928 item.iSubItem = 0;
929 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
930 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
931 return IsWindow(hwnd);
935 Send notification. depends on dispinfoW having same
936 structure as dispinfoA.
937 infoPtr : listview struct
938 code : *Unicode* notification code
939 pdi : dispinfo structure (can be unicode or ansi)
940 isW : TRUE if dispinfo is Unicode
942 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT code, LPNMLVDISPINFOW pdi, BOOL isW)
944 INT length = 0, ret_length;
945 LPWSTR buffer = NULL, ret_text;
946 BOOL return_ansi = FALSE;
947 BOOL return_unicode = FALSE;
948 BOOL ret;
950 if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
952 return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
953 return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
956 ret_length = pdi->item.cchTextMax;
957 ret_text = pdi->item.pszText;
959 if (return_unicode || return_ansi)
961 if (code != LVN_GETDISPINFOW)
963 length = return_ansi ?
964 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
965 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
967 else
969 length = pdi->item.cchTextMax;
970 *pdi->item.pszText = 0; /* make sure we don't process garbage */
973 buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
974 if (!buffer) return FALSE;
976 if (return_ansi)
977 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
978 buffer, length);
979 else
980 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
981 length, NULL, NULL);
983 pdi->item.pszText = buffer;
984 pdi->item.cchTextMax = length;
987 if (infoPtr->notifyFormat == NFR_ANSI)
988 code = get_ansi_notification(code);
990 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
991 ret = notify_hdr(infoPtr, code, &pdi->hdr);
992 TRACE(" resulting code=%d\n", pdi->hdr.code);
994 if (return_ansi || return_unicode)
996 if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
998 strcpy((char*)ret_text, (char*)pdi->item.pszText);
1000 else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
1002 strcpyW(ret_text, pdi->item.pszText);
1004 else if (return_ansi) /* note : pointer can be changed by app ! */
1006 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
1007 ret_length, NULL, NULL);
1009 else
1010 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
1011 ret_text, ret_length);
1013 pdi->item.pszText = ret_text; /* restores our buffer */
1014 pdi->item.cchTextMax = ret_length;
1016 Free(buffer);
1017 return ret;
1020 /* if dipsinfo holder changed notification code then convert */
1021 if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1023 length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1025 buffer = Alloc(length * sizeof(CHAR));
1026 if (!buffer) return FALSE;
1028 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1029 ret_length, NULL, NULL);
1031 strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1032 Free(buffer);
1035 return ret;
1038 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1039 const RECT *rcBounds, const LVITEMW *lplvItem)
1041 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1042 lpnmlvcd->nmcd.hdc = hdc;
1043 lpnmlvcd->nmcd.rc = *rcBounds;
1044 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1045 lpnmlvcd->clrText = infoPtr->clrText;
1046 if (!lplvItem) return;
1047 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1048 lpnmlvcd->iSubItem = lplvItem->iSubItem;
1049 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1050 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1051 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1052 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1055 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1057 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1058 DWORD result;
1060 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1061 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1062 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1063 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1064 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1065 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1066 return result;
1069 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1071 COLORREF backcolor, textcolor;
1073 /* apparently, for selected items, we have to override the returned values */
1074 if (!SubItem)
1076 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1078 if (infoPtr->bFocus)
1080 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1081 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
1083 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1085 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1086 lpnmlvcd->clrText = comctl32_color.clrBtnText;
1091 backcolor = lpnmlvcd->clrTextBk;
1092 textcolor = lpnmlvcd->clrText;
1094 if (backcolor == CLR_DEFAULT)
1095 backcolor = comctl32_color.clrWindow;
1096 if (textcolor == CLR_DEFAULT)
1097 textcolor = comctl32_color.clrWindowText;
1099 /* Set the text attributes */
1100 if (backcolor != CLR_NONE)
1102 SetBkMode(hdc, OPAQUE);
1103 SetBkColor(hdc, backcolor);
1105 else
1106 SetBkMode(hdc, TRANSPARENT);
1107 SetTextColor(hdc, textcolor);
1110 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1112 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1115 /* returns TRUE when repaint needed, FALSE otherwise */
1116 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1118 MEASUREITEMSTRUCT mis;
1119 mis.CtlType = ODT_LISTVIEW;
1120 mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1121 mis.itemID = -1;
1122 mis.itemWidth = 0;
1123 mis.itemData = 0;
1124 mis.itemHeight= infoPtr->nItemHeight;
1125 SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1126 if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1128 infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1129 return TRUE;
1131 return FALSE;
1134 /******** Item iterator functions **********************************/
1136 static RANGES ranges_create(int count);
1137 static void ranges_destroy(RANGES ranges);
1138 static BOOL ranges_add(RANGES ranges, RANGE range);
1139 static BOOL ranges_del(RANGES ranges, RANGE range);
1140 static void ranges_dump(RANGES ranges);
1142 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1144 RANGE range = { nItem, nItem + 1 };
1146 return ranges_add(ranges, range);
1149 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1151 RANGE range = { nItem, nItem + 1 };
1153 return ranges_del(ranges, range);
1156 /***
1157 * ITERATOR DOCUMENTATION
1159 * The iterator functions allow for easy, and convenient iteration
1160 * over items of interest in the list. Typically, you create an
1161 * iterator, use it, and destroy it, as such:
1162 * ITERATOR i;
1164 * iterator_xxxitems(&i, ...);
1165 * while (iterator_{prev,next}(&i)
1167 * //code which uses i.nItem
1169 * iterator_destroy(&i);
1171 * where xxx is either: framed, or visible.
1172 * Note that it is important that the code destroys the iterator
1173 * after it's done with it, as the creation of the iterator may
1174 * allocate memory, which thus needs to be freed.
1176 * You can iterate both forwards, and backwards through the list,
1177 * by using iterator_next or iterator_prev respectively.
1179 * Lower numbered items are draw on top of higher number items in
1180 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1181 * items may overlap). So, to test items, you should use
1182 * iterator_next
1183 * which lists the items top to bottom (in Z-order).
1184 * For drawing items, you should use
1185 * iterator_prev
1186 * which lists the items bottom to top (in Z-order).
1187 * If you keep iterating over the items after the end-of-items
1188 * marker (-1) is returned, the iterator will start from the
1189 * beginning. Typically, you don't need to test for -1,
1190 * because iterator_{next,prev} will return TRUE if more items
1191 * are to be iterated over, or FALSE otherwise.
1193 * Note: the iterator is defined to be bidirectional. That is,
1194 * any number of prev followed by any number of next, or
1195 * five versa, should leave the iterator at the same item:
1196 * prev * n, next * n = next * n, prev * n
1198 * The iterator has a notion of an out-of-order, special item,
1199 * which sits at the start of the list. This is used in
1200 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1201 * which needs to be first, as it may overlap other items.
1203 * The code is a bit messy because we have:
1204 * - a special item to deal with
1205 * - simple range, or composite range
1206 * - empty range.
1207 * If you find bugs, or want to add features, please make sure you
1208 * always check/modify *both* iterator_prev, and iterator_next.
1211 /****
1212 * This function iterates through the items in increasing order,
1213 * but prefixed by the special item, then -1. That is:
1214 * special, 1, 2, 3, ..., n, -1.
1215 * Each item is listed only once.
1217 static inline BOOL iterator_next(ITERATOR* i)
1219 if (i->nItem == -1)
1221 i->nItem = i->nSpecial;
1222 if (i->nItem != -1) return TRUE;
1224 if (i->nItem == i->nSpecial)
1226 if (i->ranges) i->index = 0;
1227 goto pickarange;
1230 i->nItem++;
1231 testitem:
1232 if (i->nItem == i->nSpecial) i->nItem++;
1233 if (i->nItem < i->range.upper) return TRUE;
1235 pickarange:
1236 if (i->ranges)
1238 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1239 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1240 else goto end;
1242 else if (i->nItem >= i->range.upper) goto end;
1244 i->nItem = i->range.lower;
1245 if (i->nItem >= 0) goto testitem;
1246 end:
1247 i->nItem = -1;
1248 return FALSE;
1251 /****
1252 * This function iterates through the items in decreasing order,
1253 * followed by the special item, then -1. That is:
1254 * n, n-1, ..., 3, 2, 1, special, -1.
1255 * Each item is listed only once.
1257 static inline BOOL iterator_prev(ITERATOR* i)
1259 BOOL start = FALSE;
1261 if (i->nItem == -1)
1263 start = TRUE;
1264 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1265 goto pickarange;
1267 if (i->nItem == i->nSpecial)
1269 i->nItem = -1;
1270 return FALSE;
1273 testitem:
1274 i->nItem--;
1275 if (i->nItem == i->nSpecial) i->nItem--;
1276 if (i->nItem >= i->range.lower) return TRUE;
1278 pickarange:
1279 if (i->ranges)
1281 if (i->index > 0)
1282 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1283 else goto end;
1285 else if (!start && i->nItem < i->range.lower) goto end;
1287 i->nItem = i->range.upper;
1288 if (i->nItem > 0) goto testitem;
1289 end:
1290 return (i->nItem = i->nSpecial) != -1;
1293 static RANGE iterator_range(const ITERATOR *i)
1295 RANGE range;
1297 if (!i->ranges) return i->range;
1299 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1301 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1302 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1304 else range.lower = range.upper = 0;
1306 return range;
1309 /***
1310 * Releases resources associated with this iterator.
1312 static inline void iterator_destroy(const ITERATOR *i)
1314 ranges_destroy(i->ranges);
1317 /***
1318 * Create an empty iterator.
1320 static inline BOOL iterator_empty(ITERATOR* i)
1322 ZeroMemory(i, sizeof(*i));
1323 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1324 return TRUE;
1327 /***
1328 * Create an iterator over a range.
1330 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1332 iterator_empty(i);
1333 i->range = range;
1334 return TRUE;
1337 /***
1338 * Create an iterator over a bunch of ranges.
1339 * Please note that the iterator will take ownership of the ranges,
1340 * and will free them upon destruction.
1342 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1344 iterator_empty(i);
1345 i->ranges = ranges;
1346 return TRUE;
1349 /***
1350 * Creates an iterator over the items which intersect frame.
1351 * Uses absolute coordinates rather than compensating for the current offset.
1353 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1355 RECT rcItem, rcTemp;
1357 /* in case we fail, we want to return an empty iterator */
1358 if (!iterator_empty(i)) return FALSE;
1360 TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1362 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1364 INT nItem;
1366 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1368 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1369 if (IntersectRect(&rcTemp, &rcItem, frame))
1370 i->nSpecial = infoPtr->nFocusedItem;
1372 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1373 /* to do better here, we need to have PosX, and PosY sorted */
1374 TRACE("building icon ranges:\n");
1375 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1377 rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1378 rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1379 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1380 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1381 if (IntersectRect(&rcTemp, &rcItem, frame))
1382 ranges_additem(i->ranges, nItem);
1384 return TRUE;
1386 else if (infoPtr->uView == LV_VIEW_DETAILS)
1388 RANGE range;
1390 if (frame->left >= infoPtr->nItemWidth) return TRUE;
1391 if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1393 range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1394 range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1395 if (range.upper <= range.lower) return TRUE;
1396 if (!iterator_rangeitems(i, range)) return FALSE;
1397 TRACE(" report=%s\n", debugrange(&i->range));
1399 else
1401 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1402 INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1403 INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1404 INT nFirstCol;
1405 INT nLastCol;
1406 INT lower;
1407 RANGE item_range;
1408 INT nCol;
1410 if (infoPtr->nItemWidth)
1412 nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1413 nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1415 else
1417 nFirstCol = max(frame->left, 0);
1418 nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1421 lower = nFirstCol * nPerCol + nFirstRow;
1423 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1424 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1426 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1428 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1429 TRACE("building list ranges:\n");
1430 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1432 item_range.lower = nCol * nPerCol + nFirstRow;
1433 if(item_range.lower >= infoPtr->nItemCount) break;
1434 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1435 TRACE(" list=%s\n", debugrange(&item_range));
1436 ranges_add(i->ranges, item_range);
1440 return TRUE;
1443 /***
1444 * Creates an iterator over the items which intersect lprc.
1446 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1448 RECT frame = *lprc;
1449 POINT Origin;
1451 TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1453 LISTVIEW_GetOrigin(infoPtr, &Origin);
1454 OffsetRect(&frame, -Origin.x, -Origin.y);
1456 return iterator_frameditems_absolute(i, infoPtr, &frame);
1459 /***
1460 * Creates an iterator over the items which intersect the visible region of hdc.
1462 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC hdc)
1464 POINT Origin, Position;
1465 RECT rcItem, rcClip;
1466 INT rgntype;
1468 rgntype = GetClipBox(hdc, &rcClip);
1469 if (rgntype == NULLREGION) return iterator_empty(i);
1470 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1471 if (rgntype == SIMPLEREGION) return TRUE;
1473 /* first deal with the special item */
1474 if (i->nSpecial != -1)
1476 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1477 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1480 /* if we can't deal with the region, we'll just go with the simple range */
1481 LISTVIEW_GetOrigin(infoPtr, &Origin);
1482 TRACE("building visible range:\n");
1483 if (!i->ranges && i->range.lower < i->range.upper)
1485 if (!(i->ranges = ranges_create(50))) return TRUE;
1486 if (!ranges_add(i->ranges, i->range))
1488 ranges_destroy(i->ranges);
1489 i->ranges = 0;
1490 return TRUE;
1494 /* now delete the invisible items from the list */
1495 while(iterator_next(i))
1497 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1498 rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1499 rcItem.top = Position.y + Origin.y;
1500 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1501 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1502 if (!RectVisible(hdc, &rcItem))
1503 ranges_delitem(i->ranges, i->nItem);
1505 /* the iterator should restart on the next iterator_next */
1506 TRACE("done\n");
1508 return TRUE;
1511 /* Remove common elements from two iterators */
1512 /* Passed iterators have to point on the first elements */
1513 static BOOL iterator_remove_common_items(ITERATOR *iter1, ITERATOR *iter2)
1515 if(!iter1->ranges || !iter2->ranges) {
1516 int lower, upper;
1518 if(iter1->ranges || iter2->ranges ||
1519 (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1520 (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1521 ERR("result is not a one range iterator\n");
1522 return FALSE;
1525 if(iter1->range.lower==-1 || iter2->range.lower==-1)
1526 return TRUE;
1528 lower = iter1->range.lower;
1529 upper = iter1->range.upper;
1531 if(lower < iter2->range.lower)
1532 iter1->range.upper = iter2->range.lower;
1533 else if(upper > iter2->range.upper)
1534 iter1->range.lower = iter2->range.upper;
1535 else
1536 iter1->range.lower = iter1->range.upper = -1;
1538 if(iter2->range.lower < lower)
1539 iter2->range.upper = lower;
1540 else if(iter2->range.upper > upper)
1541 iter2->range.lower = upper;
1542 else
1543 iter2->range.lower = iter2->range.upper = -1;
1545 return TRUE;
1548 iterator_next(iter1);
1549 iterator_next(iter2);
1551 while(1) {
1552 if(iter1->nItem==-1 || iter2->nItem==-1)
1553 break;
1555 if(iter1->nItem == iter2->nItem) {
1556 int delete = iter1->nItem;
1558 iterator_prev(iter1);
1559 iterator_prev(iter2);
1560 ranges_delitem(iter1->ranges, delete);
1561 ranges_delitem(iter2->ranges, delete);
1562 iterator_next(iter1);
1563 iterator_next(iter2);
1564 } else if(iter1->nItem > iter2->nItem)
1565 iterator_next(iter2);
1566 else
1567 iterator_next(iter1);
1570 iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1571 iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1572 return TRUE;
1575 /******** Misc helper functions ************************************/
1577 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1578 WPARAM wParam, LPARAM lParam, BOOL isW)
1580 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1581 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1584 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1586 return (infoPtr->dwStyle & LVS_AUTOARRANGE) &&
1587 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1590 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1592 DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1593 if(state == 1 || state == 2)
1595 LVITEMW lvitem;
1596 state ^= 3;
1597 lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1598 lvitem.stateMask = LVIS_STATEIMAGEMASK;
1599 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1603 /* this should be called after window style got updated,
1604 it used to reset view state to match current window style */
1605 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1607 switch (infoPtr->dwStyle & LVS_TYPEMASK)
1609 case LVS_ICON:
1610 infoPtr->uView = LV_VIEW_ICON;
1611 break;
1612 case LVS_REPORT:
1613 infoPtr->uView = LV_VIEW_DETAILS;
1614 break;
1615 case LVS_SMALLICON:
1616 infoPtr->uView = LV_VIEW_SMALLICON;
1617 break;
1618 case LVS_LIST:
1619 infoPtr->uView = LV_VIEW_LIST;
1623 /* computes next item id value */
1624 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1626 INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1628 if (count > 0)
1630 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1631 return lpID->id + 1;
1633 return 0;
1636 /******** Internal API functions ************************************/
1638 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1640 static COLUMN_INFO mainItem;
1642 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1643 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1645 /* update cached column rectangles */
1646 if (infoPtr->colRectsDirty)
1648 COLUMN_INFO *info;
1649 LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1650 INT i;
1652 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1653 info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1654 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1656 Ptr->colRectsDirty = FALSE;
1659 return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1662 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1664 DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1665 HINSTANCE hInst;
1667 if (infoPtr->hwndHeader) return 0;
1669 TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1671 /* setup creation flags */
1672 dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1673 dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1675 hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1677 /* create header */
1678 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1679 0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1680 if (!infoPtr->hwndHeader) return -1;
1682 /* set header unicode format */
1683 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1685 /* set header font */
1686 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1688 /* set header image list */
1689 if (infoPtr->himlSmall)
1690 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1692 LISTVIEW_UpdateSize(infoPtr);
1694 return 0;
1697 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1699 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1702 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1704 return (infoPtr->uView == LV_VIEW_DETAILS ||
1705 infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1706 !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1709 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1711 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1714 /* used to handle collapse main item column case */
1715 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1717 return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1718 DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1721 /* Listview invalidation functions: use _only_ these functions to invalidate */
1723 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1725 return infoPtr->redraw;
1728 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1730 if(!is_redrawing(infoPtr)) return;
1731 TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1732 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1735 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1737 RECT rcBox;
1739 if(!is_redrawing(infoPtr)) return;
1740 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1741 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1744 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1746 POINT Origin, Position;
1747 RECT rcBox;
1749 if(!is_redrawing(infoPtr)) return;
1750 assert (infoPtr->uView == LV_VIEW_DETAILS);
1751 LISTVIEW_GetOrigin(infoPtr, &Origin);
1752 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1753 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1754 rcBox.top = 0;
1755 rcBox.bottom = infoPtr->nItemHeight;
1756 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1757 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1760 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1762 LISTVIEW_InvalidateRect(infoPtr, NULL);
1765 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1767 RECT rcCol;
1769 if(!is_redrawing(infoPtr)) return;
1770 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1771 rcCol.top = infoPtr->rcList.top;
1772 rcCol.bottom = infoPtr->rcList.bottom;
1773 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1776 /***
1777 * DESCRIPTION:
1778 * Retrieves the number of items that can fit vertically in the client area.
1780 * PARAMETER(S):
1781 * [I] infoPtr : valid pointer to the listview structure
1783 * RETURN:
1784 * Number of items per row.
1786 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1788 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1790 return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1793 /***
1794 * DESCRIPTION:
1795 * Retrieves the number of items that can fit horizontally in the client
1796 * area.
1798 * PARAMETER(S):
1799 * [I] infoPtr : valid pointer to the listview structure
1801 * RETURN:
1802 * Number of items per column.
1804 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1806 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1808 return max(nListHeight / infoPtr->nItemHeight, 1);
1812 /*************************************************************************
1813 * LISTVIEW_ProcessLetterKeys
1815 * Processes keyboard messages generated by pressing the letter keys
1816 * on the keyboard.
1817 * What this does is perform a case insensitive search from the
1818 * current position with the following quirks:
1819 * - If two chars or more are pressed in quick succession we search
1820 * for the corresponding string (e.g. 'abc').
1821 * - If there is a delay we wipe away the current search string and
1822 * restart with just that char.
1823 * - If the user keeps pressing the same character, whether slowly or
1824 * fast, so that the search string is entirely composed of this
1825 * character ('aaaaa' for instance), then we search for first item
1826 * that starting with that character.
1827 * - If the user types the above character in quick succession, then
1828 * we must also search for the corresponding string ('aaaaa'), and
1829 * go to that string if there is a match.
1831 * PARAMETERS
1832 * [I] hwnd : handle to the window
1833 * [I] charCode : the character code, the actual character
1834 * [I] keyData : key data
1836 * RETURNS
1838 * Zero.
1840 * BUGS
1842 * - The current implementation has a list of characters it will
1843 * accept and it ignores everything else. In particular it will
1844 * ignore accentuated characters which seems to match what
1845 * Windows does. But I'm not sure it makes sense to follow
1846 * Windows there.
1847 * - We don't sound a beep when the search fails.
1849 * SEE ALSO
1851 * TREEVIEW_ProcessLetterKeys
1853 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1855 WCHAR buffer[MAX_PATH];
1856 DWORD prevTime;
1857 LVITEMW item;
1858 int startidx;
1859 INT nItem;
1860 INT diff;
1862 /* simple parameter checking */
1863 if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1865 /* only allow the valid WM_CHARs through */
1866 if (!isalnumW(charCode) &&
1867 charCode != '.' && charCode != '`' && charCode != '!' &&
1868 charCode != '@' && charCode != '#' && charCode != '$' &&
1869 charCode != '%' && charCode != '^' && charCode != '&' &&
1870 charCode != '*' && charCode != '(' && charCode != ')' &&
1871 charCode != '-' && charCode != '_' && charCode != '+' &&
1872 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1873 charCode != '}' && charCode != '[' && charCode != '{' &&
1874 charCode != '/' && charCode != '?' && charCode != '>' &&
1875 charCode != '<' && charCode != ',' && charCode != '~')
1876 return 0;
1878 /* update the search parameters */
1879 prevTime = infoPtr->lastKeyPressTimestamp;
1880 infoPtr->lastKeyPressTimestamp = GetTickCount();
1881 diff = infoPtr->lastKeyPressTimestamp - prevTime;
1883 if (diff >= 0 && diff < KEY_DELAY)
1885 if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1886 infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1888 if (infoPtr->charCode != charCode)
1889 infoPtr->charCode = charCode = 0;
1891 else
1893 infoPtr->charCode = charCode;
1894 infoPtr->szSearchParam[0] = charCode;
1895 infoPtr->nSearchParamLength = 1;
1898 /* should start from next after focused item, so next item that matches
1899 will be selected, if there isn't any and focused matches it will be selected
1900 on second search stage from beginning of the list */
1901 if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1903 /* with some accumulated search data available start with current focus, otherwise
1904 it's excluded from search */
1905 startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
1906 if (startidx == infoPtr->nItemCount) startidx = 0;
1908 else
1909 startidx = 0;
1911 /* let application handle this for virtual listview */
1912 if (infoPtr->dwStyle & LVS_OWNERDATA)
1914 NMLVFINDITEMW nmlv;
1916 memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1917 nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1918 nmlv.lvfi.psz = infoPtr->szSearchParam;
1919 nmlv.iStart = startidx;
1921 infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1923 nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1925 else
1927 int i = startidx, endidx;
1929 /* and search from the current position */
1930 nItem = -1;
1931 endidx = infoPtr->nItemCount;
1933 /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1934 while (1)
1936 /* start from first item if not found with >= startidx */
1937 if (i == infoPtr->nItemCount && startidx > 0)
1939 endidx = startidx;
1940 startidx = 0;
1943 for (i = startidx; i < endidx; i++)
1945 /* retrieve text */
1946 item.mask = LVIF_TEXT;
1947 item.iItem = i;
1948 item.iSubItem = 0;
1949 item.pszText = buffer;
1950 item.cchTextMax = MAX_PATH;
1951 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1953 if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
1955 nItem = i;
1956 break;
1958 /* this is used to find first char match when search string is not available yet,
1959 otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1960 already waiting for user to complete a string */
1961 else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
1963 /* this would work but we must keep looking for a longer match */
1964 nItem = i;
1968 if ( nItem != -1 || /* found something */
1969 endidx != infoPtr->nItemCount || /* second search done */
1970 (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1971 break;
1975 if (nItem != -1)
1976 LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1978 return 0;
1981 /*************************************************************************
1982 * LISTVIEW_UpdateHeaderSize [Internal]
1984 * Function to resize the header control
1986 * PARAMS
1987 * [I] hwnd : handle to a window
1988 * [I] nNewScrollPos : scroll pos to set
1990 * RETURNS
1991 * None.
1993 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1995 RECT winRect;
1996 POINT point[2];
1998 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
2000 if (!infoPtr->hwndHeader) return;
2002 GetWindowRect(infoPtr->hwndHeader, &winRect);
2003 point[0].x = winRect.left;
2004 point[0].y = winRect.top;
2005 point[1].x = winRect.right;
2006 point[1].y = winRect.bottom;
2008 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
2009 point[0].x = -nNewScrollPos;
2010 point[1].x += nNewScrollPos;
2012 SetWindowPos(infoPtr->hwndHeader,0,
2013 point[0].x,point[0].y,point[1].x,point[1].y,
2014 (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
2015 SWP_NOZORDER | SWP_NOACTIVATE);
2018 static INT LISTVIEW_UpdateHScroll(LISTVIEW_INFO *infoPtr)
2020 SCROLLINFO horzInfo;
2021 INT dx;
2023 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2024 horzInfo.cbSize = sizeof(SCROLLINFO);
2025 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2027 /* for now, we'll set info.nMax to the _count_, and adjust it later */
2028 if (infoPtr->uView == LV_VIEW_LIST)
2030 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2031 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2033 /* scroll by at least one column per page */
2034 if(horzInfo.nPage < infoPtr->nItemWidth)
2035 horzInfo.nPage = infoPtr->nItemWidth;
2037 if (infoPtr->nItemWidth)
2038 horzInfo.nPage /= infoPtr->nItemWidth;
2040 else if (infoPtr->uView == LV_VIEW_DETAILS)
2042 horzInfo.nMax = infoPtr->nItemWidth;
2044 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2046 RECT rcView;
2048 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2051 if (LISTVIEW_IsHeaderEnabled(infoPtr))
2053 if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2055 RECT rcHeader;
2056 INT index;
2058 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2059 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2061 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2062 horzInfo.nMax = rcHeader.right;
2063 TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2067 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2068 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2069 dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2070 dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2071 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2073 /* Update the Header Control */
2074 if (infoPtr->hwndHeader)
2076 horzInfo.fMask = SIF_POS;
2077 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2078 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2081 LISTVIEW_UpdateSize(infoPtr);
2082 return dx;
2085 static INT LISTVIEW_UpdateVScroll(LISTVIEW_INFO *infoPtr)
2087 SCROLLINFO vertInfo;
2088 INT dy;
2090 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2091 vertInfo.cbSize = sizeof(SCROLLINFO);
2092 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2094 if (infoPtr->uView == LV_VIEW_DETAILS)
2096 vertInfo.nMax = infoPtr->nItemCount;
2098 /* scroll by at least one page */
2099 if(vertInfo.nPage < infoPtr->nItemHeight)
2100 vertInfo.nPage = infoPtr->nItemHeight;
2102 if (infoPtr->nItemHeight > 0)
2103 vertInfo.nPage /= infoPtr->nItemHeight;
2105 else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2107 RECT rcView;
2109 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2112 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2113 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2114 dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2115 dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2116 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2118 LISTVIEW_UpdateSize(infoPtr);
2119 return dy;
2122 /***
2123 * DESCRIPTION:
2124 * Update the scrollbars. This function should be called whenever
2125 * the content, size or view changes.
2127 * PARAMETER(S):
2128 * [I] infoPtr : valid pointer to the listview structure
2130 * RETURN:
2131 * None
2133 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
2135 INT dx, dy, pass;
2137 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2139 /* Setting the horizontal scroll can change the listview size
2140 * (and potentially everything else) so we need to recompute
2141 * everything again for the vertical scroll and vice-versa
2143 for (dx = 0, dy = 0, pass = 0; pass <= 1; pass++)
2145 dx += LISTVIEW_UpdateHScroll(infoPtr);
2146 dy += LISTVIEW_UpdateVScroll(infoPtr);
2149 /* Change of the range may have changed the scroll pos. If so move the content */
2150 if (dx != 0 || dy != 0)
2152 RECT listRect;
2153 listRect = infoPtr->rcList;
2154 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2155 SW_ERASE | SW_INVALIDATE);
2160 /***
2161 * DESCRIPTION:
2162 * Shows/hides the focus rectangle.
2164 * PARAMETER(S):
2165 * [I] infoPtr : valid pointer to the listview structure
2166 * [I] fShow : TRUE to show the focus, FALSE to hide it.
2168 * RETURN:
2169 * None
2171 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2173 HDC hdc;
2175 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2177 if (infoPtr->nFocusedItem < 0) return;
2179 /* we need some gymnastics in ICON mode to handle large items */
2180 if (infoPtr->uView == LV_VIEW_ICON)
2182 RECT rcBox;
2184 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2185 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2187 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2188 return;
2192 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2194 /* for some reason, owner draw should work only in report mode */
2195 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2197 DRAWITEMSTRUCT dis;
2198 LVITEMW item;
2200 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2201 HFONT hOldFont = SelectObject(hdc, hFont);
2203 item.iItem = infoPtr->nFocusedItem;
2204 item.iSubItem = 0;
2205 item.mask = LVIF_PARAM;
2206 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2208 ZeroMemory(&dis, sizeof(dis));
2209 dis.CtlType = ODT_LISTVIEW;
2210 dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2211 dis.itemID = item.iItem;
2212 dis.itemAction = ODA_FOCUS;
2213 if (fShow) dis.itemState |= ODS_FOCUS;
2214 dis.hwndItem = infoPtr->hwndSelf;
2215 dis.hDC = hdc;
2216 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2217 dis.itemData = item.lParam;
2219 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2221 SelectObject(hdc, hOldFont);
2223 else
2225 LISTVIEW_DrawFocusRect(infoPtr, hdc);
2227 done:
2228 ReleaseDC(infoPtr->hwndSelf, hdc);
2231 /***
2232 * Invalidates all visible selected items.
2234 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2236 ITERATOR i;
2238 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2239 while(iterator_next(&i))
2241 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2242 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2244 iterator_destroy(&i);
2248 /***
2249 * DESCRIPTION: [INTERNAL]
2250 * Computes an item's (left,top) corner, relative to rcView.
2251 * That is, the position has NOT been made relative to the Origin.
2252 * This is deliberate, to avoid computing the Origin over, and
2253 * over again, when this function is called in a loop. Instead,
2254 * one can factor the computation of the Origin before the loop,
2255 * and offset the value returned by this function, on every iteration.
2257 * PARAMETER(S):
2258 * [I] infoPtr : valid pointer to the listview structure
2259 * [I] nItem : item number
2260 * [O] lpptOrig : item top, left corner
2262 * RETURN:
2263 * None.
2265 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2267 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2269 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2271 lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2272 lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2274 else if (infoPtr->uView == LV_VIEW_LIST)
2276 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2277 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2278 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2280 else /* LV_VIEW_DETAILS */
2282 lpptPosition->x = REPORT_MARGINX;
2283 /* item is always at zero indexed column */
2284 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2285 lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2286 lpptPosition->y = nItem * infoPtr->nItemHeight;
2290 /***
2291 * DESCRIPTION: [INTERNAL]
2292 * Compute the rectangles of an item. This is to localize all
2293 * the computations in one place. If you are not interested in some
2294 * of these values, simply pass in a NULL -- the function is smart
2295 * enough to compute only what's necessary. The function computes
2296 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2297 * one, the BOX rectangle. This rectangle is very cheap to compute,
2298 * and is guaranteed to contain all the other rectangles. Computing
2299 * the ICON rect is also cheap, but all the others are potentially
2300 * expensive. This gives an easy and effective optimization when
2301 * searching (like point inclusion, or rectangle intersection):
2302 * first test against the BOX, and if TRUE, test against the desired
2303 * rectangle.
2304 * If the function does not have all the necessary information
2305 * to computed the requested rectangles, will crash with a
2306 * failed assertion. This is done so we catch all programming
2307 * errors, given that the function is called only from our code.
2309 * We have the following 'special' meanings for a few fields:
2310 * * If LVIS_FOCUSED is set, we assume the item has the focus
2311 * This is important in ICON mode, where it might get a larger
2312 * then usual rectangle
2314 * Please note that subitem support works only in REPORT mode.
2316 * PARAMETER(S):
2317 * [I] infoPtr : valid pointer to the listview structure
2318 * [I] lpLVItem : item to compute the measures for
2319 * [O] lprcBox : ptr to Box rectangle
2320 * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2321 * [0] lprcSelectBox : ptr to select box rectangle
2322 * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2323 * [O] lprcIcon : ptr to Icon rectangle
2324 * Same as LVM_GETITEMRECT with LVIR_ICON
2325 * [O] lprcStateIcon: ptr to State Icon rectangle
2326 * [O] lprcLabel : ptr to Label rectangle
2327 * Same as LVM_GETITEMRECT with LVIR_LABEL
2329 * RETURN:
2330 * None.
2332 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2333 LPRECT lprcBox, LPRECT lprcSelectBox,
2334 LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2336 BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2337 RECT Box, SelectBox, Icon, Label;
2338 COLUMN_INFO *lpColumnInfo = NULL;
2339 SIZE labelSize = { 0, 0 };
2341 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2343 /* Be smart and try to figure out the minimum we have to do */
2344 if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2345 if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2347 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2348 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2350 if (lprcSelectBox) doSelectBox = TRUE;
2351 if (lprcLabel) doLabel = TRUE;
2352 if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2353 if (doSelectBox)
2355 doIcon = TRUE;
2356 doLabel = TRUE;
2359 /************************************************************/
2360 /* compute the box rectangle (it should be cheap to do) */
2361 /************************************************************/
2362 if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2363 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2365 if (lpLVItem->iSubItem)
2367 Box = lpColumnInfo->rcHeader;
2369 else
2371 Box.left = 0;
2372 Box.right = infoPtr->nItemWidth;
2374 Box.top = 0;
2375 Box.bottom = infoPtr->nItemHeight;
2377 /******************************************************************/
2378 /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2379 /******************************************************************/
2380 if (doIcon)
2382 LONG state_width = 0;
2384 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2385 state_width = infoPtr->iconStateSize.cx;
2387 if (infoPtr->uView == LV_VIEW_ICON)
2389 Icon.left = Box.left + state_width;
2390 if (infoPtr->himlNormal)
2391 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2392 Icon.top = Box.top + ICON_TOP_PADDING;
2393 Icon.right = Icon.left;
2394 Icon.bottom = Icon.top;
2395 if (infoPtr->himlNormal)
2397 Icon.right += infoPtr->iconSize.cx;
2398 Icon.bottom += infoPtr->iconSize.cy;
2401 else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2403 Icon.left = Box.left + state_width;
2405 if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2407 /* we need the indent in report mode */
2408 assert(lpLVItem->mask & LVIF_INDENT);
2409 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2412 Icon.top = Box.top;
2413 Icon.right = Icon.left;
2414 if (infoPtr->himlSmall &&
2415 (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2416 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2417 Icon.right += infoPtr->iconSize.cx;
2418 Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2420 if(lprcIcon) *lprcIcon = Icon;
2421 TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2423 /* TODO: is this correct? */
2424 if (lprcStateIcon)
2426 lprcStateIcon->left = Icon.left - state_width;
2427 lprcStateIcon->right = Icon.left;
2428 lprcStateIcon->top = Icon.top;
2429 lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2430 TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2433 else Icon.right = 0;
2435 /************************************************************/
2436 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2437 /************************************************************/
2438 if (doLabel)
2440 /* calculate how far to the right can the label stretch */
2441 Label.right = Box.right;
2442 if (infoPtr->uView == LV_VIEW_DETAILS)
2444 if (lpLVItem->iSubItem == 0)
2446 /* we need a zero based rect here */
2447 Label = lpColumnInfo->rcHeader;
2448 OffsetRect(&Label, -Label.left, 0);
2452 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2454 labelSize.cx = infoPtr->nItemWidth;
2455 labelSize.cy = infoPtr->nItemHeight;
2456 goto calc_label;
2459 /* we need the text in non owner draw mode */
2460 assert(lpLVItem->mask & LVIF_TEXT);
2461 if (is_text(lpLVItem->pszText))
2463 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2464 HDC hdc = GetDC(infoPtr->hwndSelf);
2465 HFONT hOldFont = SelectObject(hdc, hFont);
2466 UINT uFormat;
2467 RECT rcText;
2469 /* compute rough rectangle where the label will go */
2470 SetRectEmpty(&rcText);
2471 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2472 rcText.bottom = infoPtr->nItemHeight;
2473 if (infoPtr->uView == LV_VIEW_ICON)
2474 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2476 /* now figure out the flags */
2477 if (infoPtr->uView == LV_VIEW_ICON)
2478 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2479 else
2480 uFormat = LV_SL_DT_FLAGS;
2482 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2484 if (rcText.right != rcText.left)
2485 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2487 labelSize.cy = rcText.bottom - rcText.top;
2489 SelectObject(hdc, hOldFont);
2490 ReleaseDC(infoPtr->hwndSelf, hdc);
2493 calc_label:
2494 if (infoPtr->uView == LV_VIEW_ICON)
2496 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2497 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2498 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2499 Label.right = Label.left + labelSize.cx;
2500 Label.bottom = Label.top + infoPtr->nItemHeight;
2501 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2503 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2504 labelSize.cy /= infoPtr->ntmHeight;
2505 labelSize.cy = max(labelSize.cy, 1);
2506 labelSize.cy *= infoPtr->ntmHeight;
2508 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2510 else if (infoPtr->uView == LV_VIEW_DETAILS)
2512 Label.left = Icon.right;
2513 Label.top = Box.top;
2514 Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2515 lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2516 Label.bottom = Label.top + infoPtr->nItemHeight;
2518 else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2520 Label.left = Icon.right;
2521 Label.top = Box.top;
2522 Label.right = min(Label.left + labelSize.cx, Label.right);
2523 Label.bottom = Label.top + infoPtr->nItemHeight;
2526 if (lprcLabel) *lprcLabel = Label;
2527 TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2530 /************************************************************/
2531 /* compute SELECT bounding box */
2532 /************************************************************/
2533 if (doSelectBox)
2535 if (infoPtr->uView == LV_VIEW_DETAILS)
2537 SelectBox.left = Icon.left;
2538 SelectBox.top = Box.top;
2539 SelectBox.bottom = Box.bottom;
2541 if (labelSize.cx)
2542 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2543 else
2544 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2546 else
2548 UnionRect(&SelectBox, &Icon, &Label);
2550 if (lprcSelectBox) *lprcSelectBox = SelectBox;
2551 TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2554 /* Fix the Box if necessary */
2555 if (lprcBox)
2557 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2558 else *lprcBox = Box;
2560 TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2563 /***
2564 * DESCRIPTION: [INTERNAL]
2566 * PARAMETER(S):
2567 * [I] infoPtr : valid pointer to the listview structure
2568 * [I] nItem : item number
2569 * [O] lprcBox : ptr to Box rectangle
2571 * RETURN:
2572 * None.
2574 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2576 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2577 POINT Position, Origin;
2578 LVITEMW lvItem;
2580 LISTVIEW_GetOrigin(infoPtr, &Origin);
2581 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2583 /* Be smart and try to figure out the minimum we have to do */
2584 lvItem.mask = 0;
2585 if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2586 lvItem.mask |= LVIF_TEXT;
2587 lvItem.iItem = nItem;
2588 lvItem.iSubItem = 0;
2589 lvItem.pszText = szDispText;
2590 lvItem.cchTextMax = DISP_TEXT_SIZE;
2591 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2592 if (infoPtr->uView == LV_VIEW_ICON)
2594 lvItem.mask |= LVIF_STATE;
2595 lvItem.stateMask = LVIS_FOCUSED;
2596 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2598 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2600 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2601 SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2603 OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2605 else
2606 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2609 /* LISTVIEW_MapIdToIndex helper */
2610 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2612 ITEM_ID *id1 = (ITEM_ID*)p1;
2613 ITEM_ID *id2 = (ITEM_ID*)p2;
2615 if (id1->id == id2->id) return 0;
2617 return (id1->id < id2->id) ? -1 : 1;
2620 /***
2621 * DESCRIPTION:
2622 * Returns the item index for id specified.
2624 * PARAMETER(S):
2625 * [I] infoPtr : valid pointer to the listview structure
2626 * [I] iID : item id to get index for
2628 * RETURN:
2629 * Item index, or -1 on failure.
2631 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2633 ITEM_ID ID;
2634 INT index;
2636 TRACE("iID=%d\n", iID);
2638 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2639 if (infoPtr->nItemCount == 0) return -1;
2641 ID.id = iID;
2642 index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2644 if (index != -1)
2646 ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2647 return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2650 return -1;
2653 /***
2654 * DESCRIPTION:
2655 * Returns the item id for index given.
2657 * PARAMETER(S):
2658 * [I] infoPtr : valid pointer to the listview structure
2659 * [I] iItem : item index to get id for
2661 * RETURN:
2662 * Item id.
2664 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2666 ITEM_INFO *lpItem;
2667 HDPA hdpaSubItems;
2669 TRACE("iItem=%d\n", iItem);
2671 if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2672 if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2674 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2675 lpItem = DPA_GetPtr(hdpaSubItems, 0);
2677 return lpItem->id->id;
2680 /***
2681 * DESCRIPTION:
2682 * Returns the current icon position, and advances it along the top.
2683 * The returned position is not offset by Origin.
2685 * PARAMETER(S):
2686 * [I] infoPtr : valid pointer to the listview structure
2687 * [O] lpPos : will get the current icon position
2689 * RETURN:
2690 * None
2692 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2694 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2696 *lpPos = infoPtr->currIconPos;
2698 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2699 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2701 infoPtr->currIconPos.x = 0;
2702 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2706 /***
2707 * DESCRIPTION:
2708 * Returns the current icon position, and advances it down the left edge.
2709 * The returned position is not offset by Origin.
2711 * PARAMETER(S):
2712 * [I] infoPtr : valid pointer to the listview structure
2713 * [O] lpPos : will get the current icon position
2715 * RETURN:
2716 * None
2718 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2720 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2722 *lpPos = infoPtr->currIconPos;
2724 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2725 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2727 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2728 infoPtr->currIconPos.y = 0;
2732 /***
2733 * DESCRIPTION:
2734 * Moves an icon to the specified position.
2735 * It takes care of invalidating the item, etc.
2737 * PARAMETER(S):
2738 * [I] infoPtr : valid pointer to the listview structure
2739 * [I] nItem : the item to move
2740 * [I] lpPos : the new icon position
2741 * [I] isNew : flags the item as being new
2743 * RETURN:
2744 * Success: TRUE
2745 * Failure: FALSE
2747 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2749 POINT old;
2751 if (!isNew)
2753 old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2754 old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2756 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2757 LISTVIEW_InvalidateItem(infoPtr, nItem);
2760 /* Allocating a POINTER for every item is too resource intensive,
2761 * so we'll keep the (x,y) in different arrays */
2762 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2763 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2765 LISTVIEW_InvalidateItem(infoPtr, nItem);
2767 return TRUE;
2770 /***
2771 * DESCRIPTION:
2772 * Arranges listview items in icon display mode.
2774 * PARAMETER(S):
2775 * [I] infoPtr : valid pointer to the listview structure
2776 * [I] nAlignCode : alignment code
2778 * RETURN:
2779 * SUCCESS : TRUE
2780 * FAILURE : FALSE
2782 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2784 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2785 POINT pos;
2786 INT i;
2788 if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2790 TRACE("nAlignCode=%d\n", nAlignCode);
2792 if (nAlignCode == LVA_DEFAULT)
2794 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2795 else nAlignCode = LVA_ALIGNTOP;
2798 switch (nAlignCode)
2800 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2801 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2802 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2803 default: return FALSE;
2806 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2807 for (i = 0; i < infoPtr->nItemCount; i++)
2809 next_pos(infoPtr, &pos);
2810 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2813 return TRUE;
2816 /***
2817 * DESCRIPTION:
2818 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2819 * For LVS_REPORT always returns empty rectangle.
2821 * PARAMETER(S):
2822 * [I] infoPtr : valid pointer to the listview structure
2823 * [O] lprcView : bounding rectangle
2825 * RETURN:
2826 * SUCCESS : TRUE
2827 * FAILURE : FALSE
2829 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2831 INT i, x, y;
2833 SetRectEmpty(lprcView);
2835 switch (infoPtr->uView)
2837 case LV_VIEW_ICON:
2838 case LV_VIEW_SMALLICON:
2839 for (i = 0; i < infoPtr->nItemCount; i++)
2841 x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2842 y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2843 lprcView->right = max(lprcView->right, x);
2844 lprcView->bottom = max(lprcView->bottom, y);
2846 if (infoPtr->nItemCount > 0)
2848 lprcView->right += infoPtr->nItemWidth;
2849 lprcView->bottom += infoPtr->nItemHeight;
2851 break;
2853 case LV_VIEW_LIST:
2854 y = LISTVIEW_GetCountPerColumn(infoPtr);
2855 x = infoPtr->nItemCount / y;
2856 if (infoPtr->nItemCount % y) x++;
2857 lprcView->right = x * infoPtr->nItemWidth;
2858 lprcView->bottom = y * infoPtr->nItemHeight;
2859 break;
2863 /***
2864 * DESCRIPTION:
2865 * Retrieves the bounding rectangle of all the items.
2867 * PARAMETER(S):
2868 * [I] infoPtr : valid pointer to the listview structure
2869 * [O] lprcView : bounding rectangle
2871 * RETURN:
2872 * SUCCESS : TRUE
2873 * FAILURE : FALSE
2875 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2877 POINT ptOrigin;
2879 TRACE("(lprcView=%p)\n", lprcView);
2881 if (!lprcView) return FALSE;
2883 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2885 if (infoPtr->uView != LV_VIEW_DETAILS)
2887 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2888 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2891 TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2893 return TRUE;
2896 /***
2897 * DESCRIPTION:
2898 * Retrieves the subitem pointer associated with the subitem index.
2900 * PARAMETER(S):
2901 * [I] hdpaSubItems : DPA handle for a specific item
2902 * [I] nSubItem : index of subitem
2904 * RETURN:
2905 * SUCCESS : subitem pointer
2906 * FAILURE : NULL
2908 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2910 SUBITEM_INFO *lpSubItem;
2911 INT i;
2913 /* we should binary search here if need be */
2914 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2916 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2917 if (lpSubItem->iSubItem == nSubItem)
2918 return lpSubItem;
2921 return NULL;
2925 /***
2926 * DESCRIPTION:
2927 * Calculates the desired item width.
2929 * PARAMETER(S):
2930 * [I] infoPtr : valid pointer to the listview structure
2932 * RETURN:
2933 * The desired item width.
2935 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2937 INT nItemWidth = 0;
2939 TRACE("uView=%d\n", infoPtr->uView);
2941 if (infoPtr->uView == LV_VIEW_ICON)
2942 nItemWidth = infoPtr->iconSpacing.cx;
2943 else if (infoPtr->uView == LV_VIEW_DETAILS)
2945 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2947 RECT rcHeader;
2948 INT index;
2950 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2951 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2953 LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2954 nItemWidth = rcHeader.right;
2957 else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2959 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2960 LVITEMW lvItem;
2961 INT i;
2963 lvItem.mask = LVIF_TEXT;
2964 lvItem.iSubItem = 0;
2966 for (i = 0; i < infoPtr->nItemCount; i++)
2968 lvItem.iItem = i;
2969 lvItem.pszText = szDispText;
2970 lvItem.cchTextMax = DISP_TEXT_SIZE;
2971 if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2972 nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2973 nItemWidth);
2976 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2977 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2979 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2982 return nItemWidth;
2985 /***
2986 * DESCRIPTION:
2987 * Calculates the desired item height.
2989 * PARAMETER(S):
2990 * [I] infoPtr : valid pointer to the listview structure
2992 * RETURN:
2993 * The desired item height.
2995 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2997 INT nItemHeight;
2999 TRACE("uView=%d\n", infoPtr->uView);
3001 if (infoPtr->uView == LV_VIEW_ICON)
3002 nItemHeight = infoPtr->iconSpacing.cy;
3003 else
3005 nItemHeight = infoPtr->ntmHeight;
3006 if (infoPtr->himlState)
3007 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
3008 if (infoPtr->himlSmall)
3009 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
3010 nItemHeight += HEIGHT_PADDING;
3011 if (infoPtr->nMeasureItemHeight > 0)
3012 nItemHeight = infoPtr->nMeasureItemHeight;
3015 return max(nItemHeight, 1);
3018 /***
3019 * DESCRIPTION:
3020 * Updates the width, and height of an item.
3022 * PARAMETER(S):
3023 * [I] infoPtr : valid pointer to the listview structure
3025 * RETURN:
3026 * None.
3028 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
3030 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
3031 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
3035 /***
3036 * DESCRIPTION:
3037 * Retrieves and saves important text metrics info for the current
3038 * Listview font.
3040 * PARAMETER(S):
3041 * [I] infoPtr : valid pointer to the listview structure
3044 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
3046 HDC hdc = GetDC(infoPtr->hwndSelf);
3047 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3048 HFONT hOldFont = SelectObject(hdc, hFont);
3049 TEXTMETRICW tm;
3050 SIZE sz;
3052 if (GetTextMetricsW(hdc, &tm))
3054 infoPtr->ntmHeight = tm.tmHeight;
3055 infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3058 if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3059 infoPtr->nEllipsisWidth = sz.cx;
3061 SelectObject(hdc, hOldFont);
3062 ReleaseDC(infoPtr->hwndSelf, hdc);
3064 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3067 /***
3068 * DESCRIPTION:
3069 * A compare function for ranges
3071 * PARAMETER(S)
3072 * [I] range1 : pointer to range 1;
3073 * [I] range2 : pointer to range 2;
3074 * [I] flags : flags
3076 * RETURNS:
3077 * > 0 : if range 1 > range 2
3078 * < 0 : if range 2 > range 1
3079 * = 0 : if range intersects range 2
3081 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
3083 INT cmp;
3085 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3086 cmp = -1;
3087 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3088 cmp = 1;
3089 else
3090 cmp = 0;
3092 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3094 return cmp;
3097 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3099 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3101 INT i;
3102 RANGE *prev, *curr;
3104 TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3105 assert (ranges);
3106 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3107 ranges_dump(ranges);
3108 if (DPA_GetPtrCount(ranges->hdpa) > 0)
3110 prev = DPA_GetPtr(ranges->hdpa, 0);
3111 assert (prev->lower >= 0 && prev->lower < prev->upper);
3112 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3114 curr = DPA_GetPtr(ranges->hdpa, i);
3115 assert (prev->upper <= curr->lower);
3116 assert (curr->lower < curr->upper);
3117 prev = curr;
3120 TRACE("--- Done checking---\n");
3123 static RANGES ranges_create(int count)
3125 RANGES ranges = Alloc(sizeof(struct tagRANGES));
3126 if (!ranges) return NULL;
3127 ranges->hdpa = DPA_Create(count);
3128 if (ranges->hdpa) return ranges;
3129 Free(ranges);
3130 return NULL;
3133 static void ranges_clear(RANGES ranges)
3135 INT i;
3137 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3138 Free(DPA_GetPtr(ranges->hdpa, i));
3139 DPA_DeleteAllPtrs(ranges->hdpa);
3143 static void ranges_destroy(RANGES ranges)
3145 if (!ranges) return;
3146 ranges_clear(ranges);
3147 DPA_Destroy(ranges->hdpa);
3148 Free(ranges);
3151 static RANGES ranges_clone(RANGES ranges)
3153 RANGES clone;
3154 INT i;
3156 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3158 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3160 RANGE *newrng = Alloc(sizeof(RANGE));
3161 if (!newrng) goto fail;
3162 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3163 if (!DPA_SetPtr(clone->hdpa, i, newrng))
3165 Free(newrng);
3166 goto fail;
3169 return clone;
3171 fail:
3172 TRACE ("clone failed\n");
3173 ranges_destroy(clone);
3174 return NULL;
3177 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3179 INT i;
3181 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3182 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3184 return ranges;
3187 static void ranges_dump(RANGES ranges)
3189 INT i;
3191 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3192 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3195 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3197 RANGE srchrng = { nItem, nItem + 1 };
3199 TRACE("(nItem=%d)\n", nItem);
3200 ranges_check(ranges, "before contain");
3201 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3204 static INT ranges_itemcount(RANGES ranges)
3206 INT i, count = 0;
3208 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3210 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3211 count += sel->upper - sel->lower;
3214 return count;
3217 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3219 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3220 INT index;
3222 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3223 if (index == -1) return TRUE;
3225 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3227 chkrng = DPA_GetPtr(ranges->hdpa, index);
3228 if (chkrng->lower >= nItem)
3229 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3230 if (chkrng->upper > nItem)
3231 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3233 return TRUE;
3236 static BOOL ranges_add(RANGES ranges, RANGE range)
3238 RANGE srchrgn;
3239 INT index;
3241 TRACE("(%s)\n", debugrange(&range));
3242 ranges_check(ranges, "before add");
3244 /* try find overlapping regions first */
3245 srchrgn.lower = range.lower - 1;
3246 srchrgn.upper = range.upper + 1;
3247 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3249 if (index == -1)
3251 RANGE *newrgn;
3253 TRACE("Adding new range\n");
3255 /* create the brand new range to insert */
3256 newrgn = Alloc(sizeof(RANGE));
3257 if(!newrgn) goto fail;
3258 *newrgn = range;
3260 /* figure out where to insert it */
3261 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3262 TRACE("index=%d\n", index);
3263 if (index == -1) index = 0;
3265 /* and get it over with */
3266 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3268 Free(newrgn);
3269 goto fail;
3272 else
3274 RANGE *chkrgn, *mrgrgn;
3275 INT fromindex, mergeindex;
3277 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3278 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3280 chkrgn->lower = min(range.lower, chkrgn->lower);
3281 chkrgn->upper = max(range.upper, chkrgn->upper);
3283 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3285 /* merge now common ranges */
3286 fromindex = 0;
3287 srchrgn.lower = chkrgn->lower - 1;
3288 srchrgn.upper = chkrgn->upper + 1;
3292 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3293 if (mergeindex == -1) break;
3294 if (mergeindex == index)
3296 fromindex = index + 1;
3297 continue;
3300 TRACE("Merge with index %i\n", mergeindex);
3302 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3303 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3304 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3305 Free(mrgrgn);
3306 DPA_DeletePtr(ranges->hdpa, mergeindex);
3307 if (mergeindex < index) index --;
3308 } while(1);
3311 ranges_check(ranges, "after add");
3312 return TRUE;
3314 fail:
3315 ranges_check(ranges, "failed add");
3316 return FALSE;
3319 static BOOL ranges_del(RANGES ranges, RANGE range)
3321 RANGE *chkrgn;
3322 INT index;
3324 TRACE("(%s)\n", debugrange(&range));
3325 ranges_check(ranges, "before del");
3327 /* we don't use DPAS_SORTED here, since we need *
3328 * to find the first overlapping range */
3329 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3330 while(index != -1)
3332 chkrgn = DPA_GetPtr(ranges->hdpa, index);
3334 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3336 /* case 1: Same range */
3337 if ( (chkrgn->upper == range.upper) &&
3338 (chkrgn->lower == range.lower) )
3340 DPA_DeletePtr(ranges->hdpa, index);
3341 Free(chkrgn);
3342 break;
3344 /* case 2: engulf */
3345 else if ( (chkrgn->upper <= range.upper) &&
3346 (chkrgn->lower >= range.lower) )
3348 DPA_DeletePtr(ranges->hdpa, index);
3349 Free(chkrgn);
3351 /* case 3: overlap upper */
3352 else if ( (chkrgn->upper <= range.upper) &&
3353 (chkrgn->lower < range.lower) )
3355 chkrgn->upper = range.lower;
3357 /* case 4: overlap lower */
3358 else if ( (chkrgn->upper > range.upper) &&
3359 (chkrgn->lower >= range.lower) )
3361 chkrgn->lower = range.upper;
3362 break;
3364 /* case 5: fully internal */
3365 else
3367 RANGE *newrgn;
3369 if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3370 newrgn->lower = chkrgn->lower;
3371 newrgn->upper = range.lower;
3372 chkrgn->lower = range.upper;
3373 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3375 Free(newrgn);
3376 goto fail;
3378 break;
3381 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3384 ranges_check(ranges, "after del");
3385 return TRUE;
3387 fail:
3388 ranges_check(ranges, "failed del");
3389 return FALSE;
3392 /***
3393 * DESCRIPTION:
3394 * Removes all selection ranges
3396 * Parameters(s):
3397 * [I] infoPtr : valid pointer to the listview structure
3398 * [I] toSkip : item range to skip removing the selection
3400 * RETURNS:
3401 * SUCCESS : TRUE
3402 * FAILURE : FALSE
3404 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3406 LVITEMW lvItem;
3407 ITERATOR i;
3408 RANGES clone;
3410 TRACE("()\n");
3412 lvItem.state = 0;
3413 lvItem.stateMask = LVIS_SELECTED;
3415 /* need to clone the DPA because callbacks can change it */
3416 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3417 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3418 while(iterator_next(&i))
3419 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3420 /* note that the iterator destructor will free the cloned range */
3421 iterator_destroy(&i);
3423 return TRUE;
3426 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3428 RANGES toSkip;
3430 if (!(toSkip = ranges_create(1))) return FALSE;
3431 if (nItem != -1) ranges_additem(toSkip, nItem);
3432 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3433 ranges_destroy(toSkip);
3434 return TRUE;
3437 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3439 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3442 /***
3443 * DESCRIPTION:
3444 * Retrieves the number of items that are marked as selected.
3446 * PARAMETER(S):
3447 * [I] infoPtr : valid pointer to the listview structure
3449 * RETURN:
3450 * Number of items selected.
3452 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3454 INT nSelectedCount = 0;
3456 if (infoPtr->uCallbackMask & LVIS_SELECTED)
3458 INT i;
3459 for (i = 0; i < infoPtr->nItemCount; i++)
3461 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3462 nSelectedCount++;
3465 else
3466 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3468 TRACE("nSelectedCount=%d\n", nSelectedCount);
3469 return nSelectedCount;
3472 /***
3473 * DESCRIPTION:
3474 * Manages the item focus.
3476 * PARAMETER(S):
3477 * [I] infoPtr : valid pointer to the listview structure
3478 * [I] nItem : item index
3480 * RETURN:
3481 * TRUE : focused item changed
3482 * FALSE : focused item has NOT changed
3484 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3486 INT oldFocus = infoPtr->nFocusedItem;
3487 LVITEMW lvItem;
3489 if (nItem == infoPtr->nFocusedItem) return FALSE;
3491 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3492 lvItem.stateMask = LVIS_FOCUSED;
3493 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3495 return oldFocus != infoPtr->nFocusedItem;
3498 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3500 if (nShiftItem < nItem) return nShiftItem;
3502 if (nShiftItem > nItem) return nShiftItem + direction;
3504 if (direction > 0) return nShiftItem + direction;
3506 return min(nShiftItem, infoPtr->nItemCount - 1);
3509 /* This function updates focus index.
3511 Parameters:
3512 focus : current focus index
3513 item : index of item to be added/removed
3514 direction : add/remove flag
3516 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3518 BOOL old_change = infoPtr->bDoChangeNotify;
3520 infoPtr->bDoChangeNotify = FALSE;
3521 focus = shift_item(infoPtr, focus, item, direction);
3522 if (focus != infoPtr->nFocusedItem)
3523 LISTVIEW_SetItemFocus(infoPtr, focus);
3524 infoPtr->bDoChangeNotify = old_change;
3528 * DESCRIPTION:
3529 * Updates the various indices after an item has been inserted or deleted.
3531 * PARAMETER(S):
3532 * [I] infoPtr : valid pointer to the listview structure
3533 * [I] nItem : item index
3534 * [I] direction : Direction of shift, +1 or -1.
3536 * RETURN:
3537 * None
3539 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3541 TRACE("Shifting %i, %i steps\n", nItem, direction);
3543 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3544 assert(abs(direction) == 1);
3545 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3547 /* But we are not supposed to modify nHotItem! */
3551 * DESCRIPTION:
3552 * Adds a block of selections.
3554 * PARAMETER(S):
3555 * [I] infoPtr : valid pointer to the listview structure
3556 * [I] nItem : item index
3558 * RETURN:
3559 * Whether the window is still valid.
3561 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3563 INT nFirst = min(infoPtr->nSelectionMark, nItem);
3564 INT nLast = max(infoPtr->nSelectionMark, nItem);
3565 HWND hwndSelf = infoPtr->hwndSelf;
3566 NMLVODSTATECHANGE nmlv;
3567 LVITEMW item;
3568 BOOL bOldChange;
3569 INT i;
3571 /* Temporarily disable change notification
3572 * If the control is LVS_OWNERDATA, we need to send
3573 * only one LVN_ODSTATECHANGED notification.
3574 * See MSDN documentation for LVN_ITEMCHANGED.
3576 bOldChange = infoPtr->bDoChangeNotify;
3577 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3579 if (nFirst == -1) nFirst = nItem;
3581 item.state = LVIS_SELECTED;
3582 item.stateMask = LVIS_SELECTED;
3584 for (i = nFirst; i <= nLast; i++)
3585 LISTVIEW_SetItemState(infoPtr,i,&item);
3587 ZeroMemory(&nmlv, sizeof(nmlv));
3588 nmlv.iFrom = nFirst;
3589 nmlv.iTo = nLast;
3590 nmlv.uOldState = 0;
3591 nmlv.uNewState = item.state;
3593 notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3594 if (!IsWindow(hwndSelf))
3595 return FALSE;
3596 infoPtr->bDoChangeNotify = bOldChange;
3597 return TRUE;
3601 /***
3602 * DESCRIPTION:
3603 * Sets a single group selection.
3605 * PARAMETER(S):
3606 * [I] infoPtr : valid pointer to the listview structure
3607 * [I] nItem : item index
3609 * RETURN:
3610 * None
3612 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3614 RANGES selection;
3615 LVITEMW item;
3616 ITERATOR i;
3617 BOOL bOldChange;
3619 if (!(selection = ranges_create(100))) return;
3621 item.state = LVIS_SELECTED;
3622 item.stateMask = LVIS_SELECTED;
3624 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3626 if (infoPtr->nSelectionMark == -1)
3628 infoPtr->nSelectionMark = nItem;
3629 ranges_additem(selection, nItem);
3631 else
3633 RANGE sel;
3635 sel.lower = min(infoPtr->nSelectionMark, nItem);
3636 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3637 ranges_add(selection, sel);
3640 else
3642 RECT rcItem, rcSel, rcSelMark;
3643 POINT ptItem;
3645 rcItem.left = LVIR_BOUNDS;
3646 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3647 ranges_destroy (selection);
3648 return;
3650 rcSelMark.left = LVIR_BOUNDS;
3651 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3652 ranges_destroy (selection);
3653 return;
3655 UnionRect(&rcSel, &rcItem, &rcSelMark);
3656 iterator_frameditems(&i, infoPtr, &rcSel);
3657 while(iterator_next(&i))
3659 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3660 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3662 iterator_destroy(&i);
3665 /* disable per item notifications on LVS_OWNERDATA style
3666 FIXME: single LVN_ODSTATECHANGED should be used */
3667 bOldChange = infoPtr->bDoChangeNotify;
3668 if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3670 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3673 iterator_rangesitems(&i, selection);
3674 while(iterator_next(&i))
3675 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3676 /* this will also destroy the selection */
3677 iterator_destroy(&i);
3679 infoPtr->bDoChangeNotify = bOldChange;
3681 LISTVIEW_SetItemFocus(infoPtr, nItem);
3684 /***
3685 * DESCRIPTION:
3686 * Sets a single selection.
3688 * PARAMETER(S):
3689 * [I] infoPtr : valid pointer to the listview structure
3690 * [I] nItem : item index
3692 * RETURN:
3693 * None
3695 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3697 LVITEMW lvItem;
3699 TRACE("nItem=%d\n", nItem);
3701 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3703 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3704 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3705 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3707 infoPtr->nSelectionMark = nItem;
3710 /***
3711 * DESCRIPTION:
3712 * Set selection(s) with keyboard.
3714 * PARAMETER(S):
3715 * [I] infoPtr : valid pointer to the listview structure
3716 * [I] nItem : item index
3717 * [I] space : VK_SPACE code sent
3719 * RETURN:
3720 * SUCCESS : TRUE (needs to be repainted)
3721 * FAILURE : FALSE (nothing has changed)
3723 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem, BOOL space)
3725 /* FIXME: pass in the state */
3726 WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3727 WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3728 BOOL bResult = FALSE;
3730 TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3731 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3733 bResult = TRUE;
3735 if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3736 LISTVIEW_SetSelection(infoPtr, nItem);
3737 else
3739 if (wShift)
3740 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3741 else if (wCtrl)
3743 LVITEMW lvItem;
3744 lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3745 lvItem.stateMask = LVIS_SELECTED;
3746 if (space)
3748 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3749 if (lvItem.state & LVIS_SELECTED)
3750 infoPtr->nSelectionMark = nItem;
3752 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3755 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3758 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3759 return bResult;
3762 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3764 LVHITTESTINFO lvHitTestInfo;
3766 ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3767 lvHitTestInfo.pt.x = pt.x;
3768 lvHitTestInfo.pt.y = pt.y;
3770 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3772 lpLVItem->mask = LVIF_PARAM;
3773 lpLVItem->iItem = lvHitTestInfo.iItem;
3774 lpLVItem->iSubItem = 0;
3776 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3779 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3781 return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3782 (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3783 (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3786 /***
3787 * DESCRIPTION:
3788 * Called when the mouse is being actively tracked and has hovered for a specified
3789 * amount of time
3791 * PARAMETER(S):
3792 * [I] infoPtr : valid pointer to the listview structure
3793 * [I] fwKeys : key indicator
3794 * [I] x,y : mouse position
3796 * RETURN:
3797 * 0 if the message was processed, non-zero if there was an error
3799 * INFO:
3800 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3801 * over the item for a certain period of time.
3804 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, INT x, INT y)
3806 NMHDR hdr;
3808 if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3810 if (LISTVIEW_IsHotTracking(infoPtr))
3812 LVITEMW item;
3813 POINT pt;
3815 pt.x = x;
3816 pt.y = y;
3818 if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3819 LISTVIEW_SetSelection(infoPtr, item.iItem);
3821 SetFocus(infoPtr->hwndSelf);
3824 return 0;
3827 #define SCROLL_LEFT 0x1
3828 #define SCROLL_RIGHT 0x2
3829 #define SCROLL_UP 0x4
3830 #define SCROLL_DOWN 0x8
3832 /***
3833 * DESCRIPTION:
3834 * Utility routine to draw and highlight items within a marquee selection rectangle.
3836 * PARAMETER(S):
3837 * [I] infoPtr : valid pointer to the listview structure
3838 * [I] coords_orig : original co-ordinates of the cursor
3839 * [I] coords_offs : offsetted coordinates of the cursor
3840 * [I] offset : offset amount
3841 * [I] scroll : Bitmask of which directions we should scroll, if at all
3843 * RETURN:
3844 * None.
3846 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3847 const POINT *coords_offs, const POINT *offset,
3848 INT scroll)
3850 BOOL controlDown = FALSE;
3851 LVITEMW item;
3852 ITERATOR old_elems, new_elems;
3853 RECT rect;
3855 if (coords_offs->x > infoPtr->marqueeOrigin.x)
3857 rect.left = infoPtr->marqueeOrigin.x;
3858 rect.right = coords_offs->x;
3860 else
3862 rect.left = coords_offs->x;
3863 rect.right = infoPtr->marqueeOrigin.x;
3866 if (coords_offs->y > infoPtr->marqueeOrigin.y)
3868 rect.top = infoPtr->marqueeOrigin.y;
3869 rect.bottom = coords_offs->y;
3871 else
3873 rect.top = coords_offs->y;
3874 rect.bottom = infoPtr->marqueeOrigin.y;
3877 /* Cancel out the old marquee rectangle and draw the new one */
3878 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3880 /* Scroll by the appropriate distance if applicable - speed up scrolling as
3881 the cursor is further away */
3883 if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3884 LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3886 if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3887 LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3889 if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3890 LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3892 if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3893 LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3895 iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3897 infoPtr->marqueeRect = rect;
3898 infoPtr->marqueeDrawRect = rect;
3899 OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3901 iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3902 iterator_remove_common_items(&old_elems, &new_elems);
3904 /* Iterate over no longer selected items */
3905 while (iterator_next(&old_elems))
3907 if (old_elems.nItem > -1)
3909 if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3910 item.state = 0;
3911 else
3912 item.state = LVIS_SELECTED;
3914 item.stateMask = LVIS_SELECTED;
3916 LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3919 iterator_destroy(&old_elems);
3922 /* Iterate over newly selected items */
3923 if (GetKeyState(VK_CONTROL) & 0x8000)
3924 controlDown = TRUE;
3926 while (iterator_next(&new_elems))
3928 if (new_elems.nItem > -1)
3930 /* If CTRL is pressed, invert. If not, always select the item. */
3931 if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3932 item.state = 0;
3933 else
3934 item.state = LVIS_SELECTED;
3936 item.stateMask = LVIS_SELECTED;
3938 LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3941 iterator_destroy(&new_elems);
3943 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3946 /***
3947 * DESCRIPTION:
3948 * Called when we are in a marquee selection that involves scrolling the listview (ie,
3949 * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3951 * PARAMETER(S):
3952 * [I] hwnd : Handle to the listview
3953 * [I] uMsg : WM_TIMER (ignored)
3954 * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3955 * [I] dwTimer : The elapsed time (ignored)
3957 * RETURN:
3958 * None.
3960 static VOID CALLBACK LISTVIEW_ScrollTimer(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
3962 LISTVIEW_INFO *infoPtr;
3963 SCROLLINFO scrollInfo;
3964 POINT coords_orig;
3965 POINT coords_offs;
3966 POINT offset;
3967 INT scroll = 0;
3969 infoPtr = (LISTVIEW_INFO *) idEvent;
3971 if (!infoPtr)
3972 return;
3974 /* Get the current cursor position and convert to client coordinates */
3975 GetCursorPos(&coords_orig);
3976 ScreenToClient(hWnd, &coords_orig);
3978 /* Ensure coordinates are within client bounds */
3979 coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3980 coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3982 /* Get offset */
3983 LISTVIEW_GetOrigin(infoPtr, &offset);
3985 /* Offset coordinates by the appropriate amount */
3986 coords_offs.x -= offset.x;
3987 coords_offs.y -= offset.y;
3989 scrollInfo.cbSize = sizeof(SCROLLINFO);
3990 scrollInfo.fMask = SIF_ALL;
3992 /* Work out in which directions we can scroll */
3993 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3995 if (scrollInfo.nPos != scrollInfo.nMin)
3996 scroll |= SCROLL_UP;
3998 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3999 scroll |= SCROLL_DOWN;
4002 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4004 if (scrollInfo.nPos != scrollInfo.nMin)
4005 scroll |= SCROLL_LEFT;
4007 if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
4008 scroll |= SCROLL_RIGHT;
4011 if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
4012 ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
4013 ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
4014 ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
4016 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
4020 /***
4021 * DESCRIPTION:
4022 * Called whenever WM_MOUSEMOVE is received.
4024 * PARAMETER(S):
4025 * [I] infoPtr : valid pointer to the listview structure
4026 * [I] fwKeys : key indicator
4027 * [I] x,y : mouse position
4029 * RETURN:
4030 * 0 if the message is processed, non-zero if there was an error
4032 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, INT x, INT y)
4034 LVHITTESTINFO ht;
4035 RECT rect;
4036 POINT pt;
4038 if (!(fwKeys & MK_LBUTTON))
4039 infoPtr->bLButtonDown = FALSE;
4041 if (infoPtr->bLButtonDown)
4043 rect.left = rect.right = infoPtr->ptClickPos.x;
4044 rect.top = rect.bottom = infoPtr->ptClickPos.y;
4046 InflateRect(&rect, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
4048 if (infoPtr->bMarqueeSelect)
4050 POINT coords_orig;
4051 POINT coords_offs;
4052 POINT offset;
4054 coords_orig.x = x;
4055 coords_orig.y = y;
4057 /* Get offset */
4058 LISTVIEW_GetOrigin(infoPtr, &offset);
4060 /* Ensure coordinates are within client bounds */
4061 coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4062 coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4064 /* Offset coordinates by the appropriate amount */
4065 coords_offs.x -= offset.x;
4066 coords_offs.y -= offset.y;
4068 /* Enable the timer if we're going outside our bounds, in case the user doesn't
4069 move the mouse again */
4071 if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4072 (y >= infoPtr->rcList.bottom))
4074 if (!infoPtr->bScrolling)
4076 infoPtr->bScrolling = TRUE;
4077 SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4080 else
4082 infoPtr->bScrolling = FALSE;
4083 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4086 LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4087 return 0;
4090 pt.x = x;
4091 pt.y = y;
4093 ht.pt = pt;
4094 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4096 /* reset item marker */
4097 if (infoPtr->nLButtonDownItem != ht.iItem)
4098 infoPtr->nLButtonDownItem = -1;
4100 if (!PtInRect(&rect, pt))
4102 /* this path covers the following:
4103 1. WM_LBUTTONDOWN over selected item (sets focus on it)
4104 2. change focus with keys
4105 3. move mouse over item from step 1 selects it and moves focus on it */
4106 if (infoPtr->nLButtonDownItem != -1 &&
4107 !LISTVIEW_GetItemState(infoPtr, infoPtr->nLButtonDownItem, LVIS_SELECTED))
4109 LVITEMW lvItem;
4111 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4112 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
4114 LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4115 infoPtr->nLButtonDownItem = -1;
4118 if (!infoPtr->bDragging)
4120 ht.pt = infoPtr->ptClickPos;
4121 LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4123 /* If the click is outside the range of an item, begin a
4124 highlight. If not, begin an item drag. */
4125 if (ht.iItem == -1)
4127 NMHDR hdr;
4129 /* If we're allowing multiple selections, send notification.
4130 If return value is non-zero, cancel. */
4131 if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4133 /* Store the absolute coordinates of the click */
4134 POINT offset;
4135 LISTVIEW_GetOrigin(infoPtr, &offset);
4137 infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4138 infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4140 /* Begin selection and capture mouse */
4141 infoPtr->bMarqueeSelect = TRUE;
4142 SetCapture(infoPtr->hwndSelf);
4145 else
4147 NMLISTVIEW nmlv;
4149 ZeroMemory(&nmlv, sizeof(nmlv));
4150 nmlv.iItem = ht.iItem;
4151 nmlv.ptAction = infoPtr->ptClickPos;
4153 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4154 infoPtr->bDragging = TRUE;
4158 return 0;
4162 /* see if we are supposed to be tracking mouse hovering */
4163 if (LISTVIEW_IsHotTracking(infoPtr)) {
4164 TRACKMOUSEEVENT trackinfo;
4165 DWORD flags;
4167 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4168 trackinfo.dwFlags = TME_QUERY;
4170 /* see if we are already tracking this hwnd */
4171 _TrackMouseEvent(&trackinfo);
4173 flags = TME_LEAVE;
4174 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4175 flags |= TME_HOVER;
4177 if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4178 trackinfo.dwFlags = flags;
4179 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4180 trackinfo.hwndTrack = infoPtr->hwndSelf;
4182 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4183 _TrackMouseEvent(&trackinfo);
4187 return 0;
4191 /***
4192 * Tests whether the item is assignable to a list with style lStyle
4194 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4196 if ( (lpLVItem->mask & LVIF_TEXT) &&
4197 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4198 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4200 return TRUE;
4204 /***
4205 * DESCRIPTION:
4206 * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4208 * PARAMETER(S):
4209 * [I] infoPtr : valid pointer to the listview structure
4210 * [I] lpLVItem : valid pointer to new item attributes
4211 * [I] isNew : the item being set is being inserted
4212 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4213 * [O] bChanged : will be set to TRUE if the item really changed
4215 * RETURN:
4216 * SUCCESS : TRUE
4217 * FAILURE : FALSE
4219 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4221 ITEM_INFO *lpItem;
4222 NMLISTVIEW nmlv;
4223 UINT uChanged = 0;
4224 LVITEMW item;
4225 /* stateMask is ignored for LVM_INSERTITEM */
4226 UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4228 TRACE("()\n");
4230 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4232 if (lpLVItem->mask == 0) return TRUE;
4234 if (infoPtr->dwStyle & LVS_OWNERDATA)
4236 /* a virtual listview only stores selection and focus */
4237 if (lpLVItem->mask & ~LVIF_STATE)
4238 return FALSE;
4239 lpItem = NULL;
4241 else
4243 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4244 lpItem = DPA_GetPtr(hdpaSubItems, 0);
4245 assert (lpItem);
4248 /* we need to get the lParam and state of the item */
4249 item.iItem = lpLVItem->iItem;
4250 item.iSubItem = lpLVItem->iSubItem;
4251 item.mask = LVIF_STATE | LVIF_PARAM;
4252 item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4254 item.state = 0;
4255 item.lParam = 0;
4256 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4258 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4259 /* determine what fields will change */
4260 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4261 uChanged |= LVIF_STATE;
4263 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4264 uChanged |= LVIF_IMAGE;
4266 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4267 uChanged |= LVIF_PARAM;
4269 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4270 uChanged |= LVIF_INDENT;
4272 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4273 uChanged |= LVIF_TEXT;
4275 TRACE("change mask=0x%x\n", uChanged);
4277 memset(&nmlv, 0, sizeof(NMLISTVIEW));
4278 nmlv.iItem = lpLVItem->iItem;
4279 nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4280 nmlv.uOldState = item.state;
4281 nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4282 nmlv.lParam = item.lParam;
4284 /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4285 and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4286 are enabled. Even nothing really changed we still need to send this,
4287 in this case uChanged mask is just set to passed item mask. */
4288 if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4290 HWND hwndSelf = infoPtr->hwndSelf;
4292 if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4293 return FALSE;
4294 if (!IsWindow(hwndSelf))
4295 return FALSE;
4298 /* When item is inserted we need to shift existing focus index if new item has lower index. */
4299 if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4300 /* this means we won't hit a focus change path later */
4301 ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4303 if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4304 infoPtr->nFocusedItem++;
4307 if (!uChanged) return TRUE;
4308 *bChanged = TRUE;
4310 /* copy information */
4311 if (lpLVItem->mask & LVIF_TEXT)
4312 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4314 if (lpLVItem->mask & LVIF_IMAGE)
4315 lpItem->hdr.iImage = lpLVItem->iImage;
4317 if (lpLVItem->mask & LVIF_PARAM)
4318 lpItem->lParam = lpLVItem->lParam;
4320 if (lpLVItem->mask & LVIF_INDENT)
4321 lpItem->iIndent = lpLVItem->iIndent;
4323 if (uChanged & LVIF_STATE)
4325 if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4327 lpItem->state &= ~stateMask;
4328 lpItem->state |= (lpLVItem->state & stateMask);
4330 if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4332 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4333 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4335 else if (stateMask & LVIS_SELECTED)
4337 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4339 /* If we are asked to change focus, and we manage it, do it.
4340 It's important to have all new item data stored at this point,
4341 because changing existing focus could result in a redrawing operation,
4342 which in turn could ask for disp data, application should see all data
4343 for inserted item when processing LVN_GETDISPINFO.
4345 The way this works application will see nested item change notifications -
4346 changed item notifications interrupted by ones from item losing focus. */
4347 if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4349 if (lpLVItem->state & LVIS_FOCUSED)
4351 /* update selection mark */
4352 if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4353 infoPtr->nSelectionMark = lpLVItem->iItem;
4355 if (infoPtr->nFocusedItem != -1)
4357 /* remove current focus */
4358 item.mask = LVIF_STATE;
4359 item.state = 0;
4360 item.stateMask = LVIS_FOCUSED;
4362 /* recurse with redrawing an item */
4363 LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4366 infoPtr->nFocusedItem = lpLVItem->iItem;
4367 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4369 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4371 infoPtr->nFocusedItem = -1;
4376 /* if we're inserting the item, we're done */
4377 if (isNew) return TRUE;
4379 /* send LVN_ITEMCHANGED notification */
4380 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4381 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4383 return TRUE;
4386 /***
4387 * DESCRIPTION:
4388 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4390 * PARAMETER(S):
4391 * [I] infoPtr : valid pointer to the listview structure
4392 * [I] lpLVItem : valid pointer to new subitem attributes
4393 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4394 * [O] bChanged : will be set to TRUE if the item really changed
4396 * RETURN:
4397 * SUCCESS : TRUE
4398 * FAILURE : FALSE
4400 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4402 HDPA hdpaSubItems;
4403 SUBITEM_INFO *lpSubItem;
4405 /* we do not support subitems for virtual listviews */
4406 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4408 /* set subitem only if column is present */
4409 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4411 /* First do some sanity checks */
4412 /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4413 particularly useful. We currently do not actually do anything with
4414 the flag on subitems.
4416 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4417 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4419 /* get the subitem structure, and create it if not there */
4420 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4421 assert (hdpaSubItems);
4423 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4424 if (!lpSubItem)
4426 SUBITEM_INFO *tmpSubItem;
4427 INT i;
4429 lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4430 if (!lpSubItem) return FALSE;
4431 /* we could binary search here, if need be...*/
4432 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4434 tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4435 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4437 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4439 Free(lpSubItem);
4440 return FALSE;
4442 lpSubItem->iSubItem = lpLVItem->iSubItem;
4443 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4444 *bChanged = TRUE;
4447 if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4449 lpSubItem->hdr.iImage = lpLVItem->iImage;
4450 *bChanged = TRUE;
4453 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4455 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4456 *bChanged = TRUE;
4459 return TRUE;
4462 /***
4463 * DESCRIPTION:
4464 * Sets item attributes.
4466 * PARAMETER(S):
4467 * [I] infoPtr : valid pointer to the listview structure
4468 * [I] lpLVItem : new item attributes
4469 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4471 * RETURN:
4472 * SUCCESS : TRUE
4473 * FAILURE : FALSE
4475 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4477 HWND hwndSelf = infoPtr->hwndSelf;
4478 LPWSTR pszText = NULL;
4479 BOOL bResult, bChanged = FALSE;
4480 RECT oldItemArea;
4482 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4484 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4485 return FALSE;
4487 /* Store old item area */
4488 LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4490 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4491 if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4493 pszText = lpLVItem->pszText;
4494 lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4497 /* actually set the fields */
4498 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4500 if (lpLVItem->iSubItem)
4501 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4502 else
4503 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4504 if (!IsWindow(hwndSelf))
4505 return FALSE;
4507 /* redraw item, if necessary */
4508 if (bChanged && !infoPtr->bIsDrawing)
4510 /* this little optimization eliminates some nasty flicker */
4511 if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4512 !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4513 lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4514 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4515 else
4517 LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4518 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4521 /* restore text */
4522 if (pszText)
4524 textfreeT(lpLVItem->pszText, isW);
4525 lpLVItem->pszText = pszText;
4528 return bResult;
4531 /***
4532 * DESCRIPTION:
4533 * Retrieves the index of the item at coordinate (0, 0) of the client area.
4535 * PARAMETER(S):
4536 * [I] infoPtr : valid pointer to the listview structure
4538 * RETURN:
4539 * item index
4541 static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
4543 INT nItem = 0;
4544 SCROLLINFO scrollInfo;
4546 scrollInfo.cbSize = sizeof(SCROLLINFO);
4547 scrollInfo.fMask = SIF_POS;
4549 if (infoPtr->uView == LV_VIEW_LIST)
4551 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4552 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
4554 else if (infoPtr->uView == LV_VIEW_DETAILS)
4556 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4557 nItem = scrollInfo.nPos;
4559 else
4561 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4562 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
4565 TRACE("nItem=%d\n", nItem);
4567 return nItem;
4571 /***
4572 * DESCRIPTION:
4573 * Erases the background of the given rectangle
4575 * PARAMETER(S):
4576 * [I] infoPtr : valid pointer to the listview structure
4577 * [I] hdc : device context handle
4578 * [I] lprcBox : clipping rectangle
4580 * RETURN:
4581 * Success: TRUE
4582 * Failure: FALSE
4584 static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
4586 if (!infoPtr->hBkBrush) return FALSE;
4588 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
4590 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
4593 /* Draw main item or subitem */
4594 static void LISTVIEW_DrawItemPart(LISTVIEW_INFO *infoPtr, LVITEMW *item, const NMLVCUSTOMDRAW *nmlvcd, const POINT *pos)
4596 RECT rcSelect, rcLabel, rcBox, rcStateIcon, rcIcon;
4597 const RECT *background;
4598 HIMAGELIST himl;
4599 UINT format;
4600 RECT *focus;
4602 /* now check if we need to update the focus rectangle */
4603 focus = infoPtr->bFocus && (item->state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4604 if (!focus) item->state &= ~LVIS_FOCUSED;
4606 LISTVIEW_GetItemMetrics(infoPtr, item, &rcBox, &rcSelect, &rcIcon, &rcStateIcon, &rcLabel);
4607 OffsetRect(&rcBox, pos->x, pos->y);
4608 OffsetRect(&rcSelect, pos->x, pos->y);
4609 OffsetRect(&rcIcon, pos->x, pos->y);
4610 OffsetRect(&rcStateIcon, pos->x, pos->y);
4611 OffsetRect(&rcLabel, pos->x, pos->y);
4612 TRACE("%d: box=%s, select=%s, icon=%s. label=%s\n", item->iSubItem,
4613 wine_dbgstr_rect(&rcBox), wine_dbgstr_rect(&rcSelect),
4614 wine_dbgstr_rect(&rcIcon), wine_dbgstr_rect(&rcLabel));
4616 /* FIXME: temporary hack */
4617 rcSelect.left = rcLabel.left;
4619 if (infoPtr->uView == LV_VIEW_DETAILS && item->iSubItem == 0)
4621 if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4622 OffsetRect(&rcSelect, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4623 OffsetRect(&rcIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4624 OffsetRect(&rcStateIcon, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4625 OffsetRect(&rcLabel, LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left, 0);
4628 /* in icon mode, the label rect is really what we want to draw the
4629 * background for */
4630 /* in detail mode, we want to paint background for label rect when
4631 * item is not selected or listview has full row select; otherwise paint
4632 * background for text only */
4633 if ( infoPtr->uView == LV_VIEW_ICON ||
4634 (infoPtr->uView == LV_VIEW_DETAILS && (!(item->state & LVIS_SELECTED) ||
4635 (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))))
4636 background = &rcLabel;
4637 else
4638 background = &rcSelect;
4640 if (nmlvcd->clrTextBk != CLR_NONE)
4641 ExtTextOutW(nmlvcd->nmcd.hdc, background->left, background->top, ETO_OPAQUE, background, NULL, 0, NULL);
4643 if (item->state & LVIS_FOCUSED)
4645 if (infoPtr->uView == LV_VIEW_DETAILS)
4647 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4649 /* we have to update left focus bound too if item isn't in leftmost column
4650 and reduce right box bound */
4651 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
4653 INT leftmost;
4655 if ((leftmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0)))
4657 INT Originx = pos->x - LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left;
4658 INT rightmost = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
4659 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
4661 rcBox.right = LISTVIEW_GetColumnInfo(infoPtr, rightmost)->rcHeader.right + Originx;
4662 rcSelect.left = LISTVIEW_GetColumnInfo(infoPtr, leftmost)->rcHeader.left + Originx;
4665 rcSelect.right = rcBox.right;
4667 infoPtr->rcFocus = rcSelect;
4669 else
4670 infoPtr->rcFocus = rcLabel;
4673 /* state icons */
4674 if (infoPtr->himlState && STATEIMAGEINDEX(item->state) && (item->iSubItem == 0))
4676 UINT stateimage = STATEIMAGEINDEX(item->state);
4677 if (stateimage)
4679 TRACE("stateimage=%d\n", stateimage);
4680 ImageList_Draw(infoPtr->himlState, stateimage-1, nmlvcd->nmcd.hdc, rcStateIcon.left, rcStateIcon.top, ILD_NORMAL);
4684 /* item icons */
4685 himl = (infoPtr->uView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
4686 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon))
4688 UINT style;
4690 TRACE("iImage=%d\n", item->iImage);
4692 if (item->state & (LVIS_SELECTED | LVIS_CUT) && infoPtr->bFocus)
4693 style = ILD_SELECTED;
4694 else
4695 style = ILD_NORMAL;
4697 ImageList_DrawEx(himl, item->iImage, nmlvcd->nmcd.hdc, rcIcon.left, rcIcon.top,
4698 rcIcon.right - rcIcon.left, rcIcon.bottom - rcIcon.top, infoPtr->clrBk,
4699 item->state & LVIS_CUT ? RGB(255, 255, 255) : CLR_DEFAULT,
4700 style | (item->state & LVIS_OVERLAYMASK));
4703 /* Don't bother painting item being edited */
4704 if (infoPtr->hwndEdit && item->iItem == infoPtr->nEditLabelItem && item->iSubItem == 0) return;
4706 /* figure out the text drawing flags */
4707 format = (infoPtr->uView == LV_VIEW_ICON ? (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
4708 if (infoPtr->uView == LV_VIEW_ICON)
4709 format = (focus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
4710 else if (item->iSubItem)
4712 switch (LISTVIEW_GetColumnInfo(infoPtr, item->iSubItem)->fmt & LVCFMT_JUSTIFYMASK)
4714 case LVCFMT_RIGHT: format |= DT_RIGHT; break;
4715 case LVCFMT_CENTER: format |= DT_CENTER; break;
4716 default: format |= DT_LEFT;
4719 if (!(format & (DT_RIGHT | DT_CENTER)))
4721 if (himl && item->iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
4722 else rcLabel.left += LABEL_HOR_PADDING;
4724 else if (format & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
4726 /* for GRIDLINES reduce the bottom so the text formats correctly */
4727 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
4728 rcLabel.bottom--;
4730 DrawTextW(nmlvcd->nmcd.hdc, item->pszText, -1, &rcLabel, format);
4733 /***
4734 * DESCRIPTION:
4735 * Draws an item.
4737 * PARAMETER(S):
4738 * [I] infoPtr : valid pointer to the listview structure
4739 * [I] hdc : device context handle
4740 * [I] nItem : item index
4741 * [I] nSubItem : subitem index
4742 * [I] pos : item position in client coordinates
4743 * [I] cdmode : custom draw mode
4745 * RETURN:
4746 * Success: TRUE
4747 * Failure: FALSE
4749 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, ITERATOR *subitems, POINT pos, DWORD cdmode)
4751 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4752 static WCHAR callbackW[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
4753 DWORD cdsubitemmode = CDRF_DODEFAULT;
4754 RECT *focus, rcBox;
4755 NMLVCUSTOMDRAW nmlvcd;
4756 LVITEMW lvItem;
4758 TRACE("(hdc=%p, nItem=%d, subitems=%p, pos=%s)\n", hdc, nItem, subitems, wine_dbgstr_point(&pos));
4760 /* get information needed for drawing the item */
4761 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE;
4762 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
4763 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4764 lvItem.iItem = nItem;
4765 lvItem.iSubItem = 0;
4766 lvItem.state = 0;
4767 lvItem.lParam = 0;
4768 lvItem.cchTextMax = DISP_TEXT_SIZE;
4769 lvItem.pszText = szDispText;
4770 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4771 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4772 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4774 /* now check if we need to update the focus rectangle */
4775 focus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
4776 if (!focus) lvItem.state &= ~LVIS_FOCUSED;
4778 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, NULL, NULL, NULL);
4779 OffsetRect(&rcBox, pos.x, pos.y);
4781 /* Full custom draw stage sequence looks like this:
4783 LV_VIEW_DETAILS:
4785 - CDDS_ITEMPREPAINT
4786 - CDDS_ITEMPREPAINT|CDDS_SUBITEM | => sent n times, where n is number of subitems,
4787 CDDS_ITEMPOSTPAINT|CDDS_SUBITEM | including item itself
4788 - CDDS_ITEMPOSTPAINT
4790 other styles:
4792 - CDDS_ITEMPREPAINT
4793 - CDDS_ITEMPOSTPAINT
4796 /* fill in the custom draw structure */
4797 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
4798 if (cdmode & CDRF_NOTIFYITEMDRAW)
4799 cdsubitemmode = notify_customdraw(infoPtr, CDDS_ITEMPREPAINT, &nmlvcd);
4800 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
4802 if (subitems)
4804 while (iterator_next(subitems))
4806 DWORD subitemstage = CDRF_DODEFAULT;
4808 /* We need to query for each subitem, item's data (subitem == 0) is already here at this point */
4809 if (subitems->nItem)
4811 lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_INDENT;
4812 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK | LVIS_CUT | LVIS_OVERLAYMASK;
4813 lvItem.iItem = nItem;
4814 lvItem.iSubItem = subitems->nItem;
4815 lvItem.state = 0;
4816 lvItem.lParam = 0;
4817 lvItem.cchTextMax = DISP_TEXT_SIZE;
4818 lvItem.pszText = szDispText;
4819 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
4820 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4821 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
4822 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = callbackW;
4823 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
4825 /* update custom draw data */
4826 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &nmlvcd.nmcd.rc, NULL, NULL, NULL, NULL);
4827 OffsetRect(&nmlvcd.nmcd.rc, pos.x, pos.y);
4828 nmlvcd.iSubItem = subitems->nItem;
4831 if (cdsubitemmode & CDRF_NOTIFYSUBITEMDRAW)
4832 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
4833 else
4835 nmlvcd.clrTextBk = infoPtr->clrTextBk;
4836 nmlvcd.clrText = infoPtr->clrText;
4839 if (subitems->nItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
4840 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4841 else if (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
4842 prepaint_setup(infoPtr, hdc, &nmlvcd, TRUE);
4844 if (!(subitemstage & CDRF_SKIPDEFAULT))
4845 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4847 if (subitemstage & CDRF_NOTIFYPOSTPAINT)
4848 subitemstage = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPOSTPAINT, &nmlvcd);
4851 else
4853 prepaint_setup(infoPtr, hdc, &nmlvcd, FALSE);
4854 LISTVIEW_DrawItemPart(infoPtr, &lvItem, &nmlvcd, &pos);
4857 postpaint:
4858 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
4860 nmlvcd.iSubItem = 0;
4861 notify_customdraw(infoPtr, CDDS_ITEMPOSTPAINT, &nmlvcd);
4864 return TRUE;
4867 /***
4868 * DESCRIPTION:
4869 * Draws listview items when in owner draw mode.
4871 * PARAMETER(S):
4872 * [I] infoPtr : valid pointer to the listview structure
4873 * [I] hdc : device context handle
4875 * RETURN:
4876 * None
4878 static void LISTVIEW_RefreshOwnerDraw(const LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4880 UINT uID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
4881 DWORD cditemmode = CDRF_DODEFAULT;
4882 NMLVCUSTOMDRAW nmlvcd;
4883 POINT Origin, Position;
4884 DRAWITEMSTRUCT dis;
4885 LVITEMW item;
4887 TRACE("()\n");
4889 ZeroMemory(&dis, sizeof(dis));
4891 /* Get scroll info once before loop */
4892 LISTVIEW_GetOrigin(infoPtr, &Origin);
4894 /* iterate through the invalidated rows */
4895 while(iterator_next(i))
4897 item.iItem = i->nItem;
4898 item.iSubItem = 0;
4899 item.mask = LVIF_PARAM | LVIF_STATE;
4900 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4901 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
4903 dis.CtlType = ODT_LISTVIEW;
4904 dis.CtlID = uID;
4905 dis.itemID = item.iItem;
4906 dis.itemAction = ODA_DRAWENTIRE;
4907 dis.itemState = 0;
4908 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
4909 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
4910 dis.hwndItem = infoPtr->hwndSelf;
4911 dis.hDC = hdc;
4912 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
4913 dis.rcItem.left = Position.x + Origin.x;
4914 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
4915 dis.rcItem.top = Position.y + Origin.y;
4916 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
4917 dis.itemData = item.lParam;
4919 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), wine_dbgstr_rect(&dis.rcItem));
4922 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
4923 * structure for the rest. of the paint cycle
4925 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
4926 if (cdmode & CDRF_NOTIFYITEMDRAW)
4927 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
4929 if (!(cditemmode & CDRF_SKIPDEFAULT))
4931 prepaint_setup (infoPtr, hdc, &nmlvcd, FALSE);
4932 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
4935 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
4936 notify_postpaint(infoPtr, &nmlvcd);
4940 /***
4941 * DESCRIPTION:
4942 * Draws listview items when in report display mode.
4944 * PARAMETER(S):
4945 * [I] infoPtr : valid pointer to the listview structure
4946 * [I] hdc : device context handle
4947 * [I] cdmode : custom draw mode
4949 * RETURN:
4950 * None
4952 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
4954 INT rgntype;
4955 RECT rcClip, rcItem;
4956 POINT Origin;
4957 RANGES colRanges;
4958 INT col;
4959 ITERATOR j;
4961 TRACE("()\n");
4963 /* figure out what to draw */
4964 rgntype = GetClipBox(hdc, &rcClip);
4965 if (rgntype == NULLREGION) return;
4967 /* Get scroll info once before loop */
4968 LISTVIEW_GetOrigin(infoPtr, &Origin);
4970 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
4972 /* narrow down the columns we need to paint */
4973 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
4975 INT index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
4977 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
4978 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
4979 ranges_additem(colRanges, index);
4981 iterator_rangesitems(&j, colRanges);
4983 /* in full row select, we _have_ to draw the main item */
4984 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
4985 j.nSpecial = 0;
4987 /* iterate through the invalidated rows */
4988 while(iterator_next(i))
4990 RANGES subitems;
4991 POINT Position;
4992 ITERATOR k;
4994 SelectObject(hdc, infoPtr->hFont);
4995 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
4996 Position.x = Origin.x;
4997 Position.y += Origin.y;
4999 subitems = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5001 /* iterate through the invalidated columns */
5002 while(iterator_next(&j))
5004 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5006 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
5008 rcItem.top = 0;
5009 rcItem.bottom = infoPtr->nItemHeight;
5010 OffsetRect(&rcItem, Origin.x, Position.y);
5011 if (!RectVisible(hdc, &rcItem)) continue;
5014 ranges_additem(subitems, j.nItem);
5017 iterator_rangesitems(&k, subitems);
5018 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, &k, Position, cdmode);
5019 iterator_destroy(&k);
5021 iterator_destroy(&j);
5024 /***
5025 * DESCRIPTION:
5026 * Draws the gridlines if necessary when in report display mode.
5028 * PARAMETER(S):
5029 * [I] infoPtr : valid pointer to the listview structure
5030 * [I] hdc : device context handle
5032 * RETURN:
5033 * None
5035 static void LISTVIEW_RefreshReportGrid(LISTVIEW_INFO *infoPtr, HDC hdc)
5037 INT rgntype;
5038 INT y, itemheight;
5039 INT col, index;
5040 HPEN hPen, hOldPen;
5041 RECT rcClip, rcItem = {0};
5042 POINT Origin;
5043 RANGES colRanges;
5044 ITERATOR j;
5045 BOOL rmost = FALSE;
5047 TRACE("()\n");
5049 /* figure out what to draw */
5050 rgntype = GetClipBox(hdc, &rcClip);
5051 if (rgntype == NULLREGION) return;
5053 /* Get scroll info once before loop */
5054 LISTVIEW_GetOrigin(infoPtr, &Origin);
5056 colRanges = ranges_create(DPA_GetPtrCount(infoPtr->hdpaColumns));
5058 /* narrow down the columns we need to paint */
5059 for(col = 0; col < DPA_GetPtrCount(infoPtr->hdpaColumns); col++)
5061 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, col, 0);
5063 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5064 if ((rcItem.right + Origin.x >= rcClip.left) && (rcItem.left + Origin.x < rcClip.right))
5065 ranges_additem(colRanges, index);
5068 /* is right most vertical line visible? */
5069 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
5071 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5072 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5073 rmost = (rcItem.right + Origin.x < rcClip.right);
5076 if ((hPen = CreatePen( PS_SOLID, 1, comctl32_color.clr3dFace )))
5078 hOldPen = SelectObject ( hdc, hPen );
5080 /* draw the vertical lines for the columns */
5081 iterator_rangesitems(&j, colRanges);
5082 while(iterator_next(&j))
5084 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
5085 if (rcItem.left == 0) continue; /* skip leftmost column */
5086 rcItem.left += Origin.x;
5087 rcItem.right += Origin.x;
5088 rcItem.top = infoPtr->rcList.top;
5089 rcItem.bottom = infoPtr->rcList.bottom;
5090 TRACE("vert col=%d, rcItem=%s\n", j.nItem, wine_dbgstr_rect(&rcItem));
5091 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5092 LineTo (hdc, rcItem.left, rcItem.bottom);
5094 iterator_destroy(&j);
5095 /* draw rightmost grid line if visible */
5096 if (rmost)
5098 index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
5099 DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
5100 LISTVIEW_GetHeaderRect(infoPtr, index, &rcItem);
5102 rcItem.right += Origin.x;
5104 MoveToEx (hdc, rcItem.right, infoPtr->rcList.top, NULL);
5105 LineTo (hdc, rcItem.right, infoPtr->rcList.bottom);
5108 /* draw the horizontal lines for the rows */
5109 itemheight = LISTVIEW_CalculateItemHeight(infoPtr);
5110 rcItem.left = infoPtr->rcList.left;
5111 rcItem.right = infoPtr->rcList.right;
5112 for(y = Origin.y > 1 ? Origin.y - 1 : itemheight - 1 + Origin.y % itemheight; y<=infoPtr->rcList.bottom; y+=itemheight)
5114 rcItem.bottom = rcItem.top = y;
5115 TRACE("horz rcItem=%s\n", wine_dbgstr_rect(&rcItem));
5116 MoveToEx (hdc, rcItem.left, rcItem.top, NULL);
5117 LineTo (hdc, rcItem.right, rcItem.top);
5120 SelectObject( hdc, hOldPen );
5121 DeleteObject( hPen );
5123 else
5124 ranges_destroy(colRanges);
5127 /***
5128 * DESCRIPTION:
5129 * Draws listview items when in list display mode.
5131 * PARAMETER(S):
5132 * [I] infoPtr : valid pointer to the listview structure
5133 * [I] hdc : device context handle
5134 * [I] cdmode : custom draw mode
5136 * RETURN:
5137 * None
5139 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
5141 POINT Origin, Position;
5143 /* Get scroll info once before loop */
5144 LISTVIEW_GetOrigin(infoPtr, &Origin);
5146 while(iterator_prev(i))
5148 SelectObject(hdc, infoPtr->hFont);
5149 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
5150 Position.x += Origin.x;
5151 Position.y += Origin.y;
5153 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, NULL, Position, cdmode);
5158 /***
5159 * DESCRIPTION:
5160 * Draws listview items.
5162 * PARAMETER(S):
5163 * [I] infoPtr : valid pointer to the listview structure
5164 * [I] hdc : device context handle
5165 * [I] prcErase : rect to be erased before refresh (may be NULL)
5167 * RETURN:
5168 * NoneX
5170 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *prcErase)
5172 COLORREF oldTextColor = 0, oldBkColor = 0;
5173 NMLVCUSTOMDRAW nmlvcd;
5174 HFONT hOldFont = 0;
5175 DWORD cdmode;
5176 INT oldBkMode = 0;
5177 RECT rcClient;
5178 ITERATOR i;
5179 HDC hdcOrig = hdc;
5180 HBITMAP hbmp = NULL;
5181 RANGE range;
5183 LISTVIEW_DUMP(infoPtr);
5185 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5186 TRACE("double buffering\n");
5188 hdc = CreateCompatibleDC(hdcOrig);
5189 if (!hdc) {
5190 ERR("Failed to create DC for backbuffer\n");
5191 return;
5193 hbmp = CreateCompatibleBitmap(hdcOrig, infoPtr->rcList.right,
5194 infoPtr->rcList.bottom);
5195 if (!hbmp) {
5196 ERR("Failed to create bitmap for backbuffer\n");
5197 DeleteDC(hdc);
5198 return;
5201 SelectObject(hdc, hbmp);
5202 SelectObject(hdc, infoPtr->hFont);
5204 if(GetClipBox(hdcOrig, &rcClient))
5205 IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
5206 } else {
5207 /* Save dc values we're gonna trash while drawing
5208 * FIXME: Should be done in LISTVIEW_DrawItem() */
5209 hOldFont = SelectObject(hdc, infoPtr->hFont);
5210 oldBkMode = GetBkMode(hdc);
5211 oldBkColor = GetBkColor(hdc);
5212 oldTextColor = GetTextColor(hdc);
5215 infoPtr->bIsDrawing = TRUE;
5217 if (prcErase) {
5218 LISTVIEW_FillBkgnd(infoPtr, hdc, prcErase);
5219 } else if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) {
5220 /* If no erasing was done (usually because RedrawWindow was called
5221 * with RDW_INVALIDATE only) we need to copy the old contents into
5222 * the backbuffer before continuing. */
5223 BitBlt(hdc, infoPtr->rcList.left, infoPtr->rcList.top,
5224 infoPtr->rcList.right - infoPtr->rcList.left,
5225 infoPtr->rcList.bottom - infoPtr->rcList.top,
5226 hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5229 GetClientRect(infoPtr->hwndSelf, &rcClient);
5230 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
5231 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
5232 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
5234 /* nothing to draw */
5235 if(infoPtr->nItemCount == 0) goto enddraw;
5237 /* figure out what we need to draw */
5238 iterator_visibleitems(&i, infoPtr, hdc);
5239 range = iterator_range(&i);
5241 /* send cache hint notification */
5242 if (infoPtr->dwStyle & LVS_OWNERDATA)
5244 NMLVCACHEHINT nmlv;
5246 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
5247 nmlv.iFrom = range.lower;
5248 nmlv.iTo = range.upper - 1;
5249 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
5252 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
5253 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
5254 else
5256 if (infoPtr->uView == LV_VIEW_DETAILS)
5257 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
5258 else /* LV_VIEW_LIST, LV_VIEW_ICON or LV_VIEW_SMALLICON */
5259 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
5261 /* if we have a focus rect and it's visible, draw it */
5262 if (infoPtr->bFocus && range.lower <= infoPtr->nFocusedItem &&
5263 (range.upper - 1) >= infoPtr->nFocusedItem)
5264 LISTVIEW_DrawFocusRect(infoPtr, hdc);
5266 iterator_destroy(&i);
5268 enddraw:
5269 /* For LVS_EX_GRIDLINES go and draw lines */
5270 /* This includes the case where there were *no* items */
5271 if ((infoPtr->uView == LV_VIEW_DETAILS) && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
5272 LISTVIEW_RefreshReportGrid(infoPtr, hdc);
5274 /* Draw marquee rectangle if appropriate */
5275 if (infoPtr->bMarqueeSelect)
5276 DrawFocusRect(hdc, &infoPtr->marqueeDrawRect);
5278 if (cdmode & CDRF_NOTIFYPOSTPAINT)
5279 notify_postpaint(infoPtr, &nmlvcd);
5281 if(hbmp) {
5282 BitBlt(hdcOrig, infoPtr->rcList.left, infoPtr->rcList.top,
5283 infoPtr->rcList.right - infoPtr->rcList.left,
5284 infoPtr->rcList.bottom - infoPtr->rcList.top,
5285 hdc, infoPtr->rcList.left, infoPtr->rcList.top, SRCCOPY);
5287 DeleteObject(hbmp);
5288 DeleteDC(hdc);
5289 } else {
5290 SelectObject(hdc, hOldFont);
5291 SetBkMode(hdc, oldBkMode);
5292 SetBkColor(hdc, oldBkColor);
5293 SetTextColor(hdc, oldTextColor);
5296 infoPtr->bIsDrawing = FALSE;
5300 /***
5301 * DESCRIPTION:
5302 * Calculates the approximate width and height of a given number of items.
5304 * PARAMETER(S):
5305 * [I] infoPtr : valid pointer to the listview structure
5306 * [I] nItemCount : number of items
5307 * [I] wWidth : width
5308 * [I] wHeight : height
5310 * RETURN:
5311 * Returns a DWORD. The width in the low word and the height in high word.
5313 static DWORD LISTVIEW_ApproximateViewRect(const LISTVIEW_INFO *infoPtr, INT nItemCount,
5314 WORD wWidth, WORD wHeight)
5316 DWORD dwViewRect = 0;
5318 if (nItemCount == -1)
5319 nItemCount = infoPtr->nItemCount;
5321 if (infoPtr->uView == LV_VIEW_LIST)
5323 INT nItemCountPerColumn = 1;
5324 INT nColumnCount = 0;
5326 if (wHeight == 0xFFFF)
5328 /* use current height */
5329 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5332 if (wHeight < infoPtr->nItemHeight)
5333 wHeight = infoPtr->nItemHeight;
5335 if (nItemCount > 0)
5337 if (infoPtr->nItemHeight > 0)
5339 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
5340 if (nItemCountPerColumn == 0)
5341 nItemCountPerColumn = 1;
5343 if (nItemCount % nItemCountPerColumn != 0)
5344 nColumnCount = nItemCount / nItemCountPerColumn;
5345 else
5346 nColumnCount = nItemCount / nItemCountPerColumn + 1;
5350 /* Microsoft padding magic */
5351 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
5352 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
5354 dwViewRect = MAKELONG(wWidth, wHeight);
5356 else if (infoPtr->uView == LV_VIEW_DETAILS)
5358 RECT rcBox;
5360 if (infoPtr->nItemCount > 0)
5362 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
5363 wWidth = rcBox.right - rcBox.left;
5364 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
5366 else
5368 /* use current height and width */
5369 if (wHeight == 0xffff)
5370 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
5371 if (wWidth == 0xffff)
5372 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5375 dwViewRect = MAKELONG(wWidth, wHeight);
5377 else if (infoPtr->uView == LV_VIEW_ICON)
5379 UINT rows,cols;
5380 UINT nItemWidth;
5381 UINT nItemHeight;
5383 nItemWidth = infoPtr->iconSpacing.cx;
5384 nItemHeight = infoPtr->iconSpacing.cy;
5386 if (wWidth == 0xffff)
5387 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
5389 if (wWidth < nItemWidth)
5390 wWidth = nItemWidth;
5392 cols = wWidth / nItemWidth;
5393 if (cols > nItemCount)
5394 cols = nItemCount;
5395 if (cols < 1)
5396 cols = 1;
5398 if (nItemCount)
5400 rows = nItemCount / cols;
5401 if (nItemCount % cols)
5402 rows++;
5404 else
5405 rows = 0;
5407 wHeight = (nItemHeight * rows)+2;
5408 wWidth = (nItemWidth * cols)+2;
5410 dwViewRect = MAKELONG(wWidth, wHeight);
5412 else if (infoPtr->uView == LV_VIEW_SMALLICON)
5413 FIXME("uView == LV_VIEW_SMALLICON: not implemented\n");
5415 return dwViewRect;
5418 /***
5419 * DESCRIPTION:
5420 * Cancel edit label with saving item text.
5422 * PARAMETER(S):
5423 * [I] infoPtr : valid pointer to the listview structure
5425 * RETURN:
5426 * Always returns TRUE.
5428 static LRESULT LISTVIEW_CancelEditLabel(LISTVIEW_INFO *infoPtr)
5430 if (infoPtr->hwndEdit)
5432 /* handle value will be lost after LISTVIEW_EndEditLabelT */
5433 HWND edit = infoPtr->hwndEdit;
5435 LISTVIEW_EndEditLabelT(infoPtr, TRUE, IsWindowUnicode(infoPtr->hwndEdit));
5436 SendMessageW(edit, WM_CLOSE, 0, 0);
5439 return TRUE;
5442 /***
5443 * DESCRIPTION:
5444 * Create a drag image list for the specified item.
5446 * PARAMETER(S):
5447 * [I] infoPtr : valid pointer to the listview structure
5448 * [I] iItem : index of item
5449 * [O] lppt : Upper-left corner of the image
5451 * RETURN:
5452 * Returns a handle to the image list if successful, NULL otherwise.
5454 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
5456 RECT rcItem;
5457 SIZE size;
5458 POINT pos;
5459 HDC hdc, hdcOrig;
5460 HBITMAP hbmp, hOldbmp;
5461 HFONT hOldFont;
5462 HIMAGELIST dragList = 0;
5463 TRACE("iItem=%d Count=%d\n", iItem, infoPtr->nItemCount);
5465 if (iItem < 0 || iItem >= infoPtr->nItemCount || !lppt)
5466 return 0;
5468 rcItem.left = LVIR_BOUNDS;
5469 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
5470 return 0;
5472 lppt->x = rcItem.left;
5473 lppt->y = rcItem.top;
5475 size.cx = rcItem.right - rcItem.left;
5476 size.cy = rcItem.bottom - rcItem.top;
5478 hdcOrig = GetDC(infoPtr->hwndSelf);
5479 hdc = CreateCompatibleDC(hdcOrig);
5480 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
5481 hOldbmp = SelectObject(hdc, hbmp);
5482 hOldFont = SelectObject(hdc, infoPtr->hFont);
5484 SetRect(&rcItem, 0, 0, size.cx, size.cy);
5485 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
5487 pos.x = pos.y = 0;
5488 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, NULL, pos, CDRF_DODEFAULT))
5490 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
5491 SelectObject(hdc, hOldbmp);
5492 ImageList_Add(dragList, hbmp, 0);
5494 else
5495 SelectObject(hdc, hOldbmp);
5497 SelectObject(hdc, hOldFont);
5498 DeleteObject(hbmp);
5499 DeleteDC(hdc);
5500 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
5502 TRACE("ret=%p\n", dragList);
5504 return dragList;
5508 /***
5509 * DESCRIPTION:
5510 * Removes all listview items and subitems.
5512 * PARAMETER(S):
5513 * [I] infoPtr : valid pointer to the listview structure
5515 * RETURN:
5516 * SUCCESS : TRUE
5517 * FAILURE : FALSE
5519 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr, BOOL destroy)
5521 HDPA hdpaSubItems = NULL;
5522 BOOL suppress = FALSE;
5523 ITEMHDR *hdrItem;
5524 ITEM_INFO *lpItem;
5525 ITEM_ID *lpID;
5526 INT i, j;
5528 TRACE("()\n");
5530 /* we do it directly, to avoid notifications */
5531 ranges_clear(infoPtr->selectionRanges);
5532 infoPtr->nSelectionMark = -1;
5533 infoPtr->nFocusedItem = -1;
5534 SetRectEmpty(&infoPtr->rcFocus);
5535 /* But we are supposed to leave nHotItem as is! */
5537 /* send LVN_DELETEALLITEMS notification */
5538 if (!(infoPtr->dwStyle & LVS_OWNERDATA) || !destroy)
5540 NMLISTVIEW nmlv;
5542 memset(&nmlv, 0, sizeof(NMLISTVIEW));
5543 nmlv.iItem = -1;
5544 suppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
5547 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
5549 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5551 /* send LVN_DELETEITEM notification, if not suppressed
5552 and if it is not a virtual listview */
5553 if (!suppress) notify_deleteitem(infoPtr, i);
5554 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
5555 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5556 /* free id struct */
5557 j = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5558 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, j);
5559 DPA_DeletePtr(infoPtr->hdpaItemIds, j);
5560 Free(lpID);
5561 /* both item and subitem start with ITEMHDR header */
5562 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
5564 hdrItem = DPA_GetPtr(hdpaSubItems, j);
5565 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5566 Free(hdrItem);
5568 DPA_Destroy(hdpaSubItems);
5569 DPA_DeletePtr(infoPtr->hdpaItems, i);
5571 DPA_DeletePtr(infoPtr->hdpaPosX, i);
5572 DPA_DeletePtr(infoPtr->hdpaPosY, i);
5573 infoPtr->nItemCount --;
5576 if (!destroy)
5578 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5579 LISTVIEW_UpdateScroll(infoPtr);
5581 LISTVIEW_InvalidateList(infoPtr);
5583 return TRUE;
5586 /***
5587 * DESCRIPTION:
5588 * Scrolls, and updates the columns, when a column is changing width.
5590 * PARAMETER(S):
5591 * [I] infoPtr : valid pointer to the listview structure
5592 * [I] nColumn : column to scroll
5593 * [I] dx : amount of scroll, in pixels
5595 * RETURN:
5596 * None.
5598 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
5600 COLUMN_INFO *lpColumnInfo;
5601 RECT rcOld, rcCol;
5602 POINT ptOrigin;
5603 INT nCol;
5604 HDITEMW hdi;
5606 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
5607 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
5608 rcCol = lpColumnInfo->rcHeader;
5609 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5610 rcCol.left = rcCol.right;
5612 /* adjust the other columns */
5613 hdi.mask = HDI_ORDER;
5614 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
5616 INT nOrder = hdi.iOrder;
5617 for (nCol = 0; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
5619 hdi.mask = HDI_ORDER;
5620 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nCol, (LPARAM)&hdi);
5621 if (hdi.iOrder >= nOrder) {
5622 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
5623 lpColumnInfo->rcHeader.left += dx;
5624 lpColumnInfo->rcHeader.right += dx;
5629 /* do not update screen if not in report mode */
5630 if (!is_redrawing(infoPtr) || infoPtr->uView != LV_VIEW_DETAILS) return;
5632 /* Need to reset the item width when inserting a new column */
5633 infoPtr->nItemWidth += dx;
5635 LISTVIEW_UpdateScroll(infoPtr);
5636 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
5638 /* scroll to cover the deleted column, and invalidate for redraw */
5639 rcOld = infoPtr->rcList;
5640 rcOld.left = ptOrigin.x + rcCol.left + dx;
5641 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
5644 /***
5645 * DESCRIPTION:
5646 * Removes a column from the listview control.
5648 * PARAMETER(S):
5649 * [I] infoPtr : valid pointer to the listview structure
5650 * [I] nColumn : column index
5652 * RETURN:
5653 * SUCCESS : TRUE
5654 * FAILURE : FALSE
5656 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
5658 RECT rcCol;
5660 TRACE("nColumn=%d\n", nColumn);
5662 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
5663 return FALSE;
5665 /* While the MSDN specifically says that column zero should not be deleted,
5666 what actually happens is that the column itself is deleted but no items or subitems
5667 are removed.
5670 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
5672 if (!SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nColumn, 0))
5673 return FALSE;
5675 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
5676 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
5678 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
5680 SUBITEM_INFO *lpSubItem, *lpDelItem;
5681 HDPA hdpaSubItems;
5682 INT nItem, nSubItem, i;
5684 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
5686 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
5687 nSubItem = 0;
5688 lpDelItem = 0;
5689 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
5691 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
5692 if (lpSubItem->iSubItem == nColumn)
5694 nSubItem = i;
5695 lpDelItem = lpSubItem;
5697 else if (lpSubItem->iSubItem > nColumn)
5699 lpSubItem->iSubItem--;
5703 /* if we found our subitem, zap it */
5704 if (nSubItem > 0)
5706 /* free string */
5707 if (is_text(lpDelItem->hdr.pszText))
5708 Free(lpDelItem->hdr.pszText);
5710 /* free item */
5711 Free(lpDelItem);
5713 /* free dpa memory */
5714 DPA_DeletePtr(hdpaSubItems, nSubItem);
5719 /* update the other column info */
5720 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
5721 LISTVIEW_InvalidateList(infoPtr);
5722 else
5723 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
5724 LISTVIEW_UpdateItemSize(infoPtr);
5726 return TRUE;
5729 /***
5730 * DESCRIPTION:
5731 * Invalidates the listview after an item's insertion or deletion.
5733 * PARAMETER(S):
5734 * [I] infoPtr : valid pointer to the listview structure
5735 * [I] nItem : item index
5736 * [I] dir : -1 if deleting, 1 if inserting
5738 * RETURN:
5739 * None
5741 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
5743 INT nPerCol, nItemCol, nItemRow;
5744 RECT rcScroll;
5745 POINT Origin;
5747 /* if we don't refresh, what's the point of scrolling? */
5748 if (!is_redrawing(infoPtr)) return;
5750 assert (abs(dir) == 1);
5752 /* arrange icons if autoarrange is on */
5753 if (is_autoarrange(infoPtr))
5755 BOOL arrange = TRUE;
5756 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
5757 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
5758 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
5761 /* scrollbars need updating */
5762 LISTVIEW_UpdateScroll(infoPtr);
5764 /* figure out the item's position */
5765 if (infoPtr->uView == LV_VIEW_DETAILS)
5766 nPerCol = infoPtr->nItemCount + 1;
5767 else if (infoPtr->uView == LV_VIEW_LIST)
5768 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
5769 else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
5770 return;
5772 nItemCol = nItem / nPerCol;
5773 nItemRow = nItem % nPerCol;
5774 LISTVIEW_GetOrigin(infoPtr, &Origin);
5776 /* move the items below up a slot */
5777 rcScroll.left = nItemCol * infoPtr->nItemWidth;
5778 rcScroll.top = nItemRow * infoPtr->nItemHeight;
5779 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
5780 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
5781 OffsetRect(&rcScroll, Origin.x, Origin.y);
5782 TRACE("rcScroll=%s, dx=%d\n", wine_dbgstr_rect(&rcScroll), dir * infoPtr->nItemHeight);
5783 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5785 TRACE("Invalidating rcScroll=%s, rcList=%s\n", wine_dbgstr_rect(&rcScroll), wine_dbgstr_rect(&infoPtr->rcList));
5786 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5789 /* report has only that column, so we're done */
5790 if (infoPtr->uView == LV_VIEW_DETAILS) return;
5792 /* now for LISTs, we have to deal with the columns to the right */
5793 SetRect(&rcScroll, (nItemCol + 1) * infoPtr->nItemWidth, 0,
5794 (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth,
5795 nPerCol * infoPtr->nItemHeight);
5796 OffsetRect(&rcScroll, Origin.x, Origin.y);
5797 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
5798 InvalidateRect(infoPtr->hwndSelf, &rcScroll, TRUE);
5801 /***
5802 * DESCRIPTION:
5803 * Removes an item from the listview control.
5805 * PARAMETER(S):
5806 * [I] infoPtr : valid pointer to the listview structure
5807 * [I] nItem : item index
5809 * RETURN:
5810 * SUCCESS : TRUE
5811 * FAILURE : FALSE
5813 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
5815 LVITEMW item;
5816 const BOOL is_icon = (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON);
5817 INT focus = infoPtr->nFocusedItem;
5819 TRACE("(nItem=%d)\n", nItem);
5821 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5823 /* remove selection, and focus */
5824 item.state = 0;
5825 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
5826 LISTVIEW_SetItemState(infoPtr, nItem, &item);
5828 /* send LVN_DELETEITEM notification. */
5829 if (!notify_deleteitem(infoPtr, nItem)) return FALSE;
5831 /* we need to do this here, because we'll be deleting stuff */
5832 if (is_icon)
5833 LISTVIEW_InvalidateItem(infoPtr, nItem);
5835 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5837 HDPA hdpaSubItems;
5838 ITEMHDR *hdrItem;
5839 ITEM_INFO *lpItem;
5840 ITEM_ID *lpID;
5841 INT i;
5843 hdpaSubItems = DPA_DeletePtr(infoPtr->hdpaItems, nItem);
5844 lpItem = DPA_GetPtr(hdpaSubItems, 0);
5846 /* free id struct */
5847 i = DPA_GetPtrIndex(infoPtr->hdpaItemIds, lpItem->id);
5848 lpID = DPA_GetPtr(infoPtr->hdpaItemIds, i);
5849 DPA_DeletePtr(infoPtr->hdpaItemIds, i);
5850 Free(lpID);
5851 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
5853 hdrItem = DPA_GetPtr(hdpaSubItems, i);
5854 if (is_text(hdrItem->pszText)) Free(hdrItem->pszText);
5855 Free(hdrItem);
5857 DPA_Destroy(hdpaSubItems);
5860 if (is_icon)
5862 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
5863 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
5866 infoPtr->nItemCount--;
5867 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
5868 LISTVIEW_ShiftFocus(infoPtr, focus, nItem, -1);
5870 /* now is the invalidation fun */
5871 if (!is_icon)
5872 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
5873 return TRUE;
5877 /***
5878 * DESCRIPTION:
5879 * Callback implementation for editlabel control
5881 * PARAMETER(S):
5882 * [I] infoPtr : valid pointer to the listview structure
5883 * [I] storeText : store edit box text as item text
5884 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
5886 * RETURN:
5887 * SUCCESS : TRUE
5888 * FAILURE : FALSE
5890 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, BOOL storeText, BOOL isW)
5892 HWND hwndSelf = infoPtr->hwndSelf;
5893 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
5894 NMLVDISPINFOW dispInfo;
5895 INT editedItem = infoPtr->nEditLabelItem;
5896 BOOL same;
5897 WCHAR *pszText = NULL;
5898 BOOL res;
5900 if (storeText)
5902 DWORD len = isW ? GetWindowTextLengthW(infoPtr->hwndEdit) : GetWindowTextLengthA(infoPtr->hwndEdit);
5904 if (len++)
5906 if (!(pszText = Alloc(len * (isW ? sizeof(WCHAR) : sizeof(CHAR)))))
5907 return FALSE;
5909 if (isW)
5910 GetWindowTextW(infoPtr->hwndEdit, pszText, len);
5911 else
5912 GetWindowTextA(infoPtr->hwndEdit, (CHAR*)pszText, len);
5916 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
5918 ZeroMemory(&dispInfo, sizeof(dispInfo));
5919 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
5920 dispInfo.item.iItem = editedItem;
5921 dispInfo.item.iSubItem = 0;
5922 dispInfo.item.stateMask = ~0;
5923 dispInfo.item.pszText = szDispText;
5924 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
5925 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW))
5927 res = FALSE;
5928 goto cleanup;
5931 if (isW)
5932 same = (lstrcmpW(dispInfo.item.pszText, pszText) == 0);
5933 else
5935 LPWSTR tmp = textdupTtoW(pszText, FALSE);
5936 same = (lstrcmpW(dispInfo.item.pszText, tmp) == 0);
5937 textfreeT(tmp, FALSE);
5940 /* add the text from the edit in */
5941 dispInfo.item.mask |= LVIF_TEXT;
5942 dispInfo.item.pszText = same ? NULL : pszText;
5943 dispInfo.item.cchTextMax = textlenT(dispInfo.item.pszText, isW);
5945 /* Do we need to update the Item Text */
5946 res = notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW);
5948 infoPtr->nEditLabelItem = -1;
5949 infoPtr->hwndEdit = 0;
5951 if (!res) goto cleanup;
5953 if (!IsWindow(hwndSelf))
5955 res = FALSE;
5956 goto cleanup;
5958 if (!pszText) return TRUE;
5959 if (same)
5961 res = TRUE;
5962 goto cleanup;
5965 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
5967 HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, editedItem);
5968 ITEM_INFO* lpItem = DPA_GetPtr(hdpaSubItems, 0);
5969 if (lpItem && lpItem->hdr.pszText == LPSTR_TEXTCALLBACKW)
5971 LISTVIEW_InvalidateItem(infoPtr, editedItem);
5972 res = TRUE;
5973 goto cleanup;
5977 ZeroMemory(&dispInfo, sizeof(dispInfo));
5978 dispInfo.item.mask = LVIF_TEXT;
5979 dispInfo.item.iItem = editedItem;
5980 dispInfo.item.iSubItem = 0;
5981 dispInfo.item.pszText = pszText;
5982 dispInfo.item.cchTextMax = textlenT(pszText, isW);
5983 res = LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
5985 cleanup:
5986 Free(pszText);
5988 return res;
5991 /***
5992 * DESCRIPTION:
5993 * Subclassed edit control windproc function
5995 * PARAMETER(S):
5996 * [I] hwnd : the edit window handle
5997 * [I] uMsg : the message that is to be processed
5998 * [I] wParam : first message parameter
5999 * [I] lParam : second message parameter
6000 * [I] isW : TRUE if input is Unicode
6002 * RETURN:
6003 * Zero.
6005 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
6007 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
6008 BOOL save = TRUE;
6010 TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx, isW=%d)\n",
6011 hwnd, uMsg, wParam, lParam, isW);
6013 switch (uMsg)
6015 case WM_GETDLGCODE:
6016 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
6018 case WM_DESTROY:
6020 WNDPROC editProc = infoPtr->EditWndProc;
6021 infoPtr->EditWndProc = 0;
6022 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
6023 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
6026 case WM_KEYDOWN:
6027 if (VK_ESCAPE == (INT)wParam)
6029 save = FALSE;
6030 break;
6032 else if (VK_RETURN == (INT)wParam)
6033 break;
6035 default:
6036 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
6039 /* kill the edit */
6040 if (infoPtr->hwndEdit)
6041 LISTVIEW_EndEditLabelT(infoPtr, save, isW);
6043 SendMessageW(hwnd, WM_CLOSE, 0, 0);
6044 return 0;
6047 /***
6048 * DESCRIPTION:
6049 * Subclassed edit control Unicode windproc function
6051 * PARAMETER(S):
6052 * [I] hwnd : the edit window handle
6053 * [I] uMsg : the message that is to be processed
6054 * [I] wParam : first message parameter
6055 * [I] lParam : second message parameter
6057 * RETURN:
6059 static LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6061 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
6064 /***
6065 * DESCRIPTION:
6066 * Subclassed edit control ANSI windproc function
6068 * PARAMETER(S):
6069 * [I] hwnd : the edit window handle
6070 * [I] uMsg : the message that is to be processed
6071 * [I] wParam : first message parameter
6072 * [I] lParam : second message parameter
6074 * RETURN:
6076 static LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
6078 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
6081 /***
6082 * DESCRIPTION:
6083 * Creates a subclassed edit control
6085 * PARAMETER(S):
6086 * [I] infoPtr : valid pointer to the listview structure
6087 * [I] text : initial text for the edit
6088 * [I] style : the window style
6089 * [I] isW : TRUE if input is Unicode
6091 * RETURN:
6093 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, BOOL isW)
6095 static const DWORD style = WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|ES_AUTOHSCROLL|WS_BORDER|WS_VISIBLE;
6096 HINSTANCE hinst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
6097 HWND hedit;
6099 TRACE("(%p, text=%s, isW=%d)\n", infoPtr, debugtext_t(text, isW), isW);
6101 /* window will be resized and positioned after LVN_BEGINLABELEDIT */
6102 if (isW)
6103 hedit = CreateWindowW(WC_EDITW, text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6104 else
6105 hedit = CreateWindowA(WC_EDITA, (LPCSTR)text, style, 0, 0, 0, 0, infoPtr->hwndSelf, 0, hinst, 0);
6107 if (!hedit) return 0;
6109 infoPtr->EditWndProc = (WNDPROC)
6110 (isW ? SetWindowLongPtrW(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcW) :
6111 SetWindowLongPtrA(hedit, GWLP_WNDPROC, (DWORD_PTR)EditLblWndProcA) );
6113 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
6114 SendMessageW(hedit, EM_SETLIMITTEXT, DISP_TEXT_SIZE-1, 0);
6116 return hedit;
6119 /***
6120 * DESCRIPTION:
6121 * Begin in place editing of specified list view item
6123 * PARAMETER(S):
6124 * [I] infoPtr : valid pointer to the listview structure
6125 * [I] nItem : item index
6126 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
6128 * RETURN:
6129 * SUCCESS : TRUE
6130 * FAILURE : FALSE
6132 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
6134 WCHAR disptextW[DISP_TEXT_SIZE] = { 0 };
6135 HWND hwndSelf = infoPtr->hwndSelf;
6136 NMLVDISPINFOW dispInfo;
6137 HFONT hOldFont = NULL;
6138 TEXTMETRICW tm;
6139 RECT rect;
6140 SIZE sz;
6141 HDC hdc;
6143 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
6145 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
6147 /* remove existing edit box */
6148 if (infoPtr->hwndEdit)
6150 SetFocus(infoPtr->hwndSelf);
6151 infoPtr->hwndEdit = 0;
6154 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
6156 infoPtr->nEditLabelItem = nItem;
6158 LISTVIEW_SetSelection(infoPtr, nItem);
6159 LISTVIEW_SetItemFocus(infoPtr, nItem);
6160 LISTVIEW_InvalidateItem(infoPtr, nItem);
6162 rect.left = LVIR_LABEL;
6163 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
6165 ZeroMemory(&dispInfo, sizeof(dispInfo));
6166 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
6167 dispInfo.item.iItem = nItem;
6168 dispInfo.item.iSubItem = 0;
6169 dispInfo.item.stateMask = ~0;
6170 dispInfo.item.pszText = disptextW;
6171 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
6172 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
6174 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, isW);
6175 if (!infoPtr->hwndEdit) return 0;
6177 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
6179 if (!IsWindow(hwndSelf))
6180 return 0;
6181 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
6182 infoPtr->hwndEdit = 0;
6183 return 0;
6186 TRACE("disp text=%s\n", debugtext_t(dispInfo.item.pszText, isW));
6188 /* position and display edit box */
6189 hdc = GetDC(infoPtr->hwndSelf);
6191 /* select the font to get appropriate metric dimensions */
6192 if (infoPtr->hFont)
6193 hOldFont = SelectObject(hdc, infoPtr->hFont);
6195 /* use real edit box content, it could be altered during LVN_BEGINLABELEDIT notification */
6196 GetWindowTextW(infoPtr->hwndEdit, disptextW, DISP_TEXT_SIZE);
6197 TRACE("edit box text=%s\n", debugstr_w(disptextW));
6199 /* get string length in pixels */
6200 GetTextExtentPoint32W(hdc, disptextW, lstrlenW(disptextW), &sz);
6202 /* add extra spacing for the next character */
6203 GetTextMetricsW(hdc, &tm);
6204 sz.cx += tm.tmMaxCharWidth * 2;
6206 if (infoPtr->hFont)
6207 SelectObject(hdc, hOldFont);
6209 ReleaseDC(infoPtr->hwndSelf, hdc);
6211 sz.cy = rect.bottom - rect.top + 2;
6212 rect.left -= 2;
6213 rect.top -= 1;
6214 TRACE("moving edit=(%d,%d)-(%d,%d)\n", rect.left, rect.top, sz.cx, sz.cy);
6215 MoveWindow(infoPtr->hwndEdit, rect.left, rect.top, sz.cx, sz.cy, FALSE);
6216 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
6217 SetFocus(infoPtr->hwndEdit);
6218 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
6219 return infoPtr->hwndEdit;
6223 /***
6224 * DESCRIPTION:
6225 * Ensures the specified item is visible, scrolling into view if necessary.
6227 * PARAMETER(S):
6228 * [I] infoPtr : valid pointer to the listview structure
6229 * [I] nItem : item index
6230 * [I] bPartial : partially or entirely visible
6232 * RETURN:
6233 * SUCCESS : TRUE
6234 * FAILURE : FALSE
6236 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
6238 INT nScrollPosHeight = 0;
6239 INT nScrollPosWidth = 0;
6240 INT nHorzAdjust = 0;
6241 INT nVertAdjust = 0;
6242 INT nHorzDiff = 0;
6243 INT nVertDiff = 0;
6244 RECT rcItem, rcTemp;
6246 rcItem.left = LVIR_BOUNDS;
6247 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
6249 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
6251 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
6253 /* scroll left/right, but in LV_VIEW_DETAILS mode */
6254 if (infoPtr->uView == LV_VIEW_LIST)
6255 nScrollPosWidth = infoPtr->nItemWidth;
6256 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
6257 nScrollPosWidth = 1;
6259 if (rcItem.left < infoPtr->rcList.left)
6261 nHorzAdjust = -1;
6262 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.left - infoPtr->rcList.left;
6264 else
6266 nHorzAdjust = 1;
6267 if (infoPtr->uView != LV_VIEW_DETAILS) nHorzDiff = rcItem.right - infoPtr->rcList.right;
6271 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
6273 /* scroll up/down, but not in LVS_LIST mode */
6274 if (infoPtr->uView == LV_VIEW_DETAILS)
6275 nScrollPosHeight = infoPtr->nItemHeight;
6276 else if ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON))
6277 nScrollPosHeight = 1;
6279 if (rcItem.top < infoPtr->rcList.top)
6281 nVertAdjust = -1;
6282 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
6284 else
6286 nVertAdjust = 1;
6287 if (infoPtr->uView != LV_VIEW_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
6291 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
6293 if (nScrollPosWidth)
6295 INT diff = nHorzDiff / nScrollPosWidth;
6296 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
6297 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff);
6300 if (nScrollPosHeight)
6302 INT diff = nVertDiff / nScrollPosHeight;
6303 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
6304 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff);
6307 return TRUE;
6310 /***
6311 * DESCRIPTION:
6312 * Searches for an item with specific characteristics.
6314 * PARAMETER(S):
6315 * [I] hwnd : window handle
6316 * [I] nStart : base item index
6317 * [I] lpFindInfo : item information to look for
6319 * RETURN:
6320 * SUCCESS : index of item
6321 * FAILURE : -1
6323 static INT LISTVIEW_FindItemW(const LISTVIEW_INFO *infoPtr, INT nStart,
6324 const LVFINDINFOW *lpFindInfo)
6326 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
6327 BOOL bWrap = FALSE, bNearest = FALSE;
6328 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
6329 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
6330 POINT Position, Destination;
6331 LVITEMW lvItem;
6333 /* Search in virtual listviews should be done by application, not by
6334 listview control, so we just send LVN_ODFINDITEMW and return the result */
6335 if (infoPtr->dwStyle & LVS_OWNERDATA)
6337 NMLVFINDITEMW nmlv;
6339 nmlv.iStart = nStart;
6340 nmlv.lvfi = *lpFindInfo;
6341 return notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
6344 if (!lpFindInfo || nItem < 0) return -1;
6346 lvItem.mask = 0;
6347 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6348 lpFindInfo->flags & LVFI_SUBSTRING)
6350 lvItem.mask |= LVIF_TEXT;
6351 lvItem.pszText = szDispText;
6352 lvItem.cchTextMax = DISP_TEXT_SIZE;
6355 if (lpFindInfo->flags & LVFI_WRAP)
6356 bWrap = TRUE;
6358 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
6359 (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON))
6361 POINT Origin;
6362 RECT rcArea;
6364 LISTVIEW_GetOrigin(infoPtr, &Origin);
6365 Destination.x = lpFindInfo->pt.x - Origin.x;
6366 Destination.y = lpFindInfo->pt.y - Origin.y;
6367 switch(lpFindInfo->vkDirection)
6369 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
6370 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
6371 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
6372 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
6373 case VK_HOME: Destination.x = Destination.y = 0; break;
6374 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6375 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
6376 case VK_END:
6377 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
6378 Destination.x = rcArea.right;
6379 Destination.y = rcArea.bottom;
6380 break;
6381 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
6383 bNearest = TRUE;
6385 else Destination.x = Destination.y = 0;
6387 /* if LVFI_PARAM is specified, all other flags are ignored */
6388 if (lpFindInfo->flags & LVFI_PARAM)
6390 lvItem.mask |= LVIF_PARAM;
6391 bNearest = FALSE;
6392 lvItem.mask &= ~LVIF_TEXT;
6395 again:
6396 for (; nItem < nLast; nItem++)
6398 lvItem.iItem = nItem;
6399 lvItem.iSubItem = 0;
6400 lvItem.pszText = szDispText;
6401 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6403 if (lvItem.mask & LVIF_PARAM)
6405 if (lpFindInfo->lParam == lvItem.lParam)
6406 return nItem;
6407 else
6408 continue;
6411 if (lvItem.mask & LVIF_TEXT)
6413 if (lpFindInfo->flags & (LVFI_PARTIAL | LVFI_SUBSTRING))
6415 WCHAR *p = strstrW(lvItem.pszText, lpFindInfo->psz);
6416 if (!p || p != lvItem.pszText) continue;
6418 else
6420 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
6424 if (!bNearest) return nItem;
6426 /* This is very inefficient. To do a good job here,
6427 * we need a sorted array of (x,y) item positions */
6428 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
6430 /* compute the distance^2 to the destination */
6431 xdist = Destination.x - Position.x;
6432 ydist = Destination.y - Position.y;
6433 dist = xdist * xdist + ydist * ydist;
6435 /* remember the distance, and item if it's closer */
6436 if (dist < mindist)
6438 mindist = dist;
6439 nNearestItem = nItem;
6443 if (bWrap)
6445 nItem = 0;
6446 nLast = min(nStart + 1, infoPtr->nItemCount);
6447 bWrap = FALSE;
6448 goto again;
6451 return nNearestItem;
6454 /***
6455 * DESCRIPTION:
6456 * Searches for an item with specific characteristics.
6458 * PARAMETER(S):
6459 * [I] hwnd : window handle
6460 * [I] nStart : base item index
6461 * [I] lpFindInfo : item information to look for
6463 * RETURN:
6464 * SUCCESS : index of item
6465 * FAILURE : -1
6467 static INT LISTVIEW_FindItemA(const LISTVIEW_INFO *infoPtr, INT nStart,
6468 const LVFINDINFOA *lpFindInfo)
6470 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL) ||
6471 lpFindInfo->flags & LVFI_SUBSTRING;
6472 LVFINDINFOW fiw;
6473 INT res;
6474 LPWSTR strW = NULL;
6476 memcpy(&fiw, lpFindInfo, sizeof(fiw));
6477 if (hasText) fiw.psz = strW = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
6478 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
6479 textfreeT(strW, FALSE);
6480 return res;
6483 /***
6484 * DESCRIPTION:
6485 * Retrieves column attributes.
6487 * PARAMETER(S):
6488 * [I] infoPtr : valid pointer to the listview structure
6489 * [I] nColumn : column index
6490 * [IO] lpColumn : column information
6491 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
6492 * otherwise it is in fact a LPLVCOLUMNA
6494 * RETURN:
6495 * SUCCESS : TRUE
6496 * FAILURE : FALSE
6498 static BOOL LISTVIEW_GetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
6500 COLUMN_INFO *lpColumnInfo;
6501 HDITEMW hdi;
6503 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6504 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6506 /* initialize memory */
6507 ZeroMemory(&hdi, sizeof(hdi));
6509 if (lpColumn->mask & LVCF_TEXT)
6511 hdi.mask |= HDI_TEXT;
6512 hdi.pszText = lpColumn->pszText;
6513 hdi.cchTextMax = lpColumn->cchTextMax;
6516 if (lpColumn->mask & LVCF_IMAGE)
6517 hdi.mask |= HDI_IMAGE;
6519 if (lpColumn->mask & LVCF_ORDER)
6520 hdi.mask |= HDI_ORDER;
6522 if (lpColumn->mask & LVCF_SUBITEM)
6523 hdi.mask |= HDI_LPARAM;
6525 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
6527 if (lpColumn->mask & LVCF_FMT)
6528 lpColumn->fmt = lpColumnInfo->fmt;
6530 if (lpColumn->mask & LVCF_WIDTH)
6531 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
6533 if (lpColumn->mask & LVCF_IMAGE)
6534 lpColumn->iImage = hdi.iImage;
6536 if (lpColumn->mask & LVCF_ORDER)
6537 lpColumn->iOrder = hdi.iOrder;
6539 if (lpColumn->mask & LVCF_SUBITEM)
6540 lpColumn->iSubItem = hdi.lParam;
6542 if (lpColumn->mask & LVCF_MINWIDTH)
6543 lpColumn->cxMin = lpColumnInfo->cxMin;
6545 return TRUE;
6548 static inline BOOL LISTVIEW_GetColumnOrderArray(const LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
6550 if (!infoPtr->hwndHeader) return FALSE;
6551 return SendMessageW(infoPtr->hwndHeader, HDM_GETORDERARRAY, iCount, (LPARAM)lpiArray);
6554 /***
6555 * DESCRIPTION:
6556 * Retrieves the column width.
6558 * PARAMETER(S):
6559 * [I] infoPtr : valid pointer to the listview structure
6560 * [I] int : column index
6562 * RETURN:
6563 * SUCCESS : column width
6564 * FAILURE : zero
6566 static INT LISTVIEW_GetColumnWidth(const LISTVIEW_INFO *infoPtr, INT nColumn)
6568 INT nColumnWidth = 0;
6569 HDITEMW hdItem;
6571 TRACE("nColumn=%d\n", nColumn);
6573 /* we have a 'column' in LIST and REPORT mode only */
6574 switch(infoPtr->uView)
6576 case LV_VIEW_LIST:
6577 nColumnWidth = infoPtr->nItemWidth;
6578 break;
6579 case LV_VIEW_DETAILS:
6580 /* We are not using LISTVIEW_GetHeaderRect as this data is updated only after a HDN_ITEMCHANGED.
6581 * There is an application that subclasses the listview, calls LVM_GETCOLUMNWIDTH in the
6582 * HDN_ITEMCHANGED handler and goes into infinite recursion if it receives old data.
6584 hdItem.mask = HDI_WIDTH;
6585 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdItem))
6587 WARN("(%p): HDM_GETITEMW failed for item %d\n", infoPtr->hwndSelf, nColumn);
6588 return 0;
6590 nColumnWidth = hdItem.cxy;
6591 break;
6594 TRACE("nColumnWidth=%d\n", nColumnWidth);
6595 return nColumnWidth;
6598 /***
6599 * DESCRIPTION:
6600 * In list or report display mode, retrieves the number of items that can fit
6601 * vertically in the visible area. In icon or small icon display mode,
6602 * retrieves the total number of visible items.
6604 * PARAMETER(S):
6605 * [I] infoPtr : valid pointer to the listview structure
6607 * RETURN:
6608 * Number of fully visible items.
6610 static INT LISTVIEW_GetCountPerPage(const LISTVIEW_INFO *infoPtr)
6612 switch (infoPtr->uView)
6614 case LV_VIEW_ICON:
6615 case LV_VIEW_SMALLICON:
6616 return infoPtr->nItemCount;
6617 case LV_VIEW_DETAILS:
6618 return LISTVIEW_GetCountPerColumn(infoPtr);
6619 case LV_VIEW_LIST:
6620 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
6622 assert(FALSE);
6623 return 0;
6626 /***
6627 * DESCRIPTION:
6628 * Retrieves an image list handle.
6630 * PARAMETER(S):
6631 * [I] infoPtr : valid pointer to the listview structure
6632 * [I] nImageList : image list identifier
6634 * RETURN:
6635 * SUCCESS : image list handle
6636 * FAILURE : NULL
6638 static HIMAGELIST LISTVIEW_GetImageList(const LISTVIEW_INFO *infoPtr, INT nImageList)
6640 switch (nImageList)
6642 case LVSIL_NORMAL: return infoPtr->himlNormal;
6643 case LVSIL_SMALL: return infoPtr->himlSmall;
6644 case LVSIL_STATE: return infoPtr->himlState;
6645 case LVSIL_GROUPHEADER:
6646 FIXME("LVSIL_GROUPHEADER not supported\n");
6647 break;
6648 default:
6649 WARN("got unknown imagelist index - %d\n", nImageList);
6651 return NULL;
6654 /* LISTVIEW_GetISearchString */
6656 /***
6657 * DESCRIPTION:
6658 * Retrieves item attributes.
6660 * PARAMETER(S):
6661 * [I] hwnd : window handle
6662 * [IO] lpLVItem : item info
6663 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6664 * if FALSE, then lpLVItem is a LPLVITEMA.
6666 * NOTE:
6667 * This is the internal 'GetItem' interface -- it tries to
6668 * be smart and avoid text copies, if possible, by modifying
6669 * lpLVItem->pszText to point to the text string. Please note
6670 * that this is not always possible (e.g. OWNERDATA), so on
6671 * entry you *must* supply valid values for pszText, and cchTextMax.
6672 * The only difference to the documented interface is that upon
6673 * return, you should use *only* the lpLVItem->pszText, rather than
6674 * the buffer pointer you provided on input. Most code already does
6675 * that, so it's not a problem.
6676 * For the two cases when the text must be copied (that is,
6677 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
6679 * RETURN:
6680 * SUCCESS : TRUE
6681 * FAILURE : FALSE
6683 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6685 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
6686 NMLVDISPINFOW dispInfo;
6687 ITEM_INFO *lpItem;
6688 ITEMHDR* pItemHdr;
6689 HDPA hdpaSubItems;
6690 INT isubitem;
6692 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6694 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6695 return FALSE;
6697 if (lpLVItem->mask == 0) return TRUE;
6698 TRACE("mask=%x\n", lpLVItem->mask);
6700 /* make a local copy */
6701 isubitem = lpLVItem->iSubItem;
6703 /* a quick optimization if all we're asked is the focus state
6704 * these queries are worth optimising since they are common,
6705 * and can be answered in constant time, without the heavy accesses */
6706 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
6707 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
6709 lpLVItem->state = 0;
6710 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6711 lpLVItem->state |= LVIS_FOCUSED;
6712 return TRUE;
6715 ZeroMemory(&dispInfo, sizeof(dispInfo));
6717 /* if the app stores all the data, handle it separately */
6718 if (infoPtr->dwStyle & LVS_OWNERDATA)
6720 dispInfo.item.state = 0;
6722 /* apparently, we should not callback for lParam in LVS_OWNERDATA */
6723 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) ||
6724 ((lpLVItem->mask & LVIF_STATE) && (infoPtr->uCallbackMask & lpLVItem->stateMask)))
6726 UINT mask = lpLVItem->mask;
6728 /* NOTE: copy only fields which we _know_ are initialized, some apps
6729 * depend on the uninitialized fields being 0 */
6730 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
6731 dispInfo.item.iItem = lpLVItem->iItem;
6732 dispInfo.item.iSubItem = isubitem;
6733 if (lpLVItem->mask & LVIF_TEXT)
6735 if (lpLVItem->mask & LVIF_NORECOMPUTE)
6736 /* reset mask */
6737 dispInfo.item.mask &= ~(LVIF_TEXT | LVIF_NORECOMPUTE);
6738 else
6740 dispInfo.item.pszText = lpLVItem->pszText;
6741 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6744 if (lpLVItem->mask & LVIF_STATE)
6745 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
6746 /* could be zeroed on LVIF_NORECOMPUTE case */
6747 if (dispInfo.item.mask)
6749 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6750 dispInfo.item.stateMask = lpLVItem->stateMask;
6751 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6753 /* full size structure expected - _WIN32IE >= 0x560 */
6754 *lpLVItem = dispInfo.item;
6756 else if (lpLVItem->mask & LVIF_INDENT)
6758 /* indent member expected - _WIN32IE >= 0x300 */
6759 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
6761 else
6763 /* minimal structure expected */
6764 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
6766 lpLVItem->mask = mask;
6767 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
6771 /* make sure lParam is zeroed out */
6772 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
6774 /* callback marked pointer required here */
6775 if ((lpLVItem->mask & LVIF_TEXT) && (lpLVItem->mask & LVIF_NORECOMPUTE))
6776 lpLVItem->pszText = LPSTR_TEXTCALLBACKW;
6778 /* we store only a little state, so if we're not asked, we're done */
6779 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
6781 /* if focus is handled by us, report it */
6782 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6784 lpLVItem->state &= ~LVIS_FOCUSED;
6785 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6786 lpLVItem->state |= LVIS_FOCUSED;
6789 /* and do the same for selection, if we handle it */
6790 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6792 lpLVItem->state &= ~LVIS_SELECTED;
6793 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6794 lpLVItem->state |= LVIS_SELECTED;
6797 return TRUE;
6800 /* find the item and subitem structures before we proceed */
6801 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
6802 lpItem = DPA_GetPtr(hdpaSubItems, 0);
6803 assert (lpItem);
6805 if (isubitem)
6807 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
6808 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
6809 if (!lpSubItem)
6811 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
6812 isubitem = 0;
6815 else
6816 pItemHdr = &lpItem->hdr;
6818 /* Do we need to query the state from the app? */
6819 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
6821 dispInfo.item.mask |= LVIF_STATE;
6822 dispInfo.item.stateMask = infoPtr->uCallbackMask;
6825 /* Do we need to enquire about the image? */
6826 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
6827 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
6829 dispInfo.item.mask |= LVIF_IMAGE;
6830 dispInfo.item.iImage = I_IMAGECALLBACK;
6833 /* Only items support indentation */
6834 if ((lpLVItem->mask & LVIF_INDENT) && lpItem->iIndent == I_INDENTCALLBACK &&
6835 (isubitem == 0))
6837 dispInfo.item.mask |= LVIF_INDENT;
6838 dispInfo.item.iIndent = I_INDENTCALLBACK;
6841 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
6842 if ((lpLVItem->mask & LVIF_TEXT) && !(lpLVItem->mask & LVIF_NORECOMPUTE) &&
6843 !is_text(pItemHdr->pszText))
6845 dispInfo.item.mask |= LVIF_TEXT;
6846 dispInfo.item.pszText = lpLVItem->pszText;
6847 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
6848 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
6849 *dispInfo.item.pszText = '\0';
6852 /* If we don't have all the requested info, query the application */
6853 if (dispInfo.item.mask)
6855 dispInfo.item.iItem = lpLVItem->iItem;
6856 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
6857 dispInfo.item.lParam = lpItem->lParam;
6858 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
6859 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
6862 /* we should not store values for subitems */
6863 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
6865 /* Now, handle the iImage field */
6866 if (dispInfo.item.mask & LVIF_IMAGE)
6868 lpLVItem->iImage = dispInfo.item.iImage;
6869 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
6870 pItemHdr->iImage = dispInfo.item.iImage;
6872 else if (lpLVItem->mask & LVIF_IMAGE)
6874 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
6875 lpLVItem->iImage = pItemHdr->iImage;
6876 else
6877 lpLVItem->iImage = 0;
6880 /* The pszText field */
6881 if (dispInfo.item.mask & LVIF_TEXT)
6883 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
6884 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
6886 lpLVItem->pszText = dispInfo.item.pszText;
6888 else if (lpLVItem->mask & LVIF_TEXT)
6890 /* if LVN_GETDISPINFO's disabled with LVIF_NORECOMPUTE return callback placeholder */
6891 if (isW || !is_text(pItemHdr->pszText)) lpLVItem->pszText = pItemHdr->pszText;
6892 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
6895 /* Next is the lParam field */
6896 if (dispInfo.item.mask & LVIF_PARAM)
6898 lpLVItem->lParam = dispInfo.item.lParam;
6899 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
6900 lpItem->lParam = dispInfo.item.lParam;
6902 else if (lpLVItem->mask & LVIF_PARAM)
6903 lpLVItem->lParam = lpItem->lParam;
6905 /* if this is a subitem, we're done */
6906 if (isubitem) return TRUE;
6908 /* ... the state field (this one is different due to uCallbackmask) */
6909 if (lpLVItem->mask & LVIF_STATE)
6911 lpLVItem->state = lpItem->state & lpLVItem->stateMask;
6912 if (dispInfo.item.mask & LVIF_STATE)
6914 lpLVItem->state &= ~dispInfo.item.stateMask;
6915 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
6917 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
6919 lpLVItem->state &= ~LVIS_FOCUSED;
6920 if (infoPtr->nFocusedItem == lpLVItem->iItem)
6921 lpLVItem->state |= LVIS_FOCUSED;
6923 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
6925 lpLVItem->state &= ~LVIS_SELECTED;
6926 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
6927 lpLVItem->state |= LVIS_SELECTED;
6931 /* and last, but not least, the indent field */
6932 if (dispInfo.item.mask & LVIF_INDENT)
6934 lpLVItem->iIndent = dispInfo.item.iIndent;
6935 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && lpItem->iIndent == I_INDENTCALLBACK)
6936 lpItem->iIndent = dispInfo.item.iIndent;
6938 else if (lpLVItem->mask & LVIF_INDENT)
6940 lpLVItem->iIndent = lpItem->iIndent;
6943 return TRUE;
6946 /***
6947 * DESCRIPTION:
6948 * Retrieves item attributes.
6950 * PARAMETER(S):
6951 * [I] hwnd : window handle
6952 * [IO] lpLVItem : item info
6953 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
6954 * if FALSE, then lpLVItem is a LPLVITEMA.
6956 * NOTE:
6957 * This is the external 'GetItem' interface -- it properly copies
6958 * the text in the provided buffer.
6960 * RETURN:
6961 * SUCCESS : TRUE
6962 * FAILURE : FALSE
6964 static BOOL LISTVIEW_GetItemExtT(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
6966 LPWSTR pszText;
6967 BOOL bResult;
6969 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
6970 return FALSE;
6972 pszText = lpLVItem->pszText;
6973 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
6974 if (bResult && (lpLVItem->mask & LVIF_TEXT) && lpLVItem->pszText != pszText)
6976 if (lpLVItem->pszText != LPSTR_TEXTCALLBACKW)
6977 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
6978 else
6979 pszText = LPSTR_TEXTCALLBACKW;
6981 lpLVItem->pszText = pszText;
6983 return bResult;
6987 /***
6988 * DESCRIPTION:
6989 * Retrieves the position (upper-left) of the listview control item.
6990 * Note that for LVS_ICON style, the upper-left is that of the icon
6991 * and not the bounding box.
6993 * PARAMETER(S):
6994 * [I] infoPtr : valid pointer to the listview structure
6995 * [I] nItem : item index
6996 * [O] lpptPosition : coordinate information
6998 * RETURN:
6999 * SUCCESS : TRUE
7000 * FAILURE : FALSE
7002 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
7004 POINT Origin;
7006 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
7008 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7010 LISTVIEW_GetOrigin(infoPtr, &Origin);
7011 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
7013 if (infoPtr->uView == LV_VIEW_ICON)
7015 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7016 lpptPosition->y += ICON_TOP_PADDING;
7018 lpptPosition->x += Origin.x;
7019 lpptPosition->y += Origin.y;
7021 TRACE (" lpptPosition=%s\n", wine_dbgstr_point(lpptPosition));
7022 return TRUE;
7026 /***
7027 * DESCRIPTION:
7028 * Retrieves the bounding rectangle for a listview control item.
7030 * PARAMETER(S):
7031 * [I] infoPtr : valid pointer to the listview structure
7032 * [I] nItem : item index
7033 * [IO] lprc : bounding rectangle coordinates
7034 * lprc->left specifies the portion of the item for which the bounding
7035 * rectangle will be retrieved.
7037 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
7038 * including the icon and label.
7040 * * For LVS_ICON
7041 * * Experiment shows that native control returns:
7042 * * width = min (48, length of text line)
7043 * * .left = position.x - (width - iconsize.cx)/2
7044 * * .right = .left + width
7045 * * height = #lines of text * ntmHeight + icon height + 8
7046 * * .top = position.y - 2
7047 * * .bottom = .top + height
7048 * * separation between items .y = itemSpacing.cy - height
7049 * * .x = itemSpacing.cx - width
7050 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
7052 * * For LVS_ICON
7053 * * Experiment shows that native control returns:
7054 * * width = iconSize.cx + 16
7055 * * .left = position.x - (width - iconsize.cx)/2
7056 * * .right = .left + width
7057 * * height = iconSize.cy + 4
7058 * * .top = position.y - 2
7059 * * .bottom = .top + height
7060 * * separation between items .y = itemSpacing.cy - height
7061 * * .x = itemSpacing.cx - width
7062 * LVIR_LABEL Returns the bounding rectangle of the item text.
7064 * * For LVS_ICON
7065 * * Experiment shows that native control returns:
7066 * * width = text length
7067 * * .left = position.x - width/2
7068 * * .right = .left + width
7069 * * height = ntmH * linecount + 2
7070 * * .top = position.y + iconSize.cy + 6
7071 * * .bottom = .top + height
7072 * * separation between items .y = itemSpacing.cy - height
7073 * * .x = itemSpacing.cx - width
7074 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
7075 * rectangles, but excludes columns in report view.
7077 * RETURN:
7078 * SUCCESS : TRUE
7079 * FAILURE : FALSE
7081 * NOTES
7082 * Note that the bounding rectangle of the label in the LVS_ICON view depends
7083 * upon whether the window has the focus currently and on whether the item
7084 * is the one with the focus. Ensure that the control's record of which
7085 * item has the focus agrees with the items' records.
7087 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
7089 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7090 BOOL doLabel = TRUE, oversizedBox = FALSE;
7091 POINT Position, Origin;
7092 LVITEMW lvItem;
7093 LONG mode;
7095 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
7097 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7099 LISTVIEW_GetOrigin(infoPtr, &Origin);
7100 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7102 /* Be smart and try to figure out the minimum we have to do */
7103 if (lprc->left == LVIR_ICON) doLabel = FALSE;
7104 if (infoPtr->uView == LV_VIEW_DETAILS && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
7105 if (infoPtr->uView == LV_VIEW_ICON && lprc->left != LVIR_ICON &&
7106 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
7107 oversizedBox = TRUE;
7109 /* get what we need from the item before hand, so we make
7110 * only one request. This can speed up things, if data
7111 * is stored on the app side */
7112 lvItem.mask = 0;
7113 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7114 if (doLabel) lvItem.mask |= LVIF_TEXT;
7115 lvItem.iItem = nItem;
7116 lvItem.iSubItem = 0;
7117 lvItem.pszText = szDispText;
7118 lvItem.cchTextMax = DISP_TEXT_SIZE;
7119 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
7120 /* we got the state already up, simulate it here, to avoid a reget */
7121 if (infoPtr->uView == LV_VIEW_ICON && (lprc->left != LVIR_ICON))
7123 lvItem.mask |= LVIF_STATE;
7124 lvItem.stateMask = LVIS_FOCUSED;
7125 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
7128 if (infoPtr->uView == LV_VIEW_DETAILS && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
7129 lprc->left = LVIR_BOUNDS;
7131 mode = lprc->left;
7132 switch(lprc->left)
7134 case LVIR_ICON:
7135 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL, NULL);
7136 break;
7138 case LVIR_LABEL:
7139 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, NULL, lprc);
7140 break;
7142 case LVIR_BOUNDS:
7143 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL, NULL);
7144 break;
7146 case LVIR_SELECTBOUNDS:
7147 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, lprc, NULL, NULL, NULL);
7148 break;
7150 default:
7151 WARN("Unknown value: %d\n", lprc->left);
7152 return FALSE;
7155 if (infoPtr->uView == LV_VIEW_DETAILS)
7157 if (mode != LVIR_BOUNDS)
7158 OffsetRect(lprc, Origin.x + LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left,
7159 Position.y + Origin.y);
7160 else
7161 OffsetRect(lprc, Origin.x, Position.y + Origin.y);
7163 else
7164 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
7166 TRACE(" rect=%s\n", wine_dbgstr_rect(lprc));
7168 return TRUE;
7171 /***
7172 * DESCRIPTION:
7173 * Retrieves the spacing between listview control items.
7175 * PARAMETER(S):
7176 * [I] infoPtr : valid pointer to the listview structure
7177 * [IO] lprc : rectangle to receive the output
7178 * on input, lprc->top = nSubItem
7179 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
7181 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
7182 * not only those of the first column.
7184 * RETURN:
7185 * TRUE: success
7186 * FALSE: failure
7188 static BOOL LISTVIEW_GetSubItemRect(const LISTVIEW_INFO *infoPtr, INT item, LPRECT lprc)
7190 RECT rect = { 0, 0, 0, 0 };
7191 POINT origin;
7192 INT y;
7194 if (!lprc) return FALSE;
7196 TRACE("(item=%d, subitem=%d, type=%d)\n", item, lprc->top, lprc->left);
7197 /* Subitem of '0' means item itself, and this works for all control view modes */
7198 if (lprc->top == 0)
7199 return LISTVIEW_GetItemRect(infoPtr, item, lprc);
7201 if (infoPtr->uView != LV_VIEW_DETAILS) return FALSE;
7203 LISTVIEW_GetOrigin(infoPtr, &origin);
7204 /* this works for any item index, no matter if it exists or not */
7205 y = item * infoPtr->nItemHeight + origin.y;
7207 if (infoPtr->hwndHeader && SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, lprc->top, (LPARAM)&rect))
7209 rect.top = 0;
7210 rect.bottom = infoPtr->nItemHeight;
7212 else
7214 /* Native implementation is broken for this case and garbage is left for left and right fields,
7215 we zero them to get predictable output */
7216 lprc->left = lprc->right = lprc->top = 0;
7217 lprc->bottom = infoPtr->nItemHeight;
7218 OffsetRect(lprc, origin.x, y);
7219 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7220 return TRUE;
7223 switch (lprc->left)
7225 case LVIR_ICON:
7227 /* it doesn't matter if main item actually has an icon, if imagelist is set icon width is returned */
7228 if (infoPtr->himlSmall)
7229 rect.right = rect.left + infoPtr->iconSize.cx;
7230 else
7231 rect.right = rect.left;
7233 rect.bottom = rect.top + infoPtr->iconSize.cy;
7234 break;
7236 case LVIR_LABEL:
7237 case LVIR_BOUNDS:
7238 break;
7240 default:
7241 ERR("Unknown bounds=%d\n", lprc->left);
7242 return FALSE;
7245 OffsetRect(&rect, origin.x, y);
7246 *lprc = rect;
7247 TRACE("return rect %s\n", wine_dbgstr_rect(lprc));
7249 return TRUE;
7252 /***
7253 * DESCRIPTION:
7254 * Retrieves the spacing between listview control items.
7256 * PARAMETER(S):
7257 * [I] infoPtr : valid pointer to the listview structure
7258 * [I] bSmall : flag for small or large icon
7260 * RETURN:
7261 * Horizontal + vertical spacing
7263 static LONG LISTVIEW_GetItemSpacing(const LISTVIEW_INFO *infoPtr, BOOL bSmall)
7265 LONG lResult;
7267 if (!bSmall)
7269 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
7271 else
7273 if (infoPtr->uView == LV_VIEW_ICON)
7274 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
7275 else
7276 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
7278 return lResult;
7281 /***
7282 * DESCRIPTION:
7283 * Retrieves the state of a listview control item.
7285 * PARAMETER(S):
7286 * [I] infoPtr : valid pointer to the listview structure
7287 * [I] nItem : item index
7288 * [I] uMask : state mask
7290 * RETURN:
7291 * State specified by the mask.
7293 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
7295 LVITEMW lvItem;
7297 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7299 lvItem.iItem = nItem;
7300 lvItem.iSubItem = 0;
7301 lvItem.mask = LVIF_STATE;
7302 lvItem.stateMask = uMask;
7303 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
7305 return lvItem.state & uMask;
7308 /***
7309 * DESCRIPTION:
7310 * Retrieves the text of a listview control item or subitem.
7312 * PARAMETER(S):
7313 * [I] hwnd : window handle
7314 * [I] nItem : item index
7315 * [IO] lpLVItem : item information
7316 * [I] isW : TRUE if lpLVItem is Unicode
7318 * RETURN:
7319 * SUCCESS : string length
7320 * FAILURE : 0
7322 static INT LISTVIEW_GetItemTextT(const LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
7324 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
7326 lpLVItem->mask = LVIF_TEXT;
7327 lpLVItem->iItem = nItem;
7328 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
7330 return textlenT(lpLVItem->pszText, isW);
7333 /***
7334 * DESCRIPTION:
7335 * Searches for an item based on properties + relationships.
7337 * PARAMETER(S):
7338 * [I] infoPtr : valid pointer to the listview structure
7339 * [I] nItem : item index
7340 * [I] uFlags : relationship flag
7342 * RETURN:
7343 * SUCCESS : item index
7344 * FAILURE : -1
7346 static INT LISTVIEW_GetNextItem(const LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
7348 UINT uMask = 0;
7349 LVFINDINFOW lvFindInfo;
7350 INT nCountPerColumn;
7351 INT nCountPerRow;
7352 INT i;
7354 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
7355 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
7357 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
7359 if (uFlags & LVNI_CUT)
7360 uMask |= LVIS_CUT;
7362 if (uFlags & LVNI_DROPHILITED)
7363 uMask |= LVIS_DROPHILITED;
7365 if (uFlags & LVNI_FOCUSED)
7366 uMask |= LVIS_FOCUSED;
7368 if (uFlags & LVNI_SELECTED)
7369 uMask |= LVIS_SELECTED;
7371 /* if we're asked for the focused item, that's only one,
7372 * so it's worth optimizing */
7373 if (uFlags & LVNI_FOCUSED)
7375 if ((LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) != uMask) return -1;
7376 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
7379 if (uFlags & LVNI_ABOVE)
7381 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7383 while (nItem >= 0)
7385 nItem--;
7386 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7387 return nItem;
7390 else
7392 /* Special case for autoarrange - move 'til the top of a list */
7393 if (is_autoarrange(infoPtr))
7395 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7396 while (nItem - nCountPerRow >= 0)
7398 nItem -= nCountPerRow;
7399 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7400 return nItem;
7402 return -1;
7404 lvFindInfo.flags = LVFI_NEARESTXY;
7405 lvFindInfo.vkDirection = VK_UP;
7406 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7407 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7409 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7410 return nItem;
7414 else if (uFlags & LVNI_BELOW)
7416 if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
7418 while (nItem < infoPtr->nItemCount)
7420 nItem++;
7421 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7422 return nItem;
7425 else
7427 /* Special case for autoarrange - move 'til the bottom of a list */
7428 if (is_autoarrange(infoPtr))
7430 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7431 while (nItem + nCountPerRow < infoPtr->nItemCount )
7433 nItem += nCountPerRow;
7434 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7435 return nItem;
7437 return -1;
7439 lvFindInfo.flags = LVFI_NEARESTXY;
7440 lvFindInfo.vkDirection = VK_DOWN;
7441 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7442 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7444 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7445 return nItem;
7449 else if (uFlags & LVNI_TOLEFT)
7451 if (infoPtr->uView == LV_VIEW_LIST)
7453 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7454 while (nItem - nCountPerColumn >= 0)
7456 nItem -= nCountPerColumn;
7457 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7458 return nItem;
7461 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7463 /* Special case for autoarrange - move 'til the beginning of a row */
7464 if (is_autoarrange(infoPtr))
7466 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7467 while (nItem % nCountPerRow > 0)
7469 nItem --;
7470 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7471 return nItem;
7473 return -1;
7475 lvFindInfo.flags = LVFI_NEARESTXY;
7476 lvFindInfo.vkDirection = VK_LEFT;
7477 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7478 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7480 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7481 return nItem;
7485 else if (uFlags & LVNI_TORIGHT)
7487 if (infoPtr->uView == LV_VIEW_LIST)
7489 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
7490 while (nItem + nCountPerColumn < infoPtr->nItemCount)
7492 nItem += nCountPerColumn;
7493 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7494 return nItem;
7497 else if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7499 /* Special case for autoarrange - move 'til the end of a row */
7500 if (is_autoarrange(infoPtr))
7502 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
7503 while (nItem % nCountPerRow < nCountPerRow - 1 )
7505 nItem ++;
7506 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7507 return nItem;
7509 return -1;
7511 lvFindInfo.flags = LVFI_NEARESTXY;
7512 lvFindInfo.vkDirection = VK_RIGHT;
7513 LISTVIEW_GetItemPosition(infoPtr, nItem, &lvFindInfo.pt);
7514 while ((nItem = LISTVIEW_FindItemW(infoPtr, nItem, &lvFindInfo)) != -1)
7516 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
7517 return nItem;
7521 else
7523 nItem++;
7525 /* search by index */
7526 for (i = nItem; i < infoPtr->nItemCount; i++)
7528 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
7529 return i;
7533 return -1;
7536 /* LISTVIEW_GetNumberOfWorkAreas */
7538 /***
7539 * DESCRIPTION:
7540 * Retrieves the origin coordinates when in icon or small icon display mode.
7542 * PARAMETER(S):
7543 * [I] infoPtr : valid pointer to the listview structure
7544 * [O] lpptOrigin : coordinate information
7546 * RETURN:
7547 * None.
7549 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
7551 INT nHorzPos = 0, nVertPos = 0;
7552 SCROLLINFO scrollInfo;
7554 scrollInfo.cbSize = sizeof(SCROLLINFO);
7555 scrollInfo.fMask = SIF_POS;
7557 if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
7558 nHorzPos = scrollInfo.nPos;
7559 if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
7560 nVertPos = scrollInfo.nPos;
7562 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
7564 lpptOrigin->x = infoPtr->rcList.left;
7565 lpptOrigin->y = infoPtr->rcList.top;
7566 if (infoPtr->uView == LV_VIEW_LIST)
7567 nHorzPos *= infoPtr->nItemWidth;
7568 else if (infoPtr->uView == LV_VIEW_DETAILS)
7569 nVertPos *= infoPtr->nItemHeight;
7571 lpptOrigin->x -= nHorzPos;
7572 lpptOrigin->y -= nVertPos;
7574 TRACE(" origin=%s\n", wine_dbgstr_point(lpptOrigin));
7577 /***
7578 * DESCRIPTION:
7579 * Retrieves the width of a string.
7581 * PARAMETER(S):
7582 * [I] hwnd : window handle
7583 * [I] lpszText : text string to process
7584 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
7586 * RETURN:
7587 * SUCCESS : string width (in pixels)
7588 * FAILURE : zero
7590 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
7592 SIZE stringSize;
7594 stringSize.cx = 0;
7595 if (is_text(lpszText))
7597 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
7598 HDC hdc = GetDC(infoPtr->hwndSelf);
7599 HFONT hOldFont = SelectObject(hdc, hFont);
7601 if (isW)
7602 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
7603 else
7604 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
7605 SelectObject(hdc, hOldFont);
7606 ReleaseDC(infoPtr->hwndSelf, hdc);
7608 return stringSize.cx;
7611 /***
7612 * DESCRIPTION:
7613 * Determines which listview item is located at the specified position.
7615 * PARAMETER(S):
7616 * [I] infoPtr : valid pointer to the listview structure
7617 * [IO] lpht : hit test information
7618 * [I] subitem : fill out iSubItem.
7619 * [I] select : return the index only if the hit selects the item
7621 * NOTE:
7622 * (mm 20001022): We must not allow iSubItem to be touched, for
7623 * an app might pass only a structure with space up to iItem!
7624 * (MS Office 97 does that for instance in the file open dialog)
7626 * RETURN:
7627 * SUCCESS : item index
7628 * FAILURE : -1
7630 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
7632 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
7633 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
7634 POINT Origin, Position, opt;
7635 BOOL is_fullrow;
7636 LVITEMW lvItem;
7637 ITERATOR i;
7638 INT iItem;
7640 TRACE("(pt=%s, subitem=%d, select=%d)\n", wine_dbgstr_point(&lpht->pt), subitem, select);
7642 lpht->flags = 0;
7643 lpht->iItem = -1;
7644 if (subitem) lpht->iSubItem = 0;
7646 LISTVIEW_GetOrigin(infoPtr, &Origin);
7648 /* set whole list relation flags */
7649 if (subitem && infoPtr->uView == LV_VIEW_DETAILS)
7651 /* LVM_SUBITEMHITTEST checks left bound of possible client area */
7652 if (infoPtr->rcList.left > lpht->pt.x && Origin.x < lpht->pt.x)
7653 lpht->flags |= LVHT_TOLEFT;
7655 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7656 opt.y = lpht->pt.y + infoPtr->rcList.top;
7657 else
7658 opt.y = lpht->pt.y;
7660 if (infoPtr->rcList.bottom < opt.y)
7661 lpht->flags |= LVHT_BELOW;
7663 else
7665 if (infoPtr->rcList.left > lpht->pt.x)
7666 lpht->flags |= LVHT_TOLEFT;
7667 else if (infoPtr->rcList.right < lpht->pt.x)
7668 lpht->flags |= LVHT_TORIGHT;
7670 if (infoPtr->rcList.top > lpht->pt.y)
7671 lpht->flags |= LVHT_ABOVE;
7672 else if (infoPtr->rcList.bottom < lpht->pt.y)
7673 lpht->flags |= LVHT_BELOW;
7676 /* even if item is invalid try to find subitem */
7677 if (infoPtr->uView == LV_VIEW_DETAILS && subitem)
7679 RECT *pRect;
7680 INT j;
7682 opt.x = lpht->pt.x - Origin.x;
7684 lpht->iSubItem = -1;
7685 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
7687 pRect = &LISTVIEW_GetColumnInfo(infoPtr, j)->rcHeader;
7689 if ((opt.x >= pRect->left) && (opt.x < pRect->right))
7691 lpht->iSubItem = j;
7692 break;
7695 TRACE("lpht->iSubItem=%d\n", lpht->iSubItem);
7697 /* if we're outside horizontal columns bounds there's nothing to test further */
7698 if (lpht->iSubItem == -1)
7700 lpht->iItem = -1;
7701 lpht->flags = LVHT_NOWHERE;
7702 return -1;
7706 TRACE("lpht->flags=0x%x\n", lpht->flags);
7707 if (lpht->flags) return -1;
7709 lpht->flags |= LVHT_NOWHERE;
7711 /* first deal with the large items */
7712 rcSearch.left = lpht->pt.x;
7713 rcSearch.top = lpht->pt.y;
7714 rcSearch.right = rcSearch.left + 1;
7715 rcSearch.bottom = rcSearch.top + 1;
7717 iterator_frameditems(&i, infoPtr, &rcSearch);
7718 iterator_next(&i); /* go to first item in the sequence */
7719 iItem = i.nItem;
7720 iterator_destroy(&i);
7722 TRACE("lpht->iItem=%d\n", iItem);
7723 if (iItem == -1) return -1;
7725 lvItem.mask = LVIF_STATE | LVIF_TEXT;
7726 if (infoPtr->uView == LV_VIEW_DETAILS) lvItem.mask |= LVIF_INDENT;
7727 lvItem.stateMask = LVIS_STATEIMAGEMASK;
7728 if (infoPtr->uView == LV_VIEW_ICON) lvItem.stateMask |= LVIS_FOCUSED;
7729 lvItem.iItem = iItem;
7730 lvItem.iSubItem = subitem ? lpht->iSubItem : 0;
7731 lvItem.pszText = szDispText;
7732 lvItem.cchTextMax = DISP_TEXT_SIZE;
7733 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
7734 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
7736 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7737 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
7738 opt.x = lpht->pt.x - Position.x - Origin.x;
7740 if (lpht->pt.y < infoPtr->rcList.top && lpht->pt.y >= 0)
7741 opt.y = lpht->pt.y - Position.y - Origin.y + infoPtr->rcList.top;
7742 else
7743 opt.y = lpht->pt.y - Position.y - Origin.y;
7745 if (infoPtr->uView == LV_VIEW_DETAILS)
7747 rcBounds = rcBox;
7748 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
7749 opt.x = lpht->pt.x - Origin.x;
7751 else
7753 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7754 UnionRect(&rcBounds, &rcBounds, &rcState);
7756 TRACE("rcBounds=%s\n", wine_dbgstr_rect(&rcBounds));
7757 if (!PtInRect(&rcBounds, opt)) return -1;
7759 /* That's a special case - row rectangle is used as item rectangle and
7760 returned flags contain all item parts. */
7761 is_fullrow = (infoPtr->uView == LV_VIEW_DETAILS) && ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || (infoPtr->dwStyle & LVS_OWNERDRAWFIXED));
7763 if (PtInRect(&rcIcon, opt))
7764 lpht->flags |= LVHT_ONITEMICON;
7765 else if (PtInRect(&rcLabel, opt))
7766 lpht->flags |= LVHT_ONITEMLABEL;
7767 else if (infoPtr->himlState && PtInRect(&rcState, opt))
7768 lpht->flags |= LVHT_ONITEMSTATEICON;
7769 if (is_fullrow && !(lpht->flags & LVHT_ONITEM))
7771 lpht->flags = LVHT_ONITEM | LVHT_ABOVE;
7773 if (lpht->flags & LVHT_ONITEM)
7774 lpht->flags &= ~LVHT_NOWHERE;
7775 TRACE("lpht->flags=0x%x\n", lpht->flags);
7777 if (select && !is_fullrow)
7779 if (infoPtr->uView == LV_VIEW_DETAILS)
7781 /* get main item bounds */
7782 lvItem.iSubItem = 0;
7783 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, NULL, &rcIcon, &rcState, &rcLabel);
7784 UnionRect(&rcBounds, &rcIcon, &rcLabel);
7785 UnionRect(&rcBounds, &rcBounds, &rcState);
7787 if (!PtInRect(&rcBounds, opt)) iItem = -1;
7789 return lpht->iItem = iItem;
7792 /***
7793 * DESCRIPTION:
7794 * Inserts a new item in the listview control.
7796 * PARAMETER(S):
7797 * [I] infoPtr : valid pointer to the listview structure
7798 * [I] lpLVItem : item information
7799 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
7801 * RETURN:
7802 * SUCCESS : new item index
7803 * FAILURE : -1
7805 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
7807 INT nItem;
7808 HDPA hdpaSubItems;
7809 NMLISTVIEW nmlv;
7810 ITEM_INFO *lpItem;
7811 ITEM_ID *lpID;
7812 BOOL is_sorted, has_changed;
7813 LVITEMW item;
7814 HWND hwndSelf = infoPtr->hwndSelf;
7816 TRACE("(item=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
7818 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
7820 /* make sure it's an item, and not a subitem; cannot insert a subitem */
7821 if (!lpLVItem || lpLVItem->iSubItem) return -1;
7823 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
7825 if (!(lpItem = Alloc(sizeof(ITEM_INFO)))) return -1;
7827 /* insert item in listview control data structure */
7828 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
7829 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
7831 /* link with id struct */
7832 if (!(lpID = Alloc(sizeof(ITEM_ID)))) goto fail;
7833 lpItem->id = lpID;
7834 lpID->item = hdpaSubItems;
7835 lpID->id = get_next_itemid(infoPtr);
7836 if ( DPA_InsertPtr(infoPtr->hdpaItemIds, infoPtr->nItemCount, lpID) == -1) goto fail;
7838 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
7839 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
7841 if (lpLVItem->iItem < 0 && !is_sorted) return -1;
7843 /* calculate new item index */
7844 if (is_sorted)
7846 HDPA hItem;
7847 ITEM_INFO *item_s;
7848 INT i = 0, cmpv;
7849 WCHAR *textW;
7851 textW = textdupTtoW(lpLVItem->pszText, isW);
7853 while (i < infoPtr->nItemCount)
7855 hItem = DPA_GetPtr( infoPtr->hdpaItems, i);
7856 item_s = DPA_GetPtr(hItem, 0);
7858 cmpv = textcmpWT(item_s->hdr.pszText, textW, TRUE);
7859 if (infoPtr->dwStyle & LVS_SORTDESCENDING) cmpv *= -1;
7861 if (cmpv >= 0) break;
7862 i++;
7865 textfreeT(textW, isW);
7867 nItem = i;
7869 else
7870 nItem = min(lpLVItem->iItem, infoPtr->nItemCount);
7872 TRACE("inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
7873 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
7874 if (nItem == -1) goto fail;
7875 infoPtr->nItemCount++;
7877 /* shift indices first so they don't get tangled */
7878 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
7880 /* set the item attributes */
7881 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
7883 /* full size structure expected - _WIN32IE >= 0x560 */
7884 item = *lpLVItem;
7886 else if (lpLVItem->mask & LVIF_INDENT)
7888 /* indent member expected - _WIN32IE >= 0x300 */
7889 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
7891 else
7893 /* minimal structure expected */
7894 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
7896 item.iItem = nItem;
7897 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
7899 if (item.mask & LVIF_STATE)
7901 item.stateMask |= LVIS_STATEIMAGEMASK;
7902 item.state &= ~LVIS_STATEIMAGEMASK;
7903 item.state |= INDEXTOSTATEIMAGEMASK(1);
7905 else
7907 item.mask |= LVIF_STATE;
7908 item.stateMask = LVIS_STATEIMAGEMASK;
7909 item.state = INDEXTOSTATEIMAGEMASK(1);
7913 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
7915 /* make room for the position, if we are in the right mode */
7916 if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
7918 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
7919 goto undo;
7920 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
7922 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
7923 goto undo;
7927 /* send LVN_INSERTITEM notification */
7928 memset(&nmlv, 0, sizeof(NMLISTVIEW));
7929 nmlv.iItem = nItem;
7930 nmlv.lParam = lpItem->lParam;
7931 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
7932 if (!IsWindow(hwndSelf))
7933 return -1;
7935 /* align items (set position of each item) */
7936 if (infoPtr->uView == LV_VIEW_SMALLICON || infoPtr->uView == LV_VIEW_ICON)
7938 POINT pt;
7940 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
7941 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
7942 else
7943 LISTVIEW_NextIconPosTop(infoPtr, &pt);
7945 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
7948 /* now is the invalidation fun */
7949 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
7950 return nItem;
7952 undo:
7953 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
7954 LISTVIEW_ShiftFocus(infoPtr, infoPtr->nFocusedItem, nItem, -1);
7955 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
7956 infoPtr->nItemCount--;
7957 fail:
7958 DPA_DeletePtr(hdpaSubItems, 0);
7959 DPA_Destroy (hdpaSubItems);
7960 Free (lpItem);
7961 return -1;
7964 /***
7965 * DESCRIPTION:
7966 * Checks item visibility.
7968 * PARAMETER(S):
7969 * [I] infoPtr : valid pointer to the listview structure
7970 * [I] nFirst : item index to check for
7972 * RETURN:
7973 * Item visible : TRUE
7974 * Item invisible or failure : FALSE
7976 static BOOL LISTVIEW_IsItemVisible(const LISTVIEW_INFO *infoPtr, INT nItem)
7978 POINT Origin, Position;
7979 RECT rcItem;
7980 HDC hdc;
7981 BOOL ret;
7983 TRACE("nItem=%d\n", nItem);
7985 if (nItem < 0 || nItem >= DPA_GetPtrCount(infoPtr->hdpaItems)) return FALSE;
7987 LISTVIEW_GetOrigin(infoPtr, &Origin);
7988 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
7989 rcItem.left = Position.x + Origin.x;
7990 rcItem.top = Position.y + Origin.y;
7991 rcItem.right = rcItem.left + infoPtr->nItemWidth;
7992 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
7994 hdc = GetDC(infoPtr->hwndSelf);
7995 if (!hdc) return FALSE;
7996 ret = RectVisible(hdc, &rcItem);
7997 ReleaseDC(infoPtr->hwndSelf, hdc);
7999 return ret;
8002 /***
8003 * DESCRIPTION:
8004 * Redraws a range of items.
8006 * PARAMETER(S):
8007 * [I] infoPtr : valid pointer to the listview structure
8008 * [I] nFirst : first item
8009 * [I] nLast : last item
8011 * RETURN:
8012 * SUCCESS : TRUE
8013 * FAILURE : FALSE
8015 static BOOL LISTVIEW_RedrawItems(const LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
8017 INT i;
8019 for (i = max(nFirst, 0); i <= min(nLast, infoPtr->nItemCount - 1); i++)
8020 LISTVIEW_InvalidateItem(infoPtr, i);
8022 return TRUE;
8025 /***
8026 * DESCRIPTION:
8027 * Scroll the content of a listview.
8029 * PARAMETER(S):
8030 * [I] infoPtr : valid pointer to the listview structure
8031 * [I] dx : horizontal scroll amount in pixels
8032 * [I] dy : vertical scroll amount in pixels
8034 * RETURN:
8035 * SUCCESS : TRUE
8036 * FAILURE : FALSE
8038 * COMMENTS:
8039 * If the control is in report view (LV_VIEW_DETAILS) the control can
8040 * be scrolled only in line increments. "dy" will be rounded to the
8041 * nearest number of pixels that are a whole line. Ex: if line height
8042 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
8043 * is passed, then the scroll will be 0. (per MSDN 7/2002)
8045 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
8047 switch(infoPtr->uView) {
8048 case LV_VIEW_DETAILS:
8049 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
8050 dy /= infoPtr->nItemHeight;
8051 break;
8052 case LV_VIEW_LIST:
8053 if (dy != 0) return FALSE;
8054 break;
8055 default: /* icon */
8056 break;
8059 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx);
8060 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy);
8062 return TRUE;
8065 /***
8066 * DESCRIPTION:
8067 * Sets the background color.
8069 * PARAMETER(S):
8070 * [I] infoPtr : valid pointer to the listview structure
8071 * [I] color : background color
8073 * RETURN:
8074 * SUCCESS : TRUE
8075 * FAILURE : FALSE
8077 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
8079 TRACE("(color=%x)\n", color);
8081 if(infoPtr->clrBk != color) {
8082 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8083 infoPtr->clrBk = color;
8084 if (color == CLR_NONE)
8085 infoPtr->hBkBrush = (HBRUSH)GetClassLongPtrW(infoPtr->hwndSelf, GCLP_HBRBACKGROUND);
8086 else
8088 infoPtr->hBkBrush = CreateSolidBrush(color);
8089 infoPtr->dwLvExStyle &= ~LVS_EX_TRANSPARENTBKGND;
8093 return TRUE;
8096 /* LISTVIEW_SetBkImage */
8098 /*** Helper for {Insert,Set}ColumnT *only* */
8099 static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
8100 const LVCOLUMNW *lpColumn, BOOL isW)
8102 if (lpColumn->mask & LVCF_FMT)
8104 /* format member is valid */
8105 lphdi->mask |= HDI_FORMAT;
8107 /* set text alignment (leftmost column must be left-aligned) */
8108 if (nColumn == 0 || (lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8109 lphdi->fmt |= HDF_LEFT;
8110 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_RIGHT)
8111 lphdi->fmt |= HDF_RIGHT;
8112 else if ((lpColumn->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_CENTER)
8113 lphdi->fmt |= HDF_CENTER;
8115 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
8116 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
8118 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
8120 lphdi->fmt |= HDF_IMAGE;
8121 lphdi->iImage = I_IMAGECALLBACK;
8124 if (lpColumn->fmt & LVCFMT_FIXED_WIDTH)
8125 lphdi->fmt |= HDF_FIXEDWIDTH;
8128 if (lpColumn->mask & LVCF_WIDTH)
8130 lphdi->mask |= HDI_WIDTH;
8131 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
8133 /* make it fill the remainder of the controls width */
8134 RECT rcHeader;
8135 INT item_index;
8137 for(item_index = 0; item_index < (nColumn - 1); item_index++)
8139 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
8140 lphdi->cxy += rcHeader.right - rcHeader.left;
8143 /* retrieve the layout of the header */
8144 GetClientRect(infoPtr->hwndSelf, &rcHeader);
8145 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, wine_dbgstr_rect(&rcHeader));
8147 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
8149 else
8150 lphdi->cxy = lpColumn->cx;
8153 if (lpColumn->mask & LVCF_TEXT)
8155 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
8156 lphdi->fmt |= HDF_STRING;
8157 lphdi->pszText = lpColumn->pszText;
8158 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
8161 if (lpColumn->mask & LVCF_IMAGE)
8163 lphdi->mask |= HDI_IMAGE;
8164 lphdi->iImage = lpColumn->iImage;
8167 if (lpColumn->mask & LVCF_ORDER)
8169 lphdi->mask |= HDI_ORDER;
8170 lphdi->iOrder = lpColumn->iOrder;
8175 /***
8176 * DESCRIPTION:
8177 * Inserts a new column.
8179 * PARAMETER(S):
8180 * [I] infoPtr : valid pointer to the listview structure
8181 * [I] nColumn : column index
8182 * [I] lpColumn : column information
8183 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
8185 * RETURN:
8186 * SUCCESS : new column index
8187 * FAILURE : -1
8189 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
8190 const LVCOLUMNW *lpColumn, BOOL isW)
8192 COLUMN_INFO *lpColumnInfo;
8193 INT nNewColumn;
8194 HDITEMW hdi;
8196 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8198 if (!lpColumn || nColumn < 0) return -1;
8199 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
8201 ZeroMemory(&hdi, sizeof(HDITEMW));
8202 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8205 * A mask not including LVCF_WIDTH turns into a mask of width, width 10
8206 * (can be seen in SPY) otherwise column never gets added.
8208 if (!(lpColumn->mask & LVCF_WIDTH)) {
8209 hdi.mask |= HDI_WIDTH;
8210 hdi.cxy = 10;
8214 * when the iSubItem is available Windows copies it to the header lParam. It seems
8215 * to happen only in LVM_INSERTCOLUMN - not in LVM_SETCOLUMN
8217 if (lpColumn->mask & LVCF_SUBITEM)
8219 hdi.mask |= HDI_LPARAM;
8220 hdi.lParam = lpColumn->iSubItem;
8223 /* create header if not present */
8224 LISTVIEW_CreateHeader(infoPtr);
8225 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle) &&
8226 (infoPtr->uView == LV_VIEW_DETAILS) && (WS_VISIBLE & infoPtr->dwStyle))
8228 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
8231 /* insert item in header control */
8232 nNewColumn = SendMessageW(infoPtr->hwndHeader,
8233 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
8234 nColumn, (LPARAM)&hdi);
8235 if (nNewColumn == -1) return -1;
8236 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
8238 /* create our own column info */
8239 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
8240 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
8242 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
8243 if (lpColumn->mask & LVCF_MINWIDTH) lpColumnInfo->cxMin = lpColumn->cxMin;
8244 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, nNewColumn, (LPARAM)&lpColumnInfo->rcHeader))
8245 goto fail;
8247 /* now we have to actually adjust the data */
8248 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
8250 SUBITEM_INFO *lpSubItem;
8251 HDPA hdpaSubItems;
8252 INT nItem, i;
8253 LVITEMW item;
8254 BOOL changed;
8256 item.iSubItem = nNewColumn;
8257 item.mask = LVIF_TEXT | LVIF_IMAGE;
8258 item.iImage = I_IMAGECALLBACK;
8259 item.pszText = LPSTR_TEXTCALLBACKW;
8261 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
8263 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, nItem);
8264 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
8266 lpSubItem = DPA_GetPtr(hdpaSubItems, i);
8267 if (lpSubItem->iSubItem >= nNewColumn)
8268 lpSubItem->iSubItem++;
8271 /* add new subitem for each item */
8272 item.iItem = nItem;
8273 set_sub_item(infoPtr, &item, isW, &changed);
8277 /* make space for the new column */
8278 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8279 LISTVIEW_UpdateItemSize(infoPtr);
8281 return nNewColumn;
8283 fail:
8284 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
8285 if (lpColumnInfo)
8287 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
8288 Free(lpColumnInfo);
8290 return -1;
8293 /***
8294 * DESCRIPTION:
8295 * Sets the attributes of a header item.
8297 * PARAMETER(S):
8298 * [I] infoPtr : valid pointer to the listview structure
8299 * [I] nColumn : column index
8300 * [I] lpColumn : column attributes
8301 * [I] isW: if TRUE, then lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
8303 * RETURN:
8304 * SUCCESS : TRUE
8305 * FAILURE : FALSE
8307 static BOOL LISTVIEW_SetColumnT(const LISTVIEW_INFO *infoPtr, INT nColumn,
8308 const LVCOLUMNW *lpColumn, BOOL isW)
8310 HDITEMW hdi, hdiget;
8311 BOOL bResult;
8313 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
8315 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8317 ZeroMemory(&hdi, sizeof(HDITEMW));
8318 if (lpColumn->mask & LVCF_FMT)
8320 hdi.mask |= HDI_FORMAT;
8321 hdiget.mask = HDI_FORMAT;
8322 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdiget))
8323 hdi.fmt = hdiget.fmt & HDF_STRING;
8325 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
8327 /* set header item attributes */
8328 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, nColumn, (LPARAM)&hdi);
8329 if (!bResult) return FALSE;
8331 if (lpColumn->mask & LVCF_FMT)
8333 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
8334 INT oldFmt = lpColumnInfo->fmt;
8336 lpColumnInfo->fmt = lpColumn->fmt;
8337 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
8339 if (infoPtr->uView == LV_VIEW_DETAILS) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
8343 if (lpColumn->mask & LVCF_MINWIDTH)
8344 LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin = lpColumn->cxMin;
8346 return TRUE;
8349 /***
8350 * DESCRIPTION:
8351 * Sets the column order array
8353 * PARAMETERS:
8354 * [I] infoPtr : valid pointer to the listview structure
8355 * [I] iCount : number of elements in column order array
8356 * [I] lpiArray : pointer to column order array
8358 * RETURN:
8359 * SUCCESS : TRUE
8360 * FAILURE : FALSE
8362 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
8364 if (!infoPtr->hwndHeader) return FALSE;
8365 infoPtr->colRectsDirty = TRUE;
8366 return SendMessageW(infoPtr->hwndHeader, HDM_SETORDERARRAY, iCount, (LPARAM)lpiArray);
8369 /***
8370 * DESCRIPTION:
8371 * Sets the width of a column
8373 * PARAMETERS:
8374 * [I] infoPtr : valid pointer to the listview structure
8375 * [I] nColumn : column index
8376 * [I] cx : column width
8378 * RETURN:
8379 * SUCCESS : TRUE
8380 * FAILURE : FALSE
8382 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
8384 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
8385 INT max_cx = 0;
8386 HDITEMW hdi;
8388 TRACE("(nColumn=%d, cx=%d)\n", nColumn, cx);
8390 /* set column width only if in report or list mode */
8391 if (infoPtr->uView != LV_VIEW_DETAILS && infoPtr->uView != LV_VIEW_LIST) return FALSE;
8393 /* take care of invalid cx values - LVSCW_AUTOSIZE_* values are negative,
8394 with _USEHEADER being the lowest */
8395 if (infoPtr->uView == LV_VIEW_DETAILS && cx < LVSCW_AUTOSIZE_USEHEADER) cx = LVSCW_AUTOSIZE;
8396 else if (infoPtr->uView == LV_VIEW_LIST && cx <= 0) return FALSE;
8398 /* resize all columns if in LV_VIEW_LIST mode */
8399 if(infoPtr->uView == LV_VIEW_LIST)
8401 infoPtr->nItemWidth = cx;
8402 LISTVIEW_InvalidateList(infoPtr);
8403 return TRUE;
8406 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
8408 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
8410 INT nLabelWidth;
8411 LVITEMW lvItem;
8413 lvItem.mask = LVIF_TEXT;
8414 lvItem.iItem = 0;
8415 lvItem.iSubItem = nColumn;
8416 lvItem.cchTextMax = DISP_TEXT_SIZE;
8417 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
8419 lvItem.pszText = szDispText;
8420 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
8421 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
8422 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
8424 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
8425 max_cx += infoPtr->iconSize.cx;
8426 max_cx += TRAILING_LABEL_PADDING;
8429 /* autosize based on listview items width */
8430 if(cx == LVSCW_AUTOSIZE)
8431 cx = max_cx;
8432 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
8434 /* if iCol is the last column make it fill the remainder of the controls width */
8435 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
8437 RECT rcHeader;
8438 POINT Origin;
8440 LISTVIEW_GetOrigin(infoPtr, &Origin);
8441 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
8443 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
8445 else
8447 /* Despite what the MS docs say, if this is not the last
8448 column, then MS resizes the column to the width of the
8449 largest text string in the column, including headers
8450 and items. This is different from LVSCW_AUTOSIZE in that
8451 LVSCW_AUTOSIZE ignores the header string length. */
8452 cx = 0;
8454 /* retrieve header text */
8455 hdi.mask = HDI_TEXT|HDI_FORMAT|HDI_IMAGE|HDI_BITMAP;
8456 hdi.cchTextMax = DISP_TEXT_SIZE;
8457 hdi.pszText = szDispText;
8458 if (SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, nColumn, (LPARAM)&hdi))
8460 HDC hdc = GetDC(infoPtr->hwndSelf);
8461 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
8462 HIMAGELIST himl = (HIMAGELIST)SendMessageW(infoPtr->hwndHeader, HDM_GETIMAGELIST, 0, 0);
8463 INT bitmap_margin = 0;
8464 SIZE size;
8466 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
8467 cx = size.cx + TRAILING_HEADER_PADDING;
8469 if (hdi.fmt & (HDF_IMAGE|HDF_BITMAP))
8470 bitmap_margin = SendMessageW(infoPtr->hwndHeader, HDM_GETBITMAPMARGIN, 0, 0);
8472 if ((hdi.fmt & HDF_IMAGE) && himl)
8474 INT icon_cx, icon_cy;
8476 if (!ImageList_GetIconSize(himl, &icon_cx, &icon_cy))
8477 cx += icon_cx + 2*bitmap_margin;
8479 else if (hdi.fmt & HDF_BITMAP)
8481 BITMAP bmp;
8483 GetObjectW(hdi.hbm, sizeof(BITMAP), &bmp);
8484 cx += bmp.bmWidth + 2*bitmap_margin;
8487 SelectObject(hdc, old_font);
8488 ReleaseDC(infoPtr->hwndSelf, hdc);
8490 cx = max (cx, max_cx);
8494 if (cx < 0) return FALSE;
8496 /* call header to update the column change */
8497 hdi.mask = HDI_WIDTH;
8498 hdi.cxy = max(cx, LISTVIEW_GetColumnInfo(infoPtr, nColumn)->cxMin);
8499 TRACE("hdi.cxy=%d\n", hdi.cxy);
8500 return SendMessageW(infoPtr->hwndHeader, HDM_SETITEMW, nColumn, (LPARAM)&hdi);
8503 /***
8504 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
8507 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(const LISTVIEW_INFO *infoPtr)
8509 HDC hdc_wnd, hdc;
8510 HBITMAP hbm_im, hbm_mask, hbm_orig;
8511 RECT rc;
8512 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
8513 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
8514 HIMAGELIST himl;
8516 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
8517 ILC_COLOR | ILC_MASK, 2, 2);
8518 hdc_wnd = GetDC(infoPtr->hwndSelf);
8519 hdc = CreateCompatibleDC(hdc_wnd);
8520 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8521 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
8522 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
8524 SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
8525 hbm_orig = SelectObject(hdc, hbm_mask);
8526 FillRect(hdc, &rc, hbr_white);
8527 InflateRect(&rc, -2, -2);
8528 FillRect(hdc, &rc, hbr_black);
8530 SelectObject(hdc, hbm_im);
8531 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
8532 SelectObject(hdc, hbm_orig);
8533 ImageList_Add(himl, hbm_im, hbm_mask);
8535 SelectObject(hdc, hbm_im);
8536 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
8537 SelectObject(hdc, hbm_orig);
8538 ImageList_Add(himl, hbm_im, hbm_mask);
8540 DeleteObject(hbm_mask);
8541 DeleteObject(hbm_im);
8542 DeleteDC(hdc);
8544 return himl;
8547 /***
8548 * DESCRIPTION:
8549 * Sets the extended listview style.
8551 * PARAMETERS:
8552 * [I] infoPtr : valid pointer to the listview structure
8553 * [I] dwMask : mask
8554 * [I] dwStyle : style
8556 * RETURN:
8557 * SUCCESS : previous style
8558 * FAILURE : 0
8560 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD mask, DWORD ex_style)
8562 DWORD old_ex_style = infoPtr->dwLvExStyle;
8564 TRACE("mask=0x%08x, ex_style=0x%08x\n", mask, ex_style);
8566 /* set new style */
8567 if (mask)
8568 infoPtr->dwLvExStyle = (old_ex_style & ~mask) | (ex_style & mask);
8569 else
8570 infoPtr->dwLvExStyle = ex_style;
8572 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_CHECKBOXES)
8574 HIMAGELIST himl = 0;
8575 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
8577 LVITEMW item;
8578 item.mask = LVIF_STATE;
8579 item.stateMask = LVIS_STATEIMAGEMASK;
8580 item.state = INDEXTOSTATEIMAGEMASK(1);
8581 LISTVIEW_SetItemState(infoPtr, -1, &item);
8583 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
8584 if(!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8585 ImageList_Destroy(infoPtr->himlState);
8587 himl = LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
8588 /* checkbox list replaces previous custom list or... */
8589 if(((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) &&
8590 !(infoPtr->dwStyle & LVS_SHAREIMAGELISTS)) ||
8591 /* ...previous was checkbox list */
8592 (old_ex_style & LVS_EX_CHECKBOXES))
8593 ImageList_Destroy(himl);
8596 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERDRAGDROP)
8598 DWORD style;
8600 /* if not already created */
8601 LISTVIEW_CreateHeader(infoPtr);
8603 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
8604 if (infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP)
8605 style |= HDS_DRAGDROP;
8606 else
8607 style &= ~HDS_DRAGDROP;
8608 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style);
8611 /* GRIDLINES adds decoration at top so changes sizes */
8612 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_GRIDLINES)
8614 LISTVIEW_CreateHeader(infoPtr);
8615 LISTVIEW_UpdateSize(infoPtr);
8618 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_FULLROWSELECT)
8620 LISTVIEW_CreateHeader(infoPtr);
8623 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_TRANSPARENTBKGND)
8625 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
8626 LISTVIEW_SetBkColor(infoPtr, CLR_NONE);
8629 if((infoPtr->dwLvExStyle ^ old_ex_style) & LVS_EX_HEADERINALLVIEWS)
8631 if (infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
8632 LISTVIEW_CreateHeader(infoPtr);
8633 else
8634 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8635 LISTVIEW_UpdateSize(infoPtr);
8636 LISTVIEW_UpdateScroll(infoPtr);
8639 LISTVIEW_InvalidateList(infoPtr);
8640 return old_ex_style;
8643 /***
8644 * DESCRIPTION:
8645 * Sets the new hot cursor used during hot tracking and hover selection.
8647 * PARAMETER(S):
8648 * [I] infoPtr : valid pointer to the listview structure
8649 * [I] hCursor : the new hot cursor handle
8651 * RETURN:
8652 * Returns the previous hot cursor
8654 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
8656 HCURSOR oldCursor = infoPtr->hHotCursor;
8658 infoPtr->hHotCursor = hCursor;
8660 return oldCursor;
8664 /***
8665 * DESCRIPTION:
8666 * Sets the hot item index.
8668 * PARAMETERS:
8669 * [I] infoPtr : valid pointer to the listview structure
8670 * [I] iIndex : index
8672 * RETURN:
8673 * SUCCESS : previous hot item index
8674 * FAILURE : -1 (no hot item)
8676 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
8678 INT iOldIndex = infoPtr->nHotItem;
8680 infoPtr->nHotItem = iIndex;
8682 return iOldIndex;
8686 /***
8687 * DESCRIPTION:
8688 * Sets the amount of time the cursor must hover over an item before it is selected.
8690 * PARAMETER(S):
8691 * [I] infoPtr : valid pointer to the listview structure
8692 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
8694 * RETURN:
8695 * Returns the previous hover time
8697 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
8699 DWORD oldHoverTime = infoPtr->dwHoverTime;
8701 infoPtr->dwHoverTime = dwHoverTime;
8703 return oldHoverTime;
8706 /***
8707 * DESCRIPTION:
8708 * Sets spacing for icons of LVS_ICON style.
8710 * PARAMETER(S):
8711 * [I] infoPtr : valid pointer to the listview structure
8712 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
8713 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
8715 * RETURN:
8716 * MAKELONG(oldcx, oldcy)
8718 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
8720 INT iconWidth = 0, iconHeight = 0;
8721 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
8723 TRACE("requested=(%d,%d)\n", cx, cy);
8725 /* set to defaults, if instructed to */
8726 if (cx == -1 && cy == -1)
8728 infoPtr->autoSpacing = TRUE;
8729 if (infoPtr->himlNormal)
8730 ImageList_GetIconSize(infoPtr->himlNormal, &iconWidth, &iconHeight);
8731 cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON) + iconWidth;
8732 cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON) + iconHeight;
8734 else
8735 infoPtr->autoSpacing = FALSE;
8737 /* if 0 then keep width */
8738 if (cx != 0)
8739 infoPtr->iconSpacing.cx = cx;
8741 /* if 0 then keep height */
8742 if (cy != 0)
8743 infoPtr->iconSpacing.cy = cy;
8745 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%d,%d), ntmH=%d\n",
8746 LOWORD(oldspacing), HIWORD(oldspacing), infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy,
8747 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
8748 infoPtr->ntmHeight);
8750 /* these depend on the iconSpacing */
8751 LISTVIEW_UpdateItemSize(infoPtr);
8753 return oldspacing;
8756 static inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
8758 INT cx, cy;
8760 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
8762 size->cx = cx;
8763 size->cy = cy;
8765 else
8767 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
8768 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
8772 /***
8773 * DESCRIPTION:
8774 * Sets image lists.
8776 * PARAMETER(S):
8777 * [I] infoPtr : valid pointer to the listview structure
8778 * [I] nType : image list type
8779 * [I] himl : image list handle
8781 * RETURN:
8782 * SUCCESS : old image list
8783 * FAILURE : NULL
8785 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
8787 INT oldHeight = infoPtr->nItemHeight;
8788 HIMAGELIST himlOld = 0;
8790 TRACE("(nType=%d, himl=%p)\n", nType, himl);
8792 switch (nType)
8794 case LVSIL_NORMAL:
8795 himlOld = infoPtr->himlNormal;
8796 infoPtr->himlNormal = himl;
8797 if (infoPtr->uView == LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
8798 if (infoPtr->autoSpacing)
8799 LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
8800 break;
8802 case LVSIL_SMALL:
8803 himlOld = infoPtr->himlSmall;
8804 infoPtr->himlSmall = himl;
8805 if (infoPtr->uView != LV_VIEW_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
8806 if (infoPtr->hwndHeader)
8807 SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)himl);
8808 break;
8810 case LVSIL_STATE:
8811 himlOld = infoPtr->himlState;
8812 infoPtr->himlState = himl;
8813 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
8814 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
8815 break;
8817 default:
8818 ERR("Unknown icon type=%d\n", nType);
8819 return NULL;
8822 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
8823 if (infoPtr->nItemHeight != oldHeight)
8824 LISTVIEW_UpdateScroll(infoPtr);
8826 return himlOld;
8829 /***
8830 * DESCRIPTION:
8831 * Preallocates memory (does *not* set the actual count of items !)
8833 * PARAMETER(S):
8834 * [I] infoPtr : valid pointer to the listview structure
8835 * [I] nItems : item count (projected number of items to allocate)
8836 * [I] dwFlags : update flags
8838 * RETURN:
8839 * SUCCESS : TRUE
8840 * FAILURE : FALSE
8842 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
8844 TRACE("(nItems=%d, dwFlags=%x)\n", nItems, dwFlags);
8846 if (infoPtr->dwStyle & LVS_OWNERDATA)
8848 INT nOldCount = infoPtr->nItemCount;
8849 infoPtr->nItemCount = nItems;
8851 if (nItems < nOldCount)
8853 RANGE range = { nItems, nOldCount };
8854 ranges_del(infoPtr->selectionRanges, range);
8855 if (infoPtr->nFocusedItem >= nItems)
8857 LISTVIEW_SetItemFocus(infoPtr, -1);
8858 SetRectEmpty(&infoPtr->rcFocus);
8862 LISTVIEW_UpdateScroll(infoPtr);
8864 /* the flags are valid only in ownerdata report and list modes */
8865 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON) dwFlags = 0;
8867 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
8868 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
8870 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
8871 LISTVIEW_InvalidateList(infoPtr);
8872 else
8874 INT nFrom, nTo;
8875 POINT Origin;
8876 RECT rcErase;
8878 LISTVIEW_GetOrigin(infoPtr, &Origin);
8879 nFrom = min(nOldCount, nItems);
8880 nTo = max(nOldCount, nItems);
8882 if (infoPtr->uView == LV_VIEW_DETAILS)
8884 SetRect(&rcErase, 0, nFrom * infoPtr->nItemHeight, infoPtr->nItemWidth,
8885 nTo * infoPtr->nItemHeight);
8886 OffsetRect(&rcErase, Origin.x, Origin.y);
8887 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8888 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8890 else /* LV_VIEW_LIST */
8892 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
8894 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
8895 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
8896 rcErase.right = rcErase.left + infoPtr->nItemWidth;
8897 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8898 OffsetRect(&rcErase, Origin.x, Origin.y);
8899 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8900 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8902 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
8903 rcErase.top = 0;
8904 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
8905 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
8906 OffsetRect(&rcErase, Origin.x, Origin.y);
8907 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
8908 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
8912 else
8914 /* According to MSDN for non-LVS_OWNERDATA this is just
8915 * a performance issue. The control allocates its internal
8916 * data structures for the number of items specified. It
8917 * cuts down on the number of memory allocations. Therefore
8918 * we will just issue a WARN here
8920 WARN("for non-ownerdata performance option not implemented.\n");
8923 return TRUE;
8926 /***
8927 * DESCRIPTION:
8928 * Sets the position of an item.
8930 * PARAMETER(S):
8931 * [I] infoPtr : valid pointer to the listview structure
8932 * [I] nItem : item index
8933 * [I] pt : coordinate
8935 * RETURN:
8936 * SUCCESS : TRUE
8937 * FAILURE : FALSE
8939 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *pt)
8941 POINT Origin, Pt;
8943 TRACE("(nItem=%d, pt=%s)\n", nItem, wine_dbgstr_point(pt));
8945 if (!pt || nItem < 0 || nItem >= infoPtr->nItemCount ||
8946 !(infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)) return FALSE;
8948 Pt = *pt;
8949 LISTVIEW_GetOrigin(infoPtr, &Origin);
8951 /* This point value seems to be an undocumented feature.
8952 * The best guess is that it means either at the origin,
8953 * or at true beginning of the list. I will assume the origin. */
8954 if ((Pt.x == -1) && (Pt.y == -1))
8955 Pt = Origin;
8957 if (infoPtr->uView == LV_VIEW_ICON)
8959 Pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
8960 Pt.y -= ICON_TOP_PADDING;
8962 Pt.x -= Origin.x;
8963 Pt.y -= Origin.y;
8965 return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
8968 /***
8969 * DESCRIPTION:
8970 * Sets the state of one or many items.
8972 * PARAMETER(S):
8973 * [I] infoPtr : valid pointer to the listview structure
8974 * [I] nItem : item index
8975 * [I] item : item or subitem info
8977 * RETURN:
8978 * SUCCESS : TRUE
8979 * FAILURE : FALSE
8981 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *item)
8983 BOOL ret = TRUE;
8984 LVITEMW lvItem;
8986 if (!item) return FALSE;
8988 lvItem.iItem = nItem;
8989 lvItem.iSubItem = 0;
8990 lvItem.mask = LVIF_STATE;
8991 lvItem.state = item->state;
8992 lvItem.stateMask = item->stateMask;
8993 TRACE("item=%s\n", debuglvitem_t(&lvItem, TRUE));
8995 if (nItem == -1)
8997 UINT oldstate = 0;
8998 BOOL notify;
9000 /* special case optimization for recurring attempt to deselect all */
9001 if (lvItem.state == 0 && lvItem.stateMask == LVIS_SELECTED && !LISTVIEW_GetSelectedCount(infoPtr))
9002 return TRUE;
9004 /* select all isn't allowed in LVS_SINGLESEL */
9005 if ((lvItem.state & lvItem.stateMask & LVIS_SELECTED) && (infoPtr->dwStyle & LVS_SINGLESEL))
9006 return FALSE;
9008 /* focus all isn't allowed */
9009 if (lvItem.state & lvItem.stateMask & LVIS_FOCUSED) return FALSE;
9011 notify = infoPtr->bDoChangeNotify;
9012 if (infoPtr->dwStyle & LVS_OWNERDATA)
9014 infoPtr->bDoChangeNotify = FALSE;
9015 if (!(lvItem.state & LVIS_SELECTED) && LISTVIEW_GetSelectedCount(infoPtr))
9016 oldstate |= LVIS_SELECTED;
9017 if (infoPtr->nFocusedItem != -1) oldstate |= LVIS_FOCUSED;
9020 /* apply to all items */
9021 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
9022 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) ret = FALSE;
9024 if (infoPtr->dwStyle & LVS_OWNERDATA)
9026 NMLISTVIEW nmlv;
9028 infoPtr->bDoChangeNotify = notify;
9030 nmlv.iItem = -1;
9031 nmlv.iSubItem = 0;
9032 nmlv.uNewState = lvItem.state & lvItem.stateMask;
9033 nmlv.uOldState = oldstate & lvItem.stateMask;
9034 nmlv.uChanged = LVIF_STATE;
9035 nmlv.ptAction.x = nmlv.ptAction.y = 0;
9036 nmlv.lParam = 0;
9038 notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
9041 else
9042 ret = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
9044 return ret;
9047 /***
9048 * DESCRIPTION:
9049 * Sets the text of an item or subitem.
9051 * PARAMETER(S):
9052 * [I] hwnd : window handle
9053 * [I] nItem : item index
9054 * [I] lpLVItem : item or subitem info
9055 * [I] isW : TRUE if input is Unicode
9057 * RETURN:
9058 * SUCCESS : TRUE
9059 * FAILURE : FALSE
9061 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
9063 LVITEMW lvItem;
9065 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9066 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9068 lvItem.iItem = nItem;
9069 lvItem.iSubItem = lpLVItem->iSubItem;
9070 lvItem.mask = LVIF_TEXT;
9071 lvItem.pszText = lpLVItem->pszText;
9072 lvItem.cchTextMax = lpLVItem->cchTextMax;
9074 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
9076 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
9079 /***
9080 * DESCRIPTION:
9081 * Set item index that marks the start of a multiple selection.
9083 * PARAMETER(S):
9084 * [I] infoPtr : valid pointer to the listview structure
9085 * [I] nIndex : index
9087 * RETURN:
9088 * Index number or -1 if there is no selection mark.
9090 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
9092 INT nOldIndex = infoPtr->nSelectionMark;
9094 TRACE("(nIndex=%d)\n", nIndex);
9096 if (nIndex >= -1 && nIndex < infoPtr->nItemCount)
9097 infoPtr->nSelectionMark = nIndex;
9099 return nOldIndex;
9102 /***
9103 * DESCRIPTION:
9104 * Sets the text background color.
9106 * PARAMETER(S):
9107 * [I] infoPtr : valid pointer to the listview structure
9108 * [I] color : text background color
9110 * RETURN:
9111 * SUCCESS : TRUE
9112 * FAILURE : FALSE
9114 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
9116 TRACE("(color=%x)\n", color);
9118 infoPtr->clrTextBk = color;
9119 return TRUE;
9122 /***
9123 * DESCRIPTION:
9124 * Sets the text foreground color.
9126 * PARAMETER(S):
9127 * [I] infoPtr : valid pointer to the listview structure
9128 * [I] color : text color
9130 * RETURN:
9131 * SUCCESS : TRUE
9132 * FAILURE : FALSE
9134 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF color)
9136 TRACE("(color=%x)\n", color);
9138 infoPtr->clrText = color;
9139 return TRUE;
9142 /***
9143 * DESCRIPTION:
9144 * Sets new ToolTip window to ListView control.
9146 * PARAMETER(S):
9147 * [I] infoPtr : valid pointer to the listview structure
9148 * [I] hwndNewToolTip : handle to new ToolTip
9150 * RETURN:
9151 * old tool tip
9153 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
9155 HWND hwndOldToolTip = infoPtr->hwndToolTip;
9156 infoPtr->hwndToolTip = hwndNewToolTip;
9157 return hwndOldToolTip;
9161 * DESCRIPTION:
9162 * sets the Unicode character format flag for the control
9163 * PARAMETER(S):
9164 * [I] infoPtr :valid pointer to the listview structure
9165 * [I] fUnicode :true to switch to UNICODE false to switch to ANSI
9167 * RETURN:
9168 * Old Unicode Format
9170 static BOOL LISTVIEW_SetUnicodeFormat( LISTVIEW_INFO *infoPtr, BOOL unicode)
9172 SHORT rc = infoPtr->notifyFormat;
9173 infoPtr->notifyFormat = (unicode) ? NFR_UNICODE : NFR_ANSI;
9174 return rc == NFR_UNICODE;
9178 * DESCRIPTION:
9179 * sets the control view mode
9180 * PARAMETER(S):
9181 * [I] infoPtr :valid pointer to the listview structure
9182 * [I] nView :new view mode value
9184 * RETURN:
9185 * SUCCESS: 1
9186 * FAILURE: -1
9188 static INT LISTVIEW_SetView(LISTVIEW_INFO *infoPtr, DWORD nView)
9190 HIMAGELIST himl;
9192 if (infoPtr->uView == nView) return 1;
9194 if ((INT)nView < 0 || nView > LV_VIEW_MAX) return -1;
9195 if (nView == LV_VIEW_TILE)
9197 FIXME("View LV_VIEW_TILE unimplemented\n");
9198 return -1;
9201 infoPtr->uView = nView;
9203 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9204 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
9206 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
9207 SetRectEmpty(&infoPtr->rcFocus);
9209 himl = (nView == LV_VIEW_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
9210 set_icon_size(&infoPtr->iconSize, himl, nView != LV_VIEW_ICON);
9212 switch (nView)
9214 case LV_VIEW_ICON:
9215 case LV_VIEW_SMALLICON:
9216 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9217 break;
9218 case LV_VIEW_DETAILS:
9220 HDLAYOUT hl;
9221 WINDOWPOS wp;
9223 LISTVIEW_CreateHeader( infoPtr );
9225 hl.prc = &infoPtr->rcList;
9226 hl.pwpos = &wp;
9227 SendMessageW(infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl);
9228 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
9229 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
9230 break;
9232 case LV_VIEW_LIST:
9233 break;
9236 LISTVIEW_UpdateItemSize(infoPtr);
9237 LISTVIEW_UpdateSize(infoPtr);
9238 LISTVIEW_UpdateScroll(infoPtr);
9239 LISTVIEW_InvalidateList(infoPtr);
9241 TRACE("nView=%d\n", nView);
9243 return 1;
9246 /* LISTVIEW_SetWorkAreas */
9248 /***
9249 * DESCRIPTION:
9250 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMS
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_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
9264 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9265 ITEM_INFO* lv_first = DPA_GetPtr( first, 0 );
9266 ITEM_INFO* lv_second = DPA_GetPtr( second, 0 );
9268 /* Forward the call to the client defined callback */
9269 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
9272 /***
9273 * DESCRIPTION:
9274 * Callback internally used by LISTVIEW_SortItems() in response of LVM_SORTITEMSEX
9276 * PARAMETER(S):
9277 * [I] first : pointer to first ITEM_INFO to compare
9278 * [I] second : pointer to second ITEM_INFO to compare
9279 * [I] lParam : HWND of control
9281 * RETURN:
9282 * if first comes before second : negative
9283 * if first comes after second : positive
9284 * if first and second are equivalent : zero
9286 static INT WINAPI LISTVIEW_CallBackCompareEx(LPVOID first, LPVOID second, LPARAM lParam)
9288 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
9289 INT first_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, first );
9290 INT second_idx = DPA_GetPtrIndex( infoPtr->hdpaItems, second );
9292 /* Forward the call to the client defined callback */
9293 return (infoPtr->pfnCompare)( first_idx, second_idx, infoPtr->lParamSort );
9296 /***
9297 * DESCRIPTION:
9298 * Sorts the listview items.
9300 * PARAMETER(S):
9301 * [I] infoPtr : valid pointer to the listview structure
9302 * [I] pfnCompare : application-defined value
9303 * [I] lParamSort : pointer to comparison callback
9304 * [I] IsEx : TRUE when LVM_SORTITEMSEX used
9306 * RETURN:
9307 * SUCCESS : TRUE
9308 * FAILURE : FALSE
9310 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare,
9311 LPARAM lParamSort, BOOL IsEx)
9313 HDPA hdpaSubItems;
9314 ITEM_INFO *lpItem;
9315 LPVOID selectionMarkItem = NULL;
9316 LPVOID focusedItem = NULL;
9317 int i;
9319 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
9321 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
9323 if (!pfnCompare) return FALSE;
9324 if (!infoPtr->hdpaItems) return FALSE;
9326 /* if there are 0 or 1 items, there is no need to sort */
9327 if (infoPtr->nItemCount < 2) return TRUE;
9329 /* clear selection */
9330 ranges_clear(infoPtr->selectionRanges);
9332 /* save selection mark and focused item */
9333 if (infoPtr->nSelectionMark >= 0)
9334 selectionMarkItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark);
9335 if (infoPtr->nFocusedItem >= 0)
9336 focusedItem = DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
9338 infoPtr->pfnCompare = pfnCompare;
9339 infoPtr->lParamSort = lParamSort;
9340 if (IsEx)
9341 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompareEx, (LPARAM)infoPtr);
9342 else
9343 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
9345 /* restore selection ranges */
9346 for (i=0; i < infoPtr->nItemCount; i++)
9348 hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, i);
9349 lpItem = DPA_GetPtr(hdpaSubItems, 0);
9351 if (lpItem->state & LVIS_SELECTED)
9352 ranges_additem(infoPtr->selectionRanges, i);
9354 /* restore selection mark and focused item */
9355 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
9356 infoPtr->nFocusedItem = DPA_GetPtrIndex(infoPtr->hdpaItems, focusedItem);
9358 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
9360 /* refresh the display */
9361 LISTVIEW_InvalidateList(infoPtr);
9362 return TRUE;
9365 /***
9366 * DESCRIPTION:
9367 * Update theme handle after a theme change.
9369 * PARAMETER(S):
9370 * [I] infoPtr : valid pointer to the listview structure
9372 * RETURN:
9373 * SUCCESS : 0
9374 * FAILURE : something else
9376 static LRESULT LISTVIEW_ThemeChanged(const LISTVIEW_INFO *infoPtr)
9378 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9379 CloseThemeData(theme);
9380 OpenThemeData(infoPtr->hwndSelf, themeClass);
9381 return 0;
9384 /***
9385 * DESCRIPTION:
9386 * Updates an items or rearranges the listview control.
9388 * PARAMETER(S):
9389 * [I] infoPtr : valid pointer to the listview structure
9390 * [I] nItem : item index
9392 * RETURN:
9393 * SUCCESS : TRUE
9394 * FAILURE : FALSE
9396 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
9398 TRACE("(nItem=%d)\n", nItem);
9400 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
9402 /* rearrange with default alignment style */
9403 if (is_autoarrange(infoPtr))
9404 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
9405 else
9406 LISTVIEW_InvalidateItem(infoPtr, nItem);
9408 return TRUE;
9411 /***
9412 * DESCRIPTION:
9413 * Draw the track line at the place defined in the infoPtr structure.
9414 * The line is drawn with a XOR pen so drawing the line for the second time
9415 * in the same place erases the line.
9417 * PARAMETER(S):
9418 * [I] infoPtr : valid pointer to the listview structure
9420 * RETURN:
9421 * SUCCESS : TRUE
9422 * FAILURE : FALSE
9424 static BOOL LISTVIEW_DrawTrackLine(const LISTVIEW_INFO *infoPtr)
9426 HDC hdc;
9428 if (infoPtr->xTrackLine == -1)
9429 return FALSE;
9431 if (!(hdc = GetDC(infoPtr->hwndSelf)))
9432 return FALSE;
9433 PatBlt( hdc, infoPtr->xTrackLine, infoPtr->rcList.top,
9434 1, infoPtr->rcList.bottom - infoPtr->rcList.top, DSTINVERT );
9435 ReleaseDC(infoPtr->hwndSelf, hdc);
9436 return TRUE;
9439 /***
9440 * DESCRIPTION:
9441 * Called when an edit control should be displayed. This function is called after
9442 * we are sure that there was a single click - not a double click (this is a TIMERPROC).
9444 * PARAMETER(S):
9445 * [I] hwnd : Handle to the listview
9446 * [I] uMsg : WM_TIMER (ignored)
9447 * [I] idEvent : The timer ID interpreted as a pointer to a DELAYED_EDIT_ITEM struct
9448 * [I] dwTimer : The elapsed time (ignored)
9450 * RETURN:
9451 * None.
9453 static VOID CALLBACK LISTVIEW_DelayedEditItem(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
9455 DELAYED_ITEM_EDIT *editItem = (DELAYED_ITEM_EDIT *)idEvent;
9456 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9458 KillTimer(hwnd, idEvent);
9459 editItem->fEnabled = FALSE;
9460 /* check if the item is still selected */
9461 if (infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, editItem->iItem, LVIS_SELECTED))
9462 LISTVIEW_EditLabelT(infoPtr, editItem->iItem, TRUE);
9465 /***
9466 * DESCRIPTION:
9467 * Creates the listview control - the WM_NCCREATE phase.
9469 * PARAMETER(S):
9470 * [I] hwnd : window handle
9471 * [I] lpcs : the create parameters
9473 * RETURN:
9474 * Success: TRUE
9475 * Failure: FALSE
9477 static LRESULT LISTVIEW_NCCreate(HWND hwnd, const CREATESTRUCTW *lpcs)
9479 LISTVIEW_INFO *infoPtr;
9480 LOGFONTW logFont;
9482 TRACE("(lpcs=%p)\n", lpcs);
9484 /* initialize info pointer */
9485 infoPtr = Alloc(sizeof(LISTVIEW_INFO));
9486 if (!infoPtr) return FALSE;
9488 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
9490 infoPtr->hwndSelf = hwnd;
9491 infoPtr->dwStyle = lpcs->style; /* Note: may be changed in WM_CREATE */
9492 map_style_view(infoPtr);
9493 /* determine the type of structures to use */
9494 infoPtr->hwndNotify = lpcs->hwndParent;
9495 /* infoPtr->notifyFormat will be filled in WM_CREATE */
9497 /* initialize color information */
9498 infoPtr->clrBk = CLR_NONE;
9499 infoPtr->clrText = CLR_DEFAULT;
9500 infoPtr->clrTextBk = CLR_DEFAULT;
9501 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
9503 /* set default values */
9504 infoPtr->nFocusedItem = -1;
9505 infoPtr->nSelectionMark = -1;
9506 infoPtr->nHotItem = -1;
9507 infoPtr->redraw = TRUE;
9508 infoPtr->bNoItemMetrics = TRUE;
9509 infoPtr->bDoChangeNotify = TRUE;
9510 infoPtr->autoSpacing = TRUE;
9511 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING) - GetSystemMetrics(SM_CXICON);
9512 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING) - GetSystemMetrics(SM_CYICON);
9513 infoPtr->nEditLabelItem = -1;
9514 infoPtr->nLButtonDownItem = -1;
9515 infoPtr->dwHoverTime = HOVER_DEFAULT; /* default system hover time */
9516 infoPtr->cWheelRemainder = 0;
9517 infoPtr->nMeasureItemHeight = 0;
9518 infoPtr->xTrackLine = -1; /* no track line */
9519 infoPtr->itemEdit.fEnabled = FALSE;
9520 infoPtr->iVersion = COMCTL32_VERSION;
9521 infoPtr->colRectsDirty = FALSE;
9523 /* get default font (icon title) */
9524 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
9525 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
9526 infoPtr->hFont = infoPtr->hDefaultFont;
9527 LISTVIEW_SaveTextMetrics(infoPtr);
9529 /* allocate memory for the data structure */
9530 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
9531 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
9532 if (!(infoPtr->hdpaItemIds = DPA_Create(10))) goto fail;
9533 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
9534 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
9535 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
9536 return TRUE;
9538 fail:
9539 DestroyWindow(infoPtr->hwndHeader);
9540 ranges_destroy(infoPtr->selectionRanges);
9541 DPA_Destroy(infoPtr->hdpaItems);
9542 DPA_Destroy(infoPtr->hdpaItemIds);
9543 DPA_Destroy(infoPtr->hdpaPosX);
9544 DPA_Destroy(infoPtr->hdpaPosY);
9545 DPA_Destroy(infoPtr->hdpaColumns);
9546 Free(infoPtr);
9547 return FALSE;
9550 /***
9551 * DESCRIPTION:
9552 * Creates the listview control - the WM_CREATE phase. Most of the data is
9553 * already set up in LISTVIEW_NCCreate
9555 * PARAMETER(S):
9556 * [I] hwnd : window handle
9557 * [I] lpcs : the create parameters
9559 * RETURN:
9560 * Success: 0
9561 * Failure: -1
9563 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
9565 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
9567 TRACE("(lpcs=%p, style=0x%08x)\n", lpcs, lpcs->style);
9569 infoPtr->dwStyle = lpcs->style;
9570 map_style_view(infoPtr);
9572 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
9573 (WPARAM)infoPtr->hwndSelf, NF_QUERY);
9574 /* on error defaulting to ANSI notifications */
9575 if (infoPtr->notifyFormat == 0) infoPtr->notifyFormat = NFR_ANSI;
9576 TRACE("notify format=%d\n", infoPtr->notifyFormat);
9578 if ((infoPtr->uView == LV_VIEW_DETAILS) && (lpcs->style & WS_VISIBLE))
9580 if (LISTVIEW_CreateHeader(infoPtr) < 0) return -1;
9582 else
9583 infoPtr->hwndHeader = 0;
9585 /* init item size to avoid division by 0 */
9586 LISTVIEW_UpdateItemSize (infoPtr);
9587 LISTVIEW_UpdateSize (infoPtr);
9589 if (infoPtr->uView == LV_VIEW_DETAILS)
9591 if (!(LVS_NOCOLUMNHEADER & lpcs->style) && (WS_VISIBLE & lpcs->style))
9593 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
9595 LISTVIEW_UpdateScroll(infoPtr);
9596 /* send WM_MEASUREITEM notification */
9597 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED) notify_measureitem(infoPtr);
9600 OpenThemeData(hwnd, themeClass);
9602 /* initialize the icon sizes */
9603 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, infoPtr->uView != LV_VIEW_ICON);
9604 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
9605 return 0;
9608 /***
9609 * DESCRIPTION:
9610 * Destroys the listview control.
9612 * PARAMETER(S):
9613 * [I] infoPtr : valid pointer to the listview structure
9615 * RETURN:
9616 * Success: 0
9617 * Failure: -1
9619 static LRESULT LISTVIEW_Destroy(LISTVIEW_INFO *infoPtr)
9621 HTHEME theme = GetWindowTheme(infoPtr->hwndSelf);
9622 CloseThemeData(theme);
9624 /* delete all items */
9625 LISTVIEW_DeleteAllItems(infoPtr, TRUE);
9627 return 0;
9630 /***
9631 * DESCRIPTION:
9632 * Enables the listview control.
9634 * PARAMETER(S):
9635 * [I] infoPtr : valid pointer to the listview structure
9636 * [I] bEnable : specifies whether to enable or disable the window
9638 * RETURN:
9639 * SUCCESS : TRUE
9640 * FAILURE : FALSE
9642 static BOOL LISTVIEW_Enable(const LISTVIEW_INFO *infoPtr)
9644 if (infoPtr->dwStyle & LVS_OWNERDRAWFIXED)
9645 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
9646 return TRUE;
9649 /***
9650 * DESCRIPTION:
9651 * Erases the background of the listview control.
9653 * PARAMETER(S):
9654 * [I] infoPtr : valid pointer to the listview structure
9655 * [I] hdc : device context handle
9657 * RETURN:
9658 * SUCCESS : TRUE
9659 * FAILURE : FALSE
9661 static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
9663 RECT rc;
9665 TRACE("(hdc=%p)\n", hdc);
9667 if (!GetClipBox(hdc, &rc)) return FALSE;
9669 if (infoPtr->clrBk == CLR_NONE)
9671 if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
9672 return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
9673 (WPARAM)hdc, PRF_ERASEBKGND);
9674 else
9675 return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
9678 /* for double buffered controls we need to do this during refresh */
9679 if (infoPtr->dwLvExStyle & LVS_EX_DOUBLEBUFFER) return FALSE;
9681 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
9685 /***
9686 * DESCRIPTION:
9687 * Helper function for LISTVIEW_[HV]Scroll *only*.
9688 * Performs vertical/horizontal scrolling by a give amount.
9690 * PARAMETER(S):
9691 * [I] infoPtr : valid pointer to the listview structure
9692 * [I] dx : amount of horizontal scroll
9693 * [I] dy : amount of vertical scroll
9695 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
9697 /* now we can scroll the list */
9698 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
9699 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
9700 /* if we have focus, adjust rect */
9701 OffsetRect(&infoPtr->rcFocus, dx, dy);
9702 UpdateWindow(infoPtr->hwndSelf);
9705 /***
9706 * DESCRIPTION:
9707 * Performs vertical scrolling.
9709 * PARAMETER(S):
9710 * [I] infoPtr : valid pointer to the listview structure
9711 * [I] nScrollCode : scroll code
9712 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9713 * [I] hScrollWnd : scrollbar control window handle
9715 * RETURN:
9716 * Zero
9718 * NOTES:
9719 * SB_LINEUP/SB_LINEDOWN:
9720 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
9721 * for LVS_REPORT is 1 line
9722 * for LVS_LIST cannot occur
9725 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9726 INT nScrollDiff)
9728 INT nOldScrollPos, nNewScrollPos;
9729 SCROLLINFO scrollInfo;
9730 BOOL is_an_icon;
9732 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9733 debugscrollcode(nScrollCode), nScrollDiff);
9735 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9737 scrollInfo.cbSize = sizeof(SCROLLINFO);
9738 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9740 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9742 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
9744 nOldScrollPos = scrollInfo.nPos;
9745 switch (nScrollCode)
9747 case SB_INTERNAL:
9748 break;
9750 case SB_LINEUP:
9751 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9752 break;
9754 case SB_LINEDOWN:
9755 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9756 break;
9758 case SB_PAGEUP:
9759 nScrollDiff = -scrollInfo.nPage;
9760 break;
9762 case SB_PAGEDOWN:
9763 nScrollDiff = scrollInfo.nPage;
9764 break;
9766 case SB_THUMBPOSITION:
9767 case SB_THUMBTRACK:
9768 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9769 break;
9771 default:
9772 nScrollDiff = 0;
9775 /* quit right away if pos isn't changing */
9776 if (nScrollDiff == 0) return 0;
9778 /* calculate new position, and handle overflows */
9779 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9780 if (nScrollDiff > 0) {
9781 if (nNewScrollPos < nOldScrollPos ||
9782 nNewScrollPos > scrollInfo.nMax)
9783 nNewScrollPos = scrollInfo.nMax;
9784 } else {
9785 if (nNewScrollPos > nOldScrollPos ||
9786 nNewScrollPos < scrollInfo.nMin)
9787 nNewScrollPos = scrollInfo.nMin;
9790 /* set the new position, and reread in case it changed */
9791 scrollInfo.fMask = SIF_POS;
9792 scrollInfo.nPos = nNewScrollPos;
9793 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
9795 /* carry on only if it really changed */
9796 if (nNewScrollPos == nOldScrollPos) return 0;
9798 /* now adjust to client coordinates */
9799 nScrollDiff = nOldScrollPos - nNewScrollPos;
9800 if (infoPtr->uView == LV_VIEW_DETAILS) nScrollDiff *= infoPtr->nItemHeight;
9802 /* and scroll the window */
9803 scroll_list(infoPtr, 0, nScrollDiff);
9805 return 0;
9808 /***
9809 * DESCRIPTION:
9810 * Performs horizontal scrolling.
9812 * PARAMETER(S):
9813 * [I] infoPtr : valid pointer to the listview structure
9814 * [I] nScrollCode : scroll code
9815 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
9816 * [I] hScrollWnd : scrollbar control window handle
9818 * RETURN:
9819 * Zero
9821 * NOTES:
9822 * SB_LINELEFT/SB_LINERIGHT:
9823 * for LVS_ICON, LVS_SMALLICON 1 pixel
9824 * for LVS_REPORT is 1 pixel
9825 * for LVS_LIST is 1 column --> which is a 1 because the
9826 * scroll is based on columns not pixels
9829 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
9830 INT nScrollDiff)
9832 INT nOldScrollPos, nNewScrollPos;
9833 SCROLLINFO scrollInfo;
9834 BOOL is_an_icon;
9836 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
9837 debugscrollcode(nScrollCode), nScrollDiff);
9839 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
9841 scrollInfo.cbSize = sizeof(SCROLLINFO);
9842 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
9844 is_an_icon = ((infoPtr->uView == LV_VIEW_ICON) || (infoPtr->uView == LV_VIEW_SMALLICON));
9846 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
9848 nOldScrollPos = scrollInfo.nPos;
9850 switch (nScrollCode)
9852 case SB_INTERNAL:
9853 break;
9855 case SB_LINELEFT:
9856 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
9857 break;
9859 case SB_LINERIGHT:
9860 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
9861 break;
9863 case SB_PAGELEFT:
9864 nScrollDiff = -scrollInfo.nPage;
9865 break;
9867 case SB_PAGERIGHT:
9868 nScrollDiff = scrollInfo.nPage;
9869 break;
9871 case SB_THUMBPOSITION:
9872 case SB_THUMBTRACK:
9873 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
9874 break;
9876 default:
9877 nScrollDiff = 0;
9880 /* quit right away if pos isn't changing */
9881 if (nScrollDiff == 0) return 0;
9883 /* calculate new position, and handle overflows */
9884 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
9885 if (nScrollDiff > 0) {
9886 if (nNewScrollPos < nOldScrollPos ||
9887 nNewScrollPos > scrollInfo.nMax)
9888 nNewScrollPos = scrollInfo.nMax;
9889 } else {
9890 if (nNewScrollPos > nOldScrollPos ||
9891 nNewScrollPos < scrollInfo.nMin)
9892 nNewScrollPos = scrollInfo.nMin;
9895 /* set the new position, and reread in case it changed */
9896 scrollInfo.fMask = SIF_POS;
9897 scrollInfo.nPos = nNewScrollPos;
9898 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
9900 /* carry on only if it really changed */
9901 if (nNewScrollPos == nOldScrollPos) return 0;
9903 if (infoPtr->hwndHeader) LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
9905 /* now adjust to client coordinates */
9906 nScrollDiff = nOldScrollPos - nNewScrollPos;
9907 if (infoPtr->uView == LV_VIEW_LIST) nScrollDiff *= infoPtr->nItemWidth;
9909 /* and scroll the window */
9910 scroll_list(infoPtr, nScrollDiff, 0);
9912 return 0;
9915 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
9917 UINT pulScrollLines = 3;
9919 TRACE("(wheelDelta=%d)\n", wheelDelta);
9921 switch(infoPtr->uView)
9923 case LV_VIEW_ICON:
9924 case LV_VIEW_SMALLICON:
9926 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
9927 * should be fixed in the future.
9929 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (wheelDelta > 0) ?
9930 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE);
9931 break;
9933 case LV_VIEW_DETAILS:
9934 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
9936 /* if scrolling changes direction, ignore left overs */
9937 if ((wheelDelta < 0 && infoPtr->cWheelRemainder < 0) ||
9938 (wheelDelta > 0 && infoPtr->cWheelRemainder > 0))
9939 infoPtr->cWheelRemainder += wheelDelta;
9940 else
9941 infoPtr->cWheelRemainder = wheelDelta;
9942 if (infoPtr->cWheelRemainder && pulScrollLines)
9944 int cLineScroll;
9945 pulScrollLines = min((UINT)LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
9946 cLineScroll = pulScrollLines * (float)infoPtr->cWheelRemainder / WHEEL_DELTA;
9947 infoPtr->cWheelRemainder -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
9948 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, -cLineScroll);
9950 break;
9952 case LV_VIEW_LIST:
9953 LISTVIEW_HScroll(infoPtr, (wheelDelta > 0) ? SB_LINELEFT : SB_LINERIGHT, 0);
9954 break;
9956 return 0;
9959 /***
9960 * DESCRIPTION:
9961 * ???
9963 * PARAMETER(S):
9964 * [I] infoPtr : valid pointer to the listview structure
9965 * [I] nVirtualKey : virtual key
9966 * [I] lKeyData : key data
9968 * RETURN:
9969 * Zero
9971 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
9973 HWND hwndSelf = infoPtr->hwndSelf;
9974 INT nItem = -1;
9975 NMLVKEYDOWN nmKeyDown;
9977 TRACE("(nVirtualKey=%d, lKeyData=%d)\n", nVirtualKey, lKeyData);
9979 /* send LVN_KEYDOWN notification */
9980 nmKeyDown.wVKey = nVirtualKey;
9981 nmKeyDown.flags = 0;
9982 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
9983 if (!IsWindow(hwndSelf))
9984 return 0;
9986 switch (nVirtualKey)
9988 case VK_SPACE:
9989 nItem = infoPtr->nFocusedItem;
9990 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
9991 toggle_checkbox_state(infoPtr, infoPtr->nFocusedItem);
9992 break;
9994 case VK_RETURN:
9995 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
9997 if (!notify(infoPtr, NM_RETURN)) return 0;
9998 if (!notify(infoPtr, LVN_ITEMACTIVATE)) return 0;
10000 break;
10002 case VK_HOME:
10003 if (infoPtr->nItemCount > 0)
10004 nItem = 0;
10005 break;
10007 case VK_END:
10008 if (infoPtr->nItemCount > 0)
10009 nItem = infoPtr->nItemCount - 1;
10010 break;
10012 case VK_LEFT:
10013 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TOLEFT);
10014 break;
10016 case VK_UP:
10017 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_ABOVE);
10018 break;
10020 case VK_RIGHT:
10021 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_TORIGHT);
10022 break;
10024 case VK_DOWN:
10025 nItem = LISTVIEW_GetNextItem(infoPtr, infoPtr->nFocusedItem, LVNI_BELOW);
10026 break;
10028 case VK_PRIOR:
10029 if (infoPtr->uView == LV_VIEW_DETAILS)
10031 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10032 if (infoPtr->nFocusedItem == topidx)
10033 nItem = topidx - LISTVIEW_GetCountPerColumn(infoPtr) + 1;
10034 else
10035 nItem = topidx;
10037 else
10038 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
10039 * LISTVIEW_GetCountPerRow(infoPtr);
10040 if(nItem < 0) nItem = 0;
10041 break;
10043 case VK_NEXT:
10044 if (infoPtr->uView == LV_VIEW_DETAILS)
10046 INT topidx = LISTVIEW_GetTopIndex(infoPtr);
10047 INT cnt = LISTVIEW_GetCountPerColumn(infoPtr);
10048 if (infoPtr->nFocusedItem == topidx + cnt - 1)
10049 nItem = infoPtr->nFocusedItem + cnt - 1;
10050 else
10051 nItem = topidx + cnt - 1;
10053 else
10054 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
10055 * LISTVIEW_GetCountPerRow(infoPtr);
10056 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
10057 break;
10060 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem || nVirtualKey == VK_SPACE))
10061 LISTVIEW_KeySelection(infoPtr, nItem, nVirtualKey == VK_SPACE);
10063 return 0;
10066 /***
10067 * DESCRIPTION:
10068 * Kills the focus.
10070 * PARAMETER(S):
10071 * [I] infoPtr : valid pointer to the listview structure
10073 * RETURN:
10074 * Zero
10076 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
10078 TRACE("()\n");
10080 /* drop any left over scroll amount */
10081 infoPtr->cWheelRemainder = 0;
10083 /* if we did not have the focus, there's nothing more to do */
10084 if (!infoPtr->bFocus) return 0;
10086 /* send NM_KILLFOCUS notification */
10087 if (!notify(infoPtr, NM_KILLFOCUS)) return 0;
10089 /* if we have a focus rectangle, get rid of it */
10090 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
10092 /* if have a marquee selection, stop it */
10093 if (infoPtr->bMarqueeSelect)
10095 /* Remove the marquee rectangle and release our mouse capture */
10096 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeRect);
10097 ReleaseCapture();
10099 SetRectEmpty(&infoPtr->marqueeRect);
10101 infoPtr->bMarqueeSelect = FALSE;
10102 infoPtr->bScrolling = FALSE;
10103 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10106 /* set window focus flag */
10107 infoPtr->bFocus = FALSE;
10109 /* invalidate the selected items before resetting focus flag */
10110 LISTVIEW_InvalidateSelectedItems(infoPtr);
10112 return 0;
10115 /***
10116 * DESCRIPTION:
10117 * Processes double click messages (left mouse button).
10119 * PARAMETER(S):
10120 * [I] infoPtr : valid pointer to the listview structure
10121 * [I] wKey : key flag
10122 * [I] x,y : mouse coordinate
10124 * RETURN:
10125 * Zero
10127 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10129 LVHITTESTINFO htInfo;
10131 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10133 /* Cancel the item edition if any */
10134 if (infoPtr->itemEdit.fEnabled)
10136 KillTimer(infoPtr->hwndSelf, (UINT_PTR)&infoPtr->itemEdit);
10137 infoPtr->itemEdit.fEnabled = FALSE;
10140 /* send NM_RELEASEDCAPTURE notification */
10141 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10143 htInfo.pt.x = x;
10144 htInfo.pt.y = y;
10146 /* send NM_DBLCLK notification */
10147 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
10148 if (!notify_click(infoPtr, NM_DBLCLK, &htInfo)) return 0;
10150 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
10151 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
10153 return 0;
10156 static LRESULT LISTVIEW_TrackMouse(const LISTVIEW_INFO *infoPtr, POINT pt)
10158 MSG msg;
10159 RECT r;
10161 r.top = r.bottom = pt.y;
10162 r.left = r.right = pt.x;
10164 InflateRect(&r, GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
10166 SetCapture(infoPtr->hwndSelf);
10168 while (1)
10170 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
10172 if (msg.message == WM_MOUSEMOVE)
10174 pt.x = (short)LOWORD(msg.lParam);
10175 pt.y = (short)HIWORD(msg.lParam);
10176 if (PtInRect(&r, pt))
10177 continue;
10178 else
10180 ReleaseCapture();
10181 return 1;
10184 else if (msg.message >= WM_LBUTTONDOWN &&
10185 msg.message <= WM_RBUTTONDBLCLK)
10187 break;
10190 DispatchMessageW(&msg);
10193 if (GetCapture() != infoPtr->hwndSelf)
10194 return 0;
10197 ReleaseCapture();
10198 return 0;
10202 /***
10203 * DESCRIPTION:
10204 * Processes mouse down messages (left mouse button).
10206 * PARAMETERS:
10207 * infoPtr [I ] valid pointer to the listview structure
10208 * wKey [I ] key flag
10209 * x,y [I ] mouse coordinate
10211 * RETURN:
10212 * Zero
10214 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10216 LVHITTESTINFO lvHitTestInfo;
10217 static BOOL bGroupSelect = TRUE;
10218 POINT pt = { x, y };
10219 INT nItem;
10221 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10223 /* send NM_RELEASEDCAPTURE notification */
10224 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10226 /* set left button down flag and record the click position */
10227 infoPtr->bLButtonDown = TRUE;
10228 infoPtr->ptClickPos = pt;
10229 infoPtr->bDragging = FALSE;
10230 infoPtr->bMarqueeSelect = FALSE;
10231 infoPtr->bScrolling = FALSE;
10233 lvHitTestInfo.pt.x = x;
10234 lvHitTestInfo.pt.y = y;
10236 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
10237 TRACE("at %s, nItem=%d\n", wine_dbgstr_point(&pt), nItem);
10238 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
10240 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
10242 toggle_checkbox_state(infoPtr, nItem);
10243 return 0;
10246 if (infoPtr->dwStyle & LVS_SINGLESEL)
10248 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10249 infoPtr->nEditLabelItem = nItem;
10250 else
10251 LISTVIEW_SetSelection(infoPtr, nItem);
10253 else
10255 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
10257 if (bGroupSelect)
10259 if (!LISTVIEW_AddGroupSelection(infoPtr, nItem)) return 0;
10260 LISTVIEW_SetItemFocus(infoPtr, nItem);
10261 infoPtr->nSelectionMark = nItem;
10263 else
10265 LVITEMW item;
10267 item.state = LVIS_SELECTED | LVIS_FOCUSED;
10268 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10270 LISTVIEW_SetItemState(infoPtr,nItem,&item);
10271 infoPtr->nSelectionMark = nItem;
10274 else if (wKey & MK_CONTROL)
10276 LVITEMW item;
10278 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
10280 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
10281 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
10282 LISTVIEW_SetItemState(infoPtr, nItem, &item);
10283 infoPtr->nSelectionMark = nItem;
10285 else if (wKey & MK_SHIFT)
10287 LISTVIEW_SetGroupSelection(infoPtr, nItem);
10289 else
10291 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
10293 infoPtr->nEditLabelItem = nItem;
10294 infoPtr->nLButtonDownItem = nItem;
10296 LISTVIEW_SetItemFocus(infoPtr, nItem);
10298 else
10299 /* set selection (clears other pre-existing selections) */
10300 LISTVIEW_SetSelection(infoPtr, nItem);
10304 if (!infoPtr->bFocus)
10305 SetFocus(infoPtr->hwndSelf);
10307 if (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE)
10308 if(lvHitTestInfo.iItem != -1) notify_itemactivate(infoPtr,&lvHitTestInfo);
10310 else
10312 if (!infoPtr->bFocus)
10313 SetFocus(infoPtr->hwndSelf);
10315 /* remove all selections */
10316 if (!(wKey & MK_CONTROL) && !(wKey & MK_SHIFT))
10317 LISTVIEW_DeselectAll(infoPtr);
10318 ReleaseCapture();
10321 return 0;
10324 /***
10325 * DESCRIPTION:
10326 * Processes mouse up messages (left mouse button).
10328 * PARAMETERS:
10329 * infoPtr [I ] valid pointer to the listview structure
10330 * wKey [I ] key flag
10331 * x,y [I ] mouse coordinate
10333 * RETURN:
10334 * Zero
10336 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10338 LVHITTESTINFO lvHitTestInfo;
10340 TRACE("(key=%hu, X=%u, Y=%u)\n", wKey, x, y);
10342 if (!infoPtr->bLButtonDown) return 0;
10344 lvHitTestInfo.pt.x = x;
10345 lvHitTestInfo.pt.y = y;
10347 /* send NM_CLICK notification */
10348 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10349 if (!notify_click(infoPtr, NM_CLICK, &lvHitTestInfo)) return 0;
10351 /* set left button flag */
10352 infoPtr->bLButtonDown = FALSE;
10354 /* set a single selection, reset others */
10355 if(lvHitTestInfo.iItem == infoPtr->nLButtonDownItem && lvHitTestInfo.iItem != -1)
10356 LISTVIEW_SetSelection(infoPtr, infoPtr->nLButtonDownItem);
10357 infoPtr->nLButtonDownItem = -1;
10359 if (infoPtr->bDragging || infoPtr->bMarqueeSelect)
10361 /* Remove the marquee rectangle and release our mouse capture */
10362 if (infoPtr->bMarqueeSelect)
10364 LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
10365 ReleaseCapture();
10368 SetRectEmpty(&infoPtr->marqueeRect);
10369 SetRectEmpty(&infoPtr->marqueeDrawRect);
10371 infoPtr->bDragging = FALSE;
10372 infoPtr->bMarqueeSelect = FALSE;
10373 infoPtr->bScrolling = FALSE;
10375 KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
10376 return 0;
10379 /* if we clicked on a selected item, edit the label */
10380 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
10382 /* we want to make sure the user doesn't want to do a double click. So we will
10383 * delay the edit. WM_LBUTTONDBLCLICK will cancel the timer
10385 infoPtr->itemEdit.fEnabled = TRUE;
10386 infoPtr->itemEdit.iItem = lvHitTestInfo.iItem;
10387 SetTimer(infoPtr->hwndSelf,
10388 (UINT_PTR)&infoPtr->itemEdit,
10389 GetDoubleClickTime(),
10390 LISTVIEW_DelayedEditItem);
10393 return 0;
10396 /***
10397 * DESCRIPTION:
10398 * Destroys the listview control (called after WM_DESTROY).
10400 * PARAMETER(S):
10401 * [I] infoPtr : valid pointer to the listview structure
10403 * RETURN:
10404 * Zero
10406 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
10408 INT i;
10410 TRACE("()\n");
10412 /* destroy data structure */
10413 DPA_Destroy(infoPtr->hdpaItems);
10414 DPA_Destroy(infoPtr->hdpaItemIds);
10415 DPA_Destroy(infoPtr->hdpaPosX);
10416 DPA_Destroy(infoPtr->hdpaPosY);
10417 /* columns */
10418 for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++)
10419 Free(DPA_GetPtr(infoPtr->hdpaColumns, i));
10420 DPA_Destroy(infoPtr->hdpaColumns);
10421 ranges_destroy(infoPtr->selectionRanges);
10423 /* destroy image lists */
10424 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
10426 ImageList_Destroy(infoPtr->himlNormal);
10427 ImageList_Destroy(infoPtr->himlSmall);
10428 ImageList_Destroy(infoPtr->himlState);
10431 /* destroy font, bkgnd brush */
10432 infoPtr->hFont = 0;
10433 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
10434 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
10436 SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
10438 /* free listview info pointer*/
10439 Free(infoPtr);
10441 return 0;
10444 /***
10445 * DESCRIPTION:
10446 * Handles notifications.
10448 * PARAMETER(S):
10449 * [I] infoPtr : valid pointer to the listview structure
10450 * [I] lpnmhdr : notification information
10452 * RETURN:
10453 * Zero
10455 static LRESULT LISTVIEW_Notify(LISTVIEW_INFO *infoPtr, NMHDR *lpnmhdr)
10457 NMHEADERW *lpnmh;
10459 TRACE("(lpnmhdr=%p)\n", lpnmhdr);
10461 if (!lpnmhdr || lpnmhdr->hwndFrom != infoPtr->hwndHeader) return 0;
10463 /* remember: HDN_LAST < HDN_FIRST */
10464 if (lpnmhdr->code > HDN_FIRST || lpnmhdr->code < HDN_LAST) return 0;
10465 lpnmh = (NMHEADERW *)lpnmhdr;
10467 if (lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
10469 switch (lpnmhdr->code)
10471 case HDN_TRACKW:
10472 case HDN_TRACKA:
10474 COLUMN_INFO *lpColumnInfo;
10475 POINT ptOrigin;
10476 INT x;
10478 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10479 break;
10481 /* remove the old line (if any) */
10482 LISTVIEW_DrawTrackLine(infoPtr);
10484 /* compute & draw the new line */
10485 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10486 x = lpColumnInfo->rcHeader.left + lpnmh->pitem->cxy;
10487 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10488 infoPtr->xTrackLine = x + ptOrigin.x;
10489 LISTVIEW_DrawTrackLine(infoPtr);
10490 return notify_forward_header(infoPtr, lpnmh);
10493 case HDN_ENDTRACKA:
10494 case HDN_ENDTRACKW:
10495 /* remove the track line (if any) */
10496 LISTVIEW_DrawTrackLine(infoPtr);
10497 infoPtr->xTrackLine = -1;
10498 return notify_forward_header(infoPtr, lpnmh);
10500 case HDN_BEGINDRAG:
10501 if ((infoPtr->dwLvExStyle & LVS_EX_HEADERDRAGDROP) == 0) return 1;
10502 return notify_forward_header(infoPtr, lpnmh);
10504 case HDN_ENDDRAG:
10505 infoPtr->colRectsDirty = TRUE;
10506 LISTVIEW_InvalidateList(infoPtr);
10507 return notify_forward_header(infoPtr, lpnmh);
10509 case HDN_ITEMCHANGEDW:
10510 case HDN_ITEMCHANGEDA:
10512 COLUMN_INFO *lpColumnInfo;
10513 HDITEMW hdi;
10514 INT dx, cxy;
10516 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
10518 hdi.mask = HDI_WIDTH;
10519 if (!SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi)) return 0;
10520 cxy = hdi.cxy;
10522 else
10523 cxy = lpnmh->pitem->cxy;
10525 /* determine how much we change since the last know position */
10526 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
10527 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
10528 if (dx != 0)
10530 lpColumnInfo->rcHeader.right += dx;
10532 hdi.mask = HDI_ORDER;
10533 SendMessageW(infoPtr->hwndHeader, HDM_GETITEMW, lpnmh->iItem, (LPARAM)&hdi);
10535 /* not the rightmost one */
10536 if (hdi.iOrder + 1 < DPA_GetPtrCount(infoPtr->hdpaColumns))
10538 INT nIndex = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
10539 hdi.iOrder + 1, 0);
10540 LISTVIEW_ScrollColumns(infoPtr, nIndex, dx);
10542 else
10544 /* only needs to update the scrolls */
10545 infoPtr->nItemWidth += dx;
10546 LISTVIEW_UpdateScroll(infoPtr);
10548 LISTVIEW_UpdateItemSize(infoPtr);
10549 if (infoPtr->uView == LV_VIEW_DETAILS && is_redrawing(infoPtr))
10551 POINT ptOrigin;
10552 RECT rcCol = lpColumnInfo->rcHeader;
10554 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
10555 OffsetRect(&rcCol, ptOrigin.x, 0);
10557 rcCol.top = infoPtr->rcList.top;
10558 rcCol.bottom = infoPtr->rcList.bottom;
10560 /* resizing left-aligned columns leaves most of the left side untouched */
10561 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
10563 INT nMaxDirty = infoPtr->nEllipsisWidth + infoPtr->ntmMaxCharWidth;
10564 if (dx > 0)
10565 nMaxDirty += dx;
10566 rcCol.left = max (rcCol.left, rcCol.right - nMaxDirty);
10569 /* when shrinking the last column clear the now unused field */
10570 if (hdi.iOrder == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
10572 RECT right;
10574 rcCol.right -= dx;
10576 /* deal with right from rightmost column area */
10577 right.left = rcCol.right;
10578 right.top = rcCol.top;
10579 right.bottom = rcCol.bottom;
10580 right.right = infoPtr->rcList.right;
10582 LISTVIEW_InvalidateRect(infoPtr, &right);
10585 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
10588 break;
10591 case HDN_ITEMCLICKW:
10592 case HDN_ITEMCLICKA:
10594 /* Handle sorting by Header Column */
10595 NMLISTVIEW nmlv;
10597 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
10598 nmlv.iItem = -1;
10599 nmlv.iSubItem = lpnmh->iItem;
10600 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
10601 return notify_forward_header(infoPtr, lpnmh);
10604 case HDN_DIVIDERDBLCLICKW:
10605 case HDN_DIVIDERDBLCLICKA:
10606 /* FIXME: for LVS_EX_HEADERINALLVIEWS and not LV_VIEW_DETAILS
10607 we should use LVSCW_AUTOSIZE_USEHEADER, helper rework or
10608 split needed for that */
10609 LISTVIEW_SetColumnWidth(infoPtr, lpnmh->iItem, LVSCW_AUTOSIZE);
10610 return notify_forward_header(infoPtr, lpnmh);
10612 return 0;
10615 /***
10616 * DESCRIPTION:
10617 * Paint non-client area of control.
10619 * PARAMETER(S):
10620 * [I] infoPtr : valid pointer to the listview structureof the sender
10621 * [I] region : update region
10623 * RETURN:
10624 * TRUE - frame was painted
10625 * FALSE - call default window proc
10627 static BOOL LISTVIEW_NCPaint(const LISTVIEW_INFO *infoPtr, HRGN region)
10629 HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
10630 HDC dc;
10631 RECT r;
10632 HRGN cliprgn;
10633 int cxEdge = GetSystemMetrics (SM_CXEDGE),
10634 cyEdge = GetSystemMetrics (SM_CYEDGE);
10636 if (!theme)
10637 return DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)region, 0);
10639 GetWindowRect(infoPtr->hwndSelf, &r);
10641 cliprgn = CreateRectRgn (r.left + cxEdge, r.top + cyEdge,
10642 r.right - cxEdge, r.bottom - cyEdge);
10643 if (region != (HRGN)1)
10644 CombineRgn (cliprgn, cliprgn, region, RGN_AND);
10645 OffsetRect(&r, -r.left, -r.top);
10647 dc = GetDCEx(infoPtr->hwndSelf, region, DCX_WINDOW|DCX_INTERSECTRGN);
10648 OffsetRect(&r, -r.left, -r.top);
10650 if (IsThemeBackgroundPartiallyTransparent (theme, 0, 0))
10651 DrawThemeParentBackground(infoPtr->hwndSelf, dc, &r);
10652 DrawThemeBackground (theme, dc, 0, 0, &r, 0);
10653 ReleaseDC(infoPtr->hwndSelf, dc);
10655 /* Call default proc to get the scrollbars etc. painted */
10656 DefWindowProcW (infoPtr->hwndSelf, WM_NCPAINT, (WPARAM)cliprgn, 0);
10658 return FALSE;
10661 /***
10662 * DESCRIPTION:
10663 * Determines the type of structure to use.
10665 * PARAMETER(S):
10666 * [I] infoPtr : valid pointer to the listview structureof the sender
10667 * [I] hwndFrom : listview window handle
10668 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
10670 * RETURN:
10671 * Zero
10673 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
10675 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
10677 if (nCommand == NF_REQUERY)
10678 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
10680 return infoPtr->notifyFormat;
10683 /***
10684 * DESCRIPTION:
10685 * Paints/Repaints the listview control. Internal use.
10687 * PARAMETER(S):
10688 * [I] infoPtr : valid pointer to the listview structure
10689 * [I] hdc : device context handle
10691 * RETURN:
10692 * Zero
10694 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
10696 TRACE("(hdc=%p)\n", hdc);
10698 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
10700 infoPtr->bNoItemMetrics = FALSE;
10701 LISTVIEW_UpdateItemSize(infoPtr);
10702 if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
10703 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
10704 LISTVIEW_UpdateScroll(infoPtr);
10707 if (infoPtr->hwndHeader) UpdateWindow(infoPtr->hwndHeader);
10709 if (hdc)
10710 LISTVIEW_Refresh(infoPtr, hdc, NULL);
10711 else
10713 PAINTSTRUCT ps;
10715 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
10716 if (!hdc) return 1;
10717 LISTVIEW_Refresh(infoPtr, hdc, ps.fErase ? &ps.rcPaint : NULL);
10718 EndPaint(infoPtr->hwndSelf, &ps);
10721 return 0;
10724 /***
10725 * DESCRIPTION:
10726 * Paints/Repaints the listview control, WM_PAINT handler.
10728 * PARAMETER(S):
10729 * [I] infoPtr : valid pointer to the listview structure
10730 * [I] hdc : device context handle
10732 * RETURN:
10733 * Zero
10735 static inline LRESULT LISTVIEW_WMPaint(LISTVIEW_INFO *infoPtr, HDC hdc)
10737 TRACE("(hdc=%p)\n", hdc);
10739 if (!is_redrawing(infoPtr))
10740 return DefWindowProcW (infoPtr->hwndSelf, WM_PAINT, (WPARAM)hdc, 0);
10742 return LISTVIEW_Paint(infoPtr, hdc);
10745 /***
10746 * DESCRIPTION:
10747 * Paints/Repaints the listview control.
10749 * PARAMETER(S):
10750 * [I] infoPtr : valid pointer to the listview structure
10751 * [I] hdc : device context handle
10752 * [I] options : drawing options
10754 * RETURN:
10755 * Zero
10757 static LRESULT LISTVIEW_PrintClient(LISTVIEW_INFO *infoPtr, HDC hdc, DWORD options)
10759 FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
10761 if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
10762 return 0;
10764 if (options & PRF_ERASEBKGND)
10765 LISTVIEW_EraseBkgnd(infoPtr, hdc);
10767 if (options & PRF_CLIENT)
10768 LISTVIEW_Paint(infoPtr, hdc);
10770 return 0;
10774 /***
10775 * DESCRIPTION:
10776 * Processes double click messages (right mouse button).
10778 * PARAMETER(S):
10779 * [I] infoPtr : valid pointer to the listview structure
10780 * [I] wKey : key flag
10781 * [I] x,y : mouse coordinate
10783 * RETURN:
10784 * Zero
10786 static LRESULT LISTVIEW_RButtonDblClk(const LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10788 LVHITTESTINFO lvHitTestInfo;
10790 TRACE("(key=%hu,X=%u,Y=%u)\n", wKey, x, y);
10792 /* send NM_RELEASEDCAPTURE notification */
10793 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10795 /* send NM_RDBLCLK notification */
10796 lvHitTestInfo.pt.x = x;
10797 lvHitTestInfo.pt.y = y;
10798 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
10799 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
10801 return 0;
10804 /***
10805 * DESCRIPTION:
10806 * Processes WM_RBUTTONDOWN message and corresponding drag operation.
10808 * PARAMETER(S):
10809 * [I] infoPtr : valid pointer to the listview structure
10810 * [I] wKey : key flag
10811 * [I] x, y : mouse coordinate
10813 * RETURN:
10814 * Zero
10816 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, INT x, INT y)
10818 LVHITTESTINFO ht;
10819 INT item;
10821 TRACE("(key=%hu, x=%d, y=%d)\n", wKey, x, y);
10823 /* send NM_RELEASEDCAPTURE notification */
10824 if (!notify(infoPtr, NM_RELEASEDCAPTURE)) return 0;
10826 /* determine the index of the selected item */
10827 ht.pt.x = x;
10828 ht.pt.y = y;
10829 item = LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
10831 /* make sure the listview control window has the focus */
10832 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
10834 if ((item >= 0) && (item < infoPtr->nItemCount))
10836 LISTVIEW_SetItemFocus(infoPtr, item);
10837 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
10838 !LISTVIEW_GetItemState(infoPtr, item, LVIS_SELECTED))
10839 LISTVIEW_SetSelection(infoPtr, item);
10841 else
10842 LISTVIEW_DeselectAll(infoPtr);
10844 if (LISTVIEW_TrackMouse(infoPtr, ht.pt))
10846 if (ht.iItem != -1)
10848 NMLISTVIEW nmlv;
10850 memset(&nmlv, 0, sizeof(nmlv));
10851 nmlv.iItem = ht.iItem;
10852 nmlv.ptAction = ht.pt;
10854 notify_listview(infoPtr, LVN_BEGINRDRAG, &nmlv);
10857 else
10859 SetFocus(infoPtr->hwndSelf);
10861 ht.pt.x = x;
10862 ht.pt.y = y;
10863 LISTVIEW_HitTest(infoPtr, &ht, TRUE, FALSE);
10865 if (notify_click(infoPtr, NM_RCLICK, &ht))
10867 /* Send a WM_CONTEXTMENU message in response to the WM_RBUTTONUP */
10868 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
10869 (WPARAM)infoPtr->hwndSelf, (LPARAM)GetMessagePos());
10873 return 0;
10876 /***
10877 * DESCRIPTION:
10878 * Sets the cursor.
10880 * PARAMETER(S):
10881 * [I] infoPtr : valid pointer to the listview structure
10882 * [I] hwnd : window handle of window containing the cursor
10883 * [I] nHittest : hit-test code
10884 * [I] wMouseMsg : ideintifier of the mouse message
10886 * RETURN:
10887 * TRUE if cursor is set
10888 * FALSE otherwise
10890 static BOOL LISTVIEW_SetCursor(const LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
10892 LVHITTESTINFO lvHitTestInfo;
10894 if (!LISTVIEW_IsHotTracking(infoPtr)) goto forward;
10896 if (!infoPtr->hHotCursor) goto forward;
10898 GetCursorPos(&lvHitTestInfo.pt);
10899 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) goto forward;
10901 SetCursor(infoPtr->hHotCursor);
10903 return TRUE;
10905 forward:
10907 return DefWindowProcW(infoPtr->hwndSelf, WM_SETCURSOR, wParam, lParam);
10910 /***
10911 * DESCRIPTION:
10912 * Sets the focus.
10914 * PARAMETER(S):
10915 * [I] infoPtr : valid pointer to the listview structure
10916 * [I] hwndLoseFocus : handle of previously focused window
10918 * RETURN:
10919 * Zero
10921 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
10923 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
10925 /* if we have the focus already, there's nothing to do */
10926 if (infoPtr->bFocus) return 0;
10928 /* send NM_SETFOCUS notification */
10929 if (!notify(infoPtr, NM_SETFOCUS)) return 0;
10931 /* set window focus flag */
10932 infoPtr->bFocus = TRUE;
10934 /* put the focus rect back on */
10935 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
10937 /* redraw all visible selected items */
10938 LISTVIEW_InvalidateSelectedItems(infoPtr);
10940 return 0;
10943 /***
10944 * DESCRIPTION:
10945 * Sets the font.
10947 * PARAMETER(S):
10948 * [I] infoPtr : valid pointer to the listview structure
10949 * [I] fRedraw : font handle
10950 * [I] fRedraw : redraw flag
10952 * RETURN:
10953 * Zero
10955 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
10957 HFONT oldFont = infoPtr->hFont;
10958 INT oldHeight = infoPtr->nItemHeight;
10960 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
10962 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
10963 if (infoPtr->hFont == oldFont) return 0;
10965 LISTVIEW_SaveTextMetrics(infoPtr);
10967 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
10969 if (infoPtr->uView == LV_VIEW_DETAILS)
10971 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
10972 LISTVIEW_UpdateSize(infoPtr);
10973 LISTVIEW_UpdateScroll(infoPtr);
10975 else if (infoPtr->nItemHeight != oldHeight)
10976 LISTVIEW_UpdateScroll(infoPtr);
10978 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
10980 return 0;
10983 /***
10984 * DESCRIPTION:
10985 * Message handling for WM_SETREDRAW.
10986 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
10988 * PARAMETER(S):
10989 * [I] infoPtr : valid pointer to the listview structure
10990 * [I] redraw: state of redraw flag
10992 * RETURN:
10993 * Zero.
10995 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL redraw)
10997 TRACE("old=%d, new=%d\n", infoPtr->redraw, redraw);
10999 if (infoPtr->redraw == !!redraw)
11000 return 0;
11002 if (!(infoPtr->redraw = !!redraw))
11003 return 0;
11005 if (is_autoarrange(infoPtr))
11006 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11007 LISTVIEW_UpdateScroll(infoPtr);
11009 /* despite what the WM_SETREDRAW docs says, apps expect us
11010 * to invalidate the listview here... stupid! */
11011 LISTVIEW_InvalidateList(infoPtr);
11013 return 0;
11016 /***
11017 * DESCRIPTION:
11018 * Resizes the listview control. This function processes WM_SIZE
11019 * messages. At this time, the width and height are not used.
11021 * PARAMETER(S):
11022 * [I] infoPtr : valid pointer to the listview structure
11023 * [I] Width : new width
11024 * [I] Height : new height
11026 * RETURN:
11027 * Zero
11029 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
11031 RECT rcOld = infoPtr->rcList;
11033 TRACE("(width=%d, height=%d)\n", Width, Height);
11035 LISTVIEW_UpdateSize(infoPtr);
11036 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
11038 /* do not bother with display related stuff if we're not redrawing */
11039 if (!is_redrawing(infoPtr)) return 0;
11041 if (is_autoarrange(infoPtr))
11042 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11044 LISTVIEW_UpdateScroll(infoPtr);
11046 /* refresh all only for lists whose height changed significantly */
11047 if ((infoPtr->uView == LV_VIEW_LIST) &&
11048 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
11049 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
11050 LISTVIEW_InvalidateList(infoPtr);
11052 return 0;
11055 /***
11056 * DESCRIPTION:
11057 * Sets the size information.
11059 * PARAMETER(S):
11060 * [I] infoPtr : valid pointer to the listview structure
11062 * RETURN:
11063 * None
11065 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
11067 TRACE("uView=%d, rcList(old)=%s\n", infoPtr->uView, wine_dbgstr_rect(&infoPtr->rcList));
11069 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
11071 if (infoPtr->uView == LV_VIEW_LIST)
11073 /* Apparently the "LIST" style is supposed to have the same
11074 * number of items in a column even if there is no scroll bar.
11075 * Since if a scroll bar already exists then the bottom is already
11076 * reduced, only reduce if the scroll bar does not currently exist.
11077 * The "2" is there to mimic the native control. I think it may be
11078 * related to either padding or edges. (GLA 7/2002)
11080 if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_HSCROLL))
11081 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
11082 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
11085 /* if control created invisible header isn't created */
11086 if (infoPtr->hwndHeader)
11088 HDLAYOUT hl;
11089 WINDOWPOS wp;
11091 hl.prc = &infoPtr->rcList;
11092 hl.pwpos = &wp;
11093 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11094 TRACE(" wp.flags=0x%08x, wp=%d,%d (%dx%d)\n", wp.flags, wp.x, wp.y, wp.cx, wp.cy);
11096 if (LISTVIEW_IsHeaderEnabled(infoPtr))
11097 wp.flags |= SWP_SHOWWINDOW;
11098 else
11100 wp.flags |= SWP_HIDEWINDOW;
11101 wp.cy = 0;
11104 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
11105 TRACE(" after SWP wp=%d,%d (%dx%d)\n", wp.x, wp.y, wp.cx, wp.cy);
11107 infoPtr->rcList.top = max(wp.cy, 0);
11109 /* extra padding for grid */
11110 if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
11111 infoPtr->rcList.top += 2;
11113 TRACE(" rcList=%s\n", wine_dbgstr_rect(&infoPtr->rcList));
11116 /***
11117 * DESCRIPTION:
11118 * Processes WM_STYLECHANGED messages.
11120 * PARAMETER(S):
11121 * [I] infoPtr : valid pointer to the listview structure
11122 * [I] wStyleType : window style type (normal or extended)
11123 * [I] lpss : window style information
11125 * RETURN:
11126 * Zero
11128 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
11129 const STYLESTRUCT *lpss)
11131 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
11132 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
11133 UINT style;
11135 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11136 wStyleType, lpss->styleOld, lpss->styleNew);
11138 if (wStyleType != GWL_STYLE) return 0;
11140 infoPtr->dwStyle = lpss->styleNew;
11142 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
11143 ((lpss->styleNew & WS_HSCROLL) == 0))
11144 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
11146 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
11147 ((lpss->styleNew & WS_VSCROLL) == 0))
11148 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
11150 if (uNewView != uOldView)
11152 HIMAGELIST himl;
11154 /* LVM_SETVIEW doesn't change window style bits within LVS_TYPEMASK,
11155 changing style updates current view only when view bits change. */
11156 map_style_view(infoPtr);
11157 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
11158 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
11160 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
11161 SetRectEmpty(&infoPtr->rcFocus);
11163 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
11164 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
11166 if (uNewView == LVS_REPORT)
11168 HDLAYOUT hl;
11169 WINDOWPOS wp;
11171 LISTVIEW_CreateHeader( infoPtr );
11173 hl.prc = &infoPtr->rcList;
11174 hl.pwpos = &wp;
11175 SendMessageW( infoPtr->hwndHeader, HDM_LAYOUT, 0, (LPARAM)&hl );
11176 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy,
11177 wp.flags | ((infoPtr->dwStyle & LVS_NOCOLUMNHEADER)
11178 ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
11181 LISTVIEW_UpdateItemSize(infoPtr);
11184 if (uNewView == LVS_REPORT || infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS)
11186 if ((lpss->styleOld ^ lpss->styleNew) & LVS_NOCOLUMNHEADER)
11188 if (lpss->styleNew & LVS_NOCOLUMNHEADER)
11190 /* Turn off the header control */
11191 style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE);
11192 TRACE("Hide header control, was 0x%08x\n", style);
11193 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, style | HDS_HIDDEN);
11194 } else {
11195 /* Turn on the header control */
11196 if ((style = GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE)) & HDS_HIDDEN)
11198 TRACE("Show header control, was 0x%08x\n", style);
11199 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE, (style & ~HDS_HIDDEN) | WS_VISIBLE);
11205 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
11206 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
11207 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
11209 /* update the size of the client area */
11210 LISTVIEW_UpdateSize(infoPtr);
11212 /* add scrollbars if needed */
11213 LISTVIEW_UpdateScroll(infoPtr);
11215 /* invalidate client area + erase background */
11216 LISTVIEW_InvalidateList(infoPtr);
11218 return 0;
11221 /***
11222 * DESCRIPTION:
11223 * Processes WM_STYLECHANGING messages.
11225 * PARAMETER(S):
11226 * [I] wStyleType : window style type (normal or extended)
11227 * [I0] lpss : window style information
11229 * RETURN:
11230 * Zero
11232 static INT LISTVIEW_StyleChanging(WPARAM wStyleType,
11233 STYLESTRUCT *lpss)
11235 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
11236 wStyleType, lpss->styleOld, lpss->styleNew);
11238 /* don't forward LVS_OWNERDATA only if not already set to */
11239 if ((lpss->styleNew ^ lpss->styleOld) & LVS_OWNERDATA)
11241 if (lpss->styleOld & LVS_OWNERDATA)
11242 lpss->styleNew |= LVS_OWNERDATA;
11243 else
11244 lpss->styleNew &= ~LVS_OWNERDATA;
11247 return 0;
11250 /***
11251 * DESCRIPTION:
11252 * Processes WM_SHOWWINDOW messages.
11254 * PARAMETER(S):
11255 * [I] infoPtr : valid pointer to the listview structure
11256 * [I] bShown : window is being shown (FALSE when hidden)
11257 * [I] iStatus : window show status
11259 * RETURN:
11260 * Zero
11262 static LRESULT LISTVIEW_ShowWindow(LISTVIEW_INFO *infoPtr, WPARAM bShown, LPARAM iStatus)
11264 /* header delayed creation */
11265 if ((infoPtr->uView == LV_VIEW_DETAILS) && bShown)
11267 LISTVIEW_CreateHeader(infoPtr);
11269 if (!(LVS_NOCOLUMNHEADER & infoPtr->dwStyle))
11270 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
11273 return DefWindowProcW(infoPtr->hwndSelf, WM_SHOWWINDOW, bShown, iStatus);
11276 /***
11277 * DESCRIPTION:
11278 * Processes CCM_GETVERSION messages.
11280 * PARAMETER(S):
11281 * [I] infoPtr : valid pointer to the listview structure
11283 * RETURN:
11284 * Current version
11286 static inline LRESULT LISTVIEW_GetVersion(const LISTVIEW_INFO *infoPtr)
11288 return infoPtr->iVersion;
11291 /***
11292 * DESCRIPTION:
11293 * Processes CCM_SETVERSION messages.
11295 * PARAMETER(S):
11296 * [I] infoPtr : valid pointer to the listview structure
11297 * [I] iVersion : version to be set
11299 * RETURN:
11300 * -1 when requested version is greater than DLL version;
11301 * previous version otherwise
11303 static LRESULT LISTVIEW_SetVersion(LISTVIEW_INFO *infoPtr, DWORD iVersion)
11305 INT iOldVersion = infoPtr->iVersion;
11307 if (iVersion > COMCTL32_VERSION)
11308 return -1;
11310 infoPtr->iVersion = iVersion;
11312 TRACE("new version %d\n", iVersion);
11314 return iOldVersion;
11317 /***
11318 * DESCRIPTION:
11319 * Window procedure of the listview control.
11322 static LRESULT WINAPI
11323 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
11325 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongPtrW(hwnd, 0);
11327 TRACE("(hwnd=%p uMsg=%x wParam=%lx lParam=%lx)\n", hwnd, uMsg, wParam, lParam);
11329 if (!infoPtr && (uMsg != WM_NCCREATE))
11330 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11332 switch (uMsg)
11334 case LVM_APPROXIMATEVIEWRECT:
11335 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
11336 LOWORD(lParam), HIWORD(lParam));
11337 case LVM_ARRANGE:
11338 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
11340 case LVM_CANCELEDITLABEL:
11341 return LISTVIEW_CancelEditLabel(infoPtr);
11343 case LVM_CREATEDRAGIMAGE:
11344 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
11346 case LVM_DELETEALLITEMS:
11347 return LISTVIEW_DeleteAllItems(infoPtr, FALSE);
11349 case LVM_DELETECOLUMN:
11350 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
11352 case LVM_DELETEITEM:
11353 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
11355 case LVM_EDITLABELA:
11356 case LVM_EDITLABELW:
11357 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam,
11358 uMsg == LVM_EDITLABELW);
11359 /* case LVM_ENABLEGROUPVIEW: */
11361 case LVM_ENSUREVISIBLE:
11362 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
11364 case LVM_FINDITEMW:
11365 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
11367 case LVM_FINDITEMA:
11368 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
11370 case LVM_GETBKCOLOR:
11371 return infoPtr->clrBk;
11373 /* case LVM_GETBKIMAGE: */
11375 case LVM_GETCALLBACKMASK:
11376 return infoPtr->uCallbackMask;
11378 case LVM_GETCOLUMNA:
11379 case LVM_GETCOLUMNW:
11380 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11381 uMsg == LVM_GETCOLUMNW);
11383 case LVM_GETCOLUMNORDERARRAY:
11384 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11386 case LVM_GETCOLUMNWIDTH:
11387 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
11389 case LVM_GETCOUNTPERPAGE:
11390 return LISTVIEW_GetCountPerPage(infoPtr);
11392 case LVM_GETEDITCONTROL:
11393 return (LRESULT)infoPtr->hwndEdit;
11395 case LVM_GETEXTENDEDLISTVIEWSTYLE:
11396 return infoPtr->dwLvExStyle;
11398 /* case LVM_GETGROUPINFO: */
11400 /* case LVM_GETGROUPMETRICS: */
11402 case LVM_GETHEADER:
11403 return (LRESULT)infoPtr->hwndHeader;
11405 case LVM_GETHOTCURSOR:
11406 return (LRESULT)infoPtr->hHotCursor;
11408 case LVM_GETHOTITEM:
11409 return infoPtr->nHotItem;
11411 case LVM_GETHOVERTIME:
11412 return infoPtr->dwHoverTime;
11414 case LVM_GETIMAGELIST:
11415 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
11417 /* case LVM_GETINSERTMARK: */
11419 /* case LVM_GETINSERTMARKCOLOR: */
11421 /* case LVM_GETINSERTMARKRECT: */
11423 case LVM_GETISEARCHSTRINGA:
11424 case LVM_GETISEARCHSTRINGW:
11425 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
11426 return FALSE;
11428 case LVM_GETITEMA:
11429 case LVM_GETITEMW:
11430 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_GETITEMW);
11432 case LVM_GETITEMCOUNT:
11433 return infoPtr->nItemCount;
11435 case LVM_GETITEMPOSITION:
11436 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
11438 case LVM_GETITEMRECT:
11439 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
11441 case LVM_GETITEMSPACING:
11442 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
11444 case LVM_GETITEMSTATE:
11445 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
11447 case LVM_GETITEMTEXTA:
11448 case LVM_GETITEMTEXTW:
11449 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11450 uMsg == LVM_GETITEMTEXTW);
11452 case LVM_GETNEXTITEM:
11453 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
11455 case LVM_GETNUMBEROFWORKAREAS:
11456 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
11457 return 1;
11459 case LVM_GETORIGIN:
11460 if (!lParam) return FALSE;
11461 if (infoPtr->uView == LV_VIEW_DETAILS ||
11462 infoPtr->uView == LV_VIEW_LIST) return FALSE;
11463 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
11464 return TRUE;
11466 /* case LVM_GETOUTLINECOLOR: */
11468 /* case LVM_GETSELECTEDCOLUMN: */
11470 case LVM_GETSELECTEDCOUNT:
11471 return LISTVIEW_GetSelectedCount(infoPtr);
11473 case LVM_GETSELECTIONMARK:
11474 return infoPtr->nSelectionMark;
11476 case LVM_GETSTRINGWIDTHA:
11477 case LVM_GETSTRINGWIDTHW:
11478 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam,
11479 uMsg == LVM_GETSTRINGWIDTHW);
11481 case LVM_GETSUBITEMRECT:
11482 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
11484 case LVM_GETTEXTBKCOLOR:
11485 return infoPtr->clrTextBk;
11487 case LVM_GETTEXTCOLOR:
11488 return infoPtr->clrText;
11490 /* case LVM_GETTILEINFO: */
11492 /* case LVM_GETTILEVIEWINFO: */
11494 case LVM_GETTOOLTIPS:
11495 if( !infoPtr->hwndToolTip )
11496 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
11497 return (LRESULT)infoPtr->hwndToolTip;
11499 case LVM_GETTOPINDEX:
11500 return LISTVIEW_GetTopIndex(infoPtr);
11502 case LVM_GETUNICODEFORMAT:
11503 return (infoPtr->notifyFormat == NFR_UNICODE);
11505 case LVM_GETVIEW:
11506 return infoPtr->uView;
11508 case LVM_GETVIEWRECT:
11509 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
11511 case LVM_GETWORKAREAS:
11512 FIXME("LVM_GETWORKAREAS: unimplemented\n");
11513 return FALSE;
11515 /* case LVM_HASGROUP: */
11517 case LVM_HITTEST:
11518 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, TRUE);
11520 case LVM_INSERTCOLUMNA:
11521 case LVM_INSERTCOLUMNW:
11522 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11523 uMsg == LVM_INSERTCOLUMNW);
11525 /* case LVM_INSERTGROUP: */
11527 /* case LVM_INSERTGROUPSORTED: */
11529 case LVM_INSERTITEMA:
11530 case LVM_INSERTITEMW:
11531 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, uMsg == LVM_INSERTITEMW);
11533 /* case LVM_INSERTMARKHITTEST: */
11535 /* case LVM_ISGROUPVIEWENABLED: */
11537 case LVM_ISITEMVISIBLE:
11538 return LISTVIEW_IsItemVisible(infoPtr, (INT)wParam);
11540 case LVM_MAPIDTOINDEX:
11541 return LISTVIEW_MapIdToIndex(infoPtr, (UINT)wParam);
11543 case LVM_MAPINDEXTOID:
11544 return LISTVIEW_MapIndexToId(infoPtr, (INT)wParam);
11546 /* case LVM_MOVEGROUP: */
11548 /* case LVM_MOVEITEMTOGROUP: */
11550 case LVM_REDRAWITEMS:
11551 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
11553 /* case LVM_REMOVEALLGROUPS: */
11555 /* case LVM_REMOVEGROUP: */
11557 case LVM_SCROLL:
11558 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
11560 case LVM_SETBKCOLOR:
11561 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
11563 /* case LVM_SETBKIMAGE: */
11565 case LVM_SETCALLBACKMASK:
11566 infoPtr->uCallbackMask = (UINT)wParam;
11567 return TRUE;
11569 case LVM_SETCOLUMNA:
11570 case LVM_SETCOLUMNW:
11571 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam,
11572 uMsg == LVM_SETCOLUMNW);
11574 case LVM_SETCOLUMNORDERARRAY:
11575 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
11577 case LVM_SETCOLUMNWIDTH:
11578 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
11580 case LVM_SETEXTENDEDLISTVIEWSTYLE:
11581 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
11583 /* case LVM_SETGROUPINFO: */
11585 /* case LVM_SETGROUPMETRICS: */
11587 case LVM_SETHOTCURSOR:
11588 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
11590 case LVM_SETHOTITEM:
11591 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
11593 case LVM_SETHOVERTIME:
11594 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)lParam);
11596 case LVM_SETICONSPACING:
11597 if(lParam == -1)
11598 return LISTVIEW_SetIconSpacing(infoPtr, -1, -1);
11599 return LISTVIEW_SetIconSpacing(infoPtr, LOWORD(lParam), HIWORD(lParam));
11601 case LVM_SETIMAGELIST:
11602 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
11604 /* case LVM_SETINFOTIP: */
11606 /* case LVM_SETINSERTMARK: */
11608 /* case LVM_SETINSERTMARKCOLOR: */
11610 case LVM_SETITEMA:
11611 case LVM_SETITEMW:
11613 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
11614 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, (uMsg == LVM_SETITEMW));
11617 case LVM_SETITEMCOUNT:
11618 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
11620 case LVM_SETITEMPOSITION:
11622 POINT pt;
11623 pt.x = (short)LOWORD(lParam);
11624 pt.y = (short)HIWORD(lParam);
11625 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, &pt);
11628 case LVM_SETITEMPOSITION32:
11629 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, (POINT*)lParam);
11631 case LVM_SETITEMSTATE:
11632 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
11634 case LVM_SETITEMTEXTA:
11635 case LVM_SETITEMTEXTW:
11636 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam,
11637 uMsg == LVM_SETITEMTEXTW);
11639 /* case LVM_SETOUTLINECOLOR: */
11641 /* case LVM_SETSELECTEDCOLUMN: */
11643 case LVM_SETSELECTIONMARK:
11644 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
11646 case LVM_SETTEXTBKCOLOR:
11647 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
11649 case LVM_SETTEXTCOLOR:
11650 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
11652 /* case LVM_SETTILEINFO: */
11654 /* case LVM_SETTILEVIEWINFO: */
11656 /* case LVM_SETTILEWIDTH: */
11658 case LVM_SETTOOLTIPS:
11659 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
11661 case LVM_SETUNICODEFORMAT:
11662 return LISTVIEW_SetUnicodeFormat(infoPtr, wParam);
11664 case LVM_SETVIEW:
11665 return LISTVIEW_SetView(infoPtr, wParam);
11667 /* case LVM_SETWORKAREAS: */
11669 /* case LVM_SORTGROUPS: */
11671 case LVM_SORTITEMS:
11672 case LVM_SORTITEMSEX:
11673 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, wParam,
11674 uMsg == LVM_SORTITEMSEX);
11675 case LVM_SUBITEMHITTEST:
11676 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
11678 case LVM_UPDATE:
11679 return LISTVIEW_Update(infoPtr, (INT)wParam);
11681 case CCM_GETVERSION:
11682 return LISTVIEW_GetVersion(infoPtr);
11684 case CCM_SETVERSION:
11685 return LISTVIEW_SetVersion(infoPtr, wParam);
11687 case WM_CHAR:
11688 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
11690 case WM_COMMAND:
11691 return LISTVIEW_Command(infoPtr, wParam, lParam);
11693 case WM_NCCREATE:
11694 return LISTVIEW_NCCreate(hwnd, (LPCREATESTRUCTW)lParam);
11696 case WM_CREATE:
11697 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
11699 case WM_DESTROY:
11700 return LISTVIEW_Destroy(infoPtr);
11702 case WM_ENABLE:
11703 return LISTVIEW_Enable(infoPtr);
11705 case WM_ERASEBKGND:
11706 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
11708 case WM_GETDLGCODE:
11709 return DLGC_WANTCHARS | DLGC_WANTARROWS;
11711 case WM_GETFONT:
11712 return (LRESULT)infoPtr->hFont;
11714 case WM_HSCROLL:
11715 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0);
11717 case WM_KEYDOWN:
11718 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
11720 case WM_KILLFOCUS:
11721 return LISTVIEW_KillFocus(infoPtr);
11723 case WM_LBUTTONDBLCLK:
11724 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11726 case WM_LBUTTONDOWN:
11727 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11729 case WM_LBUTTONUP:
11730 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11732 case WM_MOUSEMOVE:
11733 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11735 case WM_MOUSEHOVER:
11736 return LISTVIEW_MouseHover(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11738 case WM_NCDESTROY:
11739 return LISTVIEW_NCDestroy(infoPtr);
11741 case WM_NCPAINT:
11742 return LISTVIEW_NCPaint(infoPtr, (HRGN)wParam);
11744 case WM_NOTIFY:
11745 return LISTVIEW_Notify(infoPtr, (LPNMHDR)lParam);
11747 case WM_NOTIFYFORMAT:
11748 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
11750 case WM_PRINTCLIENT:
11751 return LISTVIEW_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
11753 case WM_PAINT:
11754 return LISTVIEW_WMPaint(infoPtr, (HDC)wParam);
11756 case WM_RBUTTONDBLCLK:
11757 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11759 case WM_RBUTTONDOWN:
11760 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
11762 case WM_SETCURSOR:
11763 return LISTVIEW_SetCursor(infoPtr, wParam, lParam);
11765 case WM_SETFOCUS:
11766 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
11768 case WM_SETFONT:
11769 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
11771 case WM_SETREDRAW:
11772 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
11774 case WM_SHOWWINDOW:
11775 return LISTVIEW_ShowWindow(infoPtr, wParam, lParam);
11777 case WM_STYLECHANGED:
11778 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
11780 case WM_STYLECHANGING:
11781 return LISTVIEW_StyleChanging(wParam, (LPSTYLESTRUCT)lParam);
11783 case WM_SYSCOLORCHANGE:
11784 COMCTL32_RefreshSysColors();
11785 return 0;
11787 /* case WM_TIMER: */
11788 case WM_THEMECHANGED:
11789 return LISTVIEW_ThemeChanged(infoPtr);
11791 case WM_VSCROLL:
11792 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0);
11794 case WM_MOUSEWHEEL:
11795 if (wParam & (MK_SHIFT | MK_CONTROL))
11796 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11797 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
11799 case WM_WINDOWPOSCHANGED:
11800 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
11802 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
11803 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
11805 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
11807 if (notify_measureitem(infoPtr)) LISTVIEW_InvalidateList(infoPtr);
11809 LISTVIEW_Size(infoPtr, ((WINDOWPOS *)lParam)->cx, ((WINDOWPOS *)lParam)->cy);
11811 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11813 /* case WM_WININICHANGE: */
11815 default:
11816 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
11817 ERR("unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
11819 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
11824 /***
11825 * DESCRIPTION:
11826 * Registers the window class.
11828 * PARAMETER(S):
11829 * None
11831 * RETURN:
11832 * None
11834 void LISTVIEW_Register(void)
11836 WNDCLASSW wndClass;
11838 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
11839 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
11840 wndClass.lpfnWndProc = LISTVIEW_WindowProc;
11841 wndClass.cbClsExtra = 0;
11842 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
11843 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
11844 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
11845 wndClass.lpszClassName = WC_LISTVIEWW;
11846 RegisterClassW(&wndClass);
11849 /***
11850 * DESCRIPTION:
11851 * Unregisters the window class.
11853 * PARAMETER(S):
11854 * None
11856 * RETURN:
11857 * None
11859 void LISTVIEW_Unregister(void)
11861 UnregisterClassW(WC_LISTVIEWW, NULL);
11864 /***
11865 * DESCRIPTION:
11866 * Handle any WM_COMMAND messages
11868 * PARAMETER(S):
11869 * [I] infoPtr : valid pointer to the listview structure
11870 * [I] wParam : the first message parameter
11871 * [I] lParam : the second message parameter
11873 * RETURN:
11874 * Zero.
11876 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
11879 TRACE("(%p %x %x %lx)\n", infoPtr, HIWORD(wParam), LOWORD(wParam), lParam);
11881 if (!infoPtr->hwndEdit) return 0;
11883 switch (HIWORD(wParam))
11885 case EN_UPDATE:
11888 * Adjust the edit window size
11890 WCHAR buffer[1024];
11891 HDC hdc = GetDC(infoPtr->hwndEdit);
11892 HFONT hFont, hOldFont = 0;
11893 RECT rect;
11894 SIZE sz;
11896 if (!infoPtr->hwndEdit || !hdc) return 0;
11897 GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
11898 GetWindowRect(infoPtr->hwndEdit, &rect);
11900 /* Select font to get the right dimension of the string */
11901 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
11902 if (hFont)
11904 hOldFont = SelectObject(hdc, hFont);
11907 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
11909 TEXTMETRICW textMetric;
11911 /* Add Extra spacing for the next character */
11912 GetTextMetricsW(hdc, &textMetric);
11913 sz.cx += (textMetric.tmMaxCharWidth * 2);
11915 SetWindowPos(infoPtr->hwndEdit, NULL, 0, 0, sz.cx,
11916 rect.bottom - rect.top, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOZORDER);
11918 if (hFont)
11919 SelectObject(hdc, hOldFont);
11921 ReleaseDC(infoPtr->hwndEdit, hdc);
11923 break;
11925 case EN_KILLFOCUS:
11927 LISTVIEW_CancelEditLabel(infoPtr);
11928 break;
11931 default:
11932 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
11935 return 0;