The LVN_GETDISPINFO notify message should point to the same iSubItem
[wine/dcerpc.git] / dlls / comctl32 / listview.c
blob2b7950b470ab10ca505596118426e326a6a9f4f2
1 /*
2 * Listview control
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999 Luc Tourangeau
6 * Copyright 2000 Jason Mawdsley
7 * Copyright 2001 CodeWeavers Inc.
8 * Copyright 2002 Dimitrie O. Paun
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * NOTES
26 * This code was audited for completeness against the documented features
27 * of Comctl32.dll version 6.0 on Oct. 21, 2002, by Dimitrie O. Paun.
29 * Unless otherwise noted, we belive this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
33 * TODO:
35 * Features
36 * -- Hot item handling, mouse hovering
37 * -- Workareas support
38 * -- Tilemode support
39 * -- Groups support
41 * Bugs
42 * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
43 * -- Support CustonDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs.
44 * -- in LISTVIEW_AddGroupSelection, se whould send LVN_ODSTATECHANGED
45 * -- LVA_SNAPTOGRID not implemented
46 * -- LISTVIEW_ApproximateViewRect partially implemented
47 * -- LISTVIEW_[GS]etColumnOrderArray stubs
48 * -- LISTVIEW_SetColumnWidth ignores header images & bitmap
49 * -- LISTVIEW_SetIconSpacing is incomplete
50 * -- LISTVIEW_SortItems is broken
51 * -- LISTVIEW_StyleChanged doesn't handle some changes too well
53 * Speedups
54 * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
55 * linear in the number of items in the list, and this is
56 * unacceptable for large lists.
57 * -- in sorted mode, LISTVIEW_InsertItemT sorts the array,
58 * instead of inserting in the right spot
59 * -- we should keep an ordered array of coordinates in iconic mode
60 * this would allow to frame items (iterator_frameditems),
61 * and find nearest item (LVFI_NEARESTXY) a lot more efficiently
63 * Flags
64 * -- LVIF_COLUMNS
65 * -- LVIF_GROUPID
66 * -- LVIF_NORECOMPUTE
68 * States
69 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
70 * -- LVIS_CUT
71 * -- LVIS_DROPHILITED
72 * -- LVIS_OVERLAYMASK
74 * Styles
75 * -- LVS_NOLABELWRAP
76 * -- LVS_NOSCROLL (see Q137520)
77 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
79 * Extended Styles
80 * -- LVS_EX_BORDERSELECT
81 * -- LVS_EX_FLATSB
82 * -- LVS_EX_GRIDLINES
83 * -- LVS_EX_HEADERDRAGDROP
84 * -- LVS_EX_INFOTIP
85 * -- LVS_EX_LABELTIP
86 * -- LVS_EX_MULTIWORKAREAS
87 * -- LVS_EX_ONECLICKACTIVATE
88 * -- LVS_EX_REGIONAL
89 * -- LVS_EX_SIMPLESELECT
90 * -- LVS_EX_TRACKSELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
95 * Notifications:
96 * -- LVN_BEGINRDRAG
97 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
98 * -- LVN_GETINFOTIP
99 * -- LVN_HOTTRACK
100 * -- LVN_MARQUEEBEGIN
101 * -- LVN_ODFINDITEM
102 * -- LVN_ODSTATECHANGED
103 * -- LVN_SETDISPINFO
104 * -- NM_HOVER
106 * Messages:
107 * -- LVM_CANCELEDITLABEL
108 * -- LVM_ENABLEGROUPVIEW
109 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
110 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
111 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
112 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
113 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
114 * -- LVM_GETINSERTMARKRECT
115 * -- LVM_GETNUMBEROFWORKAREAS
116 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
117 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
118 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
119 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
120 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
121 * -- LVM_GETTOOLTIPS, LVM_SETTOOLTIPS
122 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
123 * -- LVM_GETVIEW, LVM_SETVIEW
124 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
125 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
126 * -- LVM_INSERTGROUPSORTED
127 * -- LVM_INSERTMARKHITTEST
128 * -- LVM_ISGROUPVIEWENABLED
129 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
130 * -- LVM_MOVEGROUP
131 * -- LVM_MOVEITEMTOGROUP
132 * -- LVM_SETINFOTIP
133 * -- LVM_SETTILEWIDTH
134 * -- LVM_SORTGROUPS
135 * -- LVM_SORTITEMSEX
137 * Known differences in message stream from native control (not known if
138 * these differences cause problems):
139 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
140 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
141 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
142 * processing for "USEDOUBLECLICKTIME".
145 #include "config.h"
146 #include "wine/port.h"
148 #include <assert.h>
149 #include <ctype.h>
150 #include <string.h>
151 #include <stdlib.h>
152 #include <stdarg.h>
153 #include <stdio.h>
155 #include "windef.h"
156 #include "winbase.h"
157 #include "winnt.h"
158 #include "wingdi.h"
159 #include "winuser.h"
160 #include "winnls.h"
161 #include "commctrl.h"
162 #include "comctl32.h"
164 #include "wine/debug.h"
165 #include "wine/unicode.h"
167 WINE_DEFAULT_DEBUG_CHANNEL(listview);
169 /* make sure you set this to 0 for production use! */
170 #define DEBUG_RANGES 1
172 typedef struct tagCOLUMN_INFO
174 RECT rcHeader; /* tracks the header's rectangle */
175 int fmt; /* same as LVCOLUMN.fmt */
176 } COLUMN_INFO;
178 typedef struct tagITEMHDR
180 LPWSTR pszText;
181 INT iImage;
182 } ITEMHDR, *LPITEMHDR;
184 typedef struct tagSUBITEM_INFO
186 ITEMHDR hdr;
187 INT iSubItem;
188 } SUBITEM_INFO;
190 typedef struct tagITEM_INFO
192 ITEMHDR hdr;
193 UINT state;
194 LPARAM lParam;
195 INT iIndent;
196 } ITEM_INFO;
198 typedef struct tagRANGE
200 INT lower;
201 INT upper;
202 } RANGE;
204 typedef struct tagRANGES
206 HDPA hdpa;
207 } *RANGES;
209 typedef struct tagITERATOR
211 INT nItem;
212 INT nSpecial;
213 RANGE range;
214 RANGES ranges;
215 INT index;
216 } ITERATOR;
218 typedef struct tagLISTVIEW_INFO
220 HWND hwndSelf;
221 HBRUSH hBkBrush;
222 COLORREF clrBk;
223 COLORREF clrText;
224 COLORREF clrTextBk;
225 COLORREF clrTextBkDefault;
226 HIMAGELIST himlNormal;
227 HIMAGELIST himlSmall;
228 HIMAGELIST himlState;
229 BOOL bLButtonDown;
230 BOOL bRButtonDown;
231 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
232 INT nItemHeight;
233 INT nItemWidth;
234 RANGES selectionRanges;
235 INT nSelectionMark;
236 INT nHotItem;
237 SHORT notifyFormat;
238 HWND hwndNotify;
239 RECT rcList; /* This rectangle is really the window
240 * client rectangle possibly reduced by the
241 * horizontal scroll bar and/or header - see
242 * LISTVIEW_UpdateSize. This rectangle offset
243 * by the LISTVIEW_GetOrigin value is in
244 * client coordinates */
245 SIZE iconSize;
246 SIZE iconSpacing;
247 SIZE iconStateSize;
248 UINT uCallbackMask;
249 HWND hwndHeader;
250 HCURSOR hHotCursor;
251 HFONT hDefaultFont;
252 HFONT hFont;
253 INT ntmHeight; /* Some cached metrics of the font used */
254 INT ntmAveCharWidth; /* by the listview to draw items */
255 BOOL bRedraw; /* Turns on/off repaints & invalidations */
256 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
257 BOOL bFocus;
258 BOOL bDoChangeNotify; /* send change notification messages? */
259 INT nFocusedItem;
260 RECT rcFocus;
261 DWORD dwStyle; /* the cached window GWL_STYLE */
262 DWORD dwLvExStyle; /* extended listview style */
263 INT nItemCount; /* the number of items in the list */
264 HDPA hdpaItems; /* array ITEM_INFO pointers */
265 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
266 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
267 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
268 POINT currIconPos; /* this is the position next icon will be placed */
269 PFNLVCOMPARE pfnCompare;
270 LPARAM lParamSort;
271 HWND hwndEdit;
272 WNDPROC EditWndProc;
273 INT nEditLabelItem;
274 DWORD dwHoverTime;
275 HWND hwndToolTip;
277 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
279 DWORD lastKeyPressTimestamp;
280 WPARAM charCode;
281 INT nSearchParamLength;
282 WCHAR szSearchParam[ MAX_PATH ];
283 BOOL bIsDrawing;
284 } LISTVIEW_INFO;
287 * constants
289 /* How many we debug buffer to allocate */
290 #define DEBUG_BUFFERS 20
291 /* The size of a single debug bbuffer */
292 #define DEBUG_BUFFER_SIZE 256
294 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
295 #define SB_INTERNAL -1
297 /* maximum size of a label */
298 #define DISP_TEXT_SIZE 512
300 /* padding for items in list and small icon display modes */
301 #define WIDTH_PADDING 12
303 /* padding for items in list, report and small icon display modes */
304 #define HEIGHT_PADDING 1
306 /* offset of items in report display mode */
307 #define REPORT_MARGINX 2
309 /* padding for icon in large icon display mode
310 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
311 * that HITTEST will see.
312 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
313 * ICON_TOP_PADDING - sum of the two above.
314 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
315 * LABEL_HOR_PADDING - between text and sides of box
316 * LABEL_VERT_PADDING - between bottom of text and end of box
318 * ICON_LR_PADDING - additional width above icon size.
319 * ICON_LR_HALF - half of the above value
321 #define ICON_TOP_PADDING_NOTHITABLE 2
322 #define ICON_TOP_PADDING_HITABLE 2
323 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
324 #define ICON_BOTTOM_PADDING 4
325 #define LABEL_HOR_PADDING 5
326 #define LABEL_VERT_PADDING 7
327 #define ICON_LR_PADDING 16
328 #define ICON_LR_HALF (ICON_LR_PADDING/2)
330 /* default label width for items in list and small icon display modes */
331 #define DEFAULT_LABEL_WIDTH 40
333 /* default column width for items in list display mode */
334 #define DEFAULT_COLUMN_WIDTH 128
336 /* Size of "line" scroll for V & H scrolls */
337 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
339 /* Padding betwen image and label */
340 #define IMAGE_PADDING 2
342 /* Padding behind the label */
343 #define TRAILING_LABEL_PADDING 12
344 #define TRAILING_HEADER_PADDING 11
346 /* Border for the icon caption */
347 #define CAPTION_BORDER 2
349 /* Standard DrawText flags */
350 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
351 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
352 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
354 /* The time in milliseconds to reset the search in the list */
355 #define KEY_DELAY 450
357 /* Dump the LISTVIEW_INFO structure to the debug channel */
358 #define LISTVIEW_DUMP(iP) do { \
359 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
360 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
361 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
362 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
363 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
364 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
365 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
366 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
367 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
368 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
369 } while(0)
372 * forward declarations
374 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
375 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
376 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
377 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
378 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
379 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
380 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
381 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
382 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
383 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
384 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
385 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
386 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
387 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
388 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
389 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
390 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
391 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
392 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
393 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
394 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
395 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
396 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
397 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
398 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
399 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
401 /******** Text handling functions *************************************/
403 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
404 * text string. The string may be ANSI or Unicode, in which case
405 * the boolean isW tells us the type of the string.
407 * The name of the function tell what type of strings it expects:
408 * W: Unicode, T: ANSI/Unicode - function of isW
411 static inline BOOL is_textW(LPCWSTR text)
413 return text != NULL && text != LPSTR_TEXTCALLBACKW;
416 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
418 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
419 return is_textW(text);
422 static inline int textlenT(LPCWSTR text, BOOL isW)
424 return !is_textT(text, isW) ? 0 :
425 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
428 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
430 if (isDestW)
431 if (isSrcW) lstrcpynW(dest, src, max);
432 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
433 else
434 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
435 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
438 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
440 LPWSTR wstr = (LPWSTR)text;
442 if (!isW && is_textT(text, isW))
444 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
445 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
446 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
448 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
449 return wstr;
452 static inline void textfreeT(LPWSTR wstr, BOOL isW)
454 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
458 * dest is a pointer to a Unicode string
459 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
461 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
463 BOOL bResult = TRUE;
465 if (src == LPSTR_TEXTCALLBACKW)
467 if (is_textW(*dest)) Free(*dest);
468 *dest = LPSTR_TEXTCALLBACKW;
470 else
472 LPWSTR pszText = textdupTtoW(src, isW);
473 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
474 bResult = Str_SetPtrW(dest, pszText);
475 textfreeT(pszText, isW);
477 return bResult;
481 * compares a Unicode to a Unicode/ANSI text string
483 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
485 if (!aw) return bt ? -1 : 0;
486 if (!bt) return aw ? 1 : 0;
487 if (aw == LPSTR_TEXTCALLBACKW)
488 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
489 if (bt != LPSTR_TEXTCALLBACKW)
491 LPWSTR bw = textdupTtoW(bt, isW);
492 int r = bw ? lstrcmpW(aw, bw) : 1;
493 textfreeT(bw, isW);
494 return r;
497 return 1;
500 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
502 int res;
504 n = min(min(n, strlenW(s1)), strlenW(s2));
505 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
506 return res ? res - sizeof(WCHAR) : res;
509 /******** Debugging functions *****************************************/
511 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
513 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
514 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
517 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
519 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
520 n = min(textlenT(text, isW), n);
521 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
524 static char* debug_getbuf()
526 static int index = 0;
527 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
528 return buffers[index++ % DEBUG_BUFFERS];
531 static inline const char* debugrange(const RANGE *lprng)
533 if (lprng)
535 char* buf = debug_getbuf();
536 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
537 return buf;
538 } else return "(null)";
541 static inline const char* debugpoint(const POINT *lppt)
543 if (lppt)
545 char* buf = debug_getbuf();
546 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
547 return buf;
548 } else return "(null)";
551 static inline const char* debugrect(const RECT *rect)
553 if (rect)
555 char* buf = debug_getbuf();
556 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
557 rect->left, rect->top, rect->right, rect->bottom);
558 return buf;
559 } else return "(null)";
562 static const char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
564 char* buf = debug_getbuf(), *text = buf;
565 int len, size = DEBUG_BUFFER_SIZE;
567 if (pScrollInfo == NULL) return "(null)";
568 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
569 if (len == -1) goto end; buf += len; size -= len;
570 if (pScrollInfo->fMask & SIF_RANGE)
571 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
572 else len = 0;
573 if (len == -1) goto end; buf += len; size -= len;
574 if (pScrollInfo->fMask & SIF_PAGE)
575 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
576 else len = 0;
577 if (len == -1) goto end; buf += len; size -= len;
578 if (pScrollInfo->fMask & SIF_POS)
579 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
580 else len = 0;
581 if (len == -1) goto end; buf += len; size -= len;
582 if (pScrollInfo->fMask & SIF_TRACKPOS)
583 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
584 else len = 0;
585 if (len == -1) goto end; buf += len; size -= len;
586 goto undo;
587 end:
588 buf = text + strlen(text);
589 undo:
590 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
591 return text;
594 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
596 if (plvnm)
598 char* buf = debug_getbuf();
599 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
600 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
601 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
602 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
603 return buf;
604 } else return "(null)";
607 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
609 char* buf = debug_getbuf(), *text = buf;
610 int len, size = DEBUG_BUFFER_SIZE;
612 if (lpLVItem == NULL) return "(null)";
613 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
614 if (len == -1) goto end; buf += len; size -= len;
615 if (lpLVItem->mask & LVIF_STATE)
616 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
617 else len = 0;
618 if (len == -1) goto end; buf += len; size -= len;
619 if (lpLVItem->mask & LVIF_TEXT)
620 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
621 else len = 0;
622 if (len == -1) goto end; buf += len; size -= len;
623 if (lpLVItem->mask & LVIF_IMAGE)
624 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
625 else len = 0;
626 if (len == -1) goto end; buf += len; size -= len;
627 if (lpLVItem->mask & LVIF_PARAM)
628 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
629 else len = 0;
630 if (len == -1) goto end; buf += len; size -= len;
631 if (lpLVItem->mask & LVIF_INDENT)
632 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
633 else len = 0;
634 if (len == -1) goto end; buf += len; size -= len;
635 goto undo;
636 end:
637 buf = text + strlen(text);
638 undo:
639 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
640 return text;
643 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
645 char* buf = debug_getbuf(), *text = buf;
646 int len, size = DEBUG_BUFFER_SIZE;
648 if (lpColumn == NULL) return "(null)";
649 len = snprintf(buf, size, "{");
650 if (len == -1) goto end; buf += len; size -= len;
651 if (lpColumn->mask & LVCF_SUBITEM)
652 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
653 else len = 0;
654 if (len == -1) goto end; buf += len; size -= len;
655 if (lpColumn->mask & LVCF_FMT)
656 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
657 else len = 0;
658 if (len == -1) goto end; buf += len; size -= len;
659 if (lpColumn->mask & LVCF_WIDTH)
660 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
661 else len = 0;
662 if (len == -1) goto end; buf += len; size -= len;
663 if (lpColumn->mask & LVCF_TEXT)
664 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
665 else len = 0;
666 if (len == -1) goto end; buf += len; size -= len;
667 if (lpColumn->mask & LVCF_IMAGE)
668 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
669 else len = 0;
670 if (len == -1) goto end; buf += len; size -= len;
671 if (lpColumn->mask & LVCF_ORDER)
672 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
673 else len = 0;
674 if (len == -1) goto end; buf += len; size -= len;
675 goto undo;
676 end:
677 buf = text + strlen(text);
678 undo:
679 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
680 return text;
683 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
685 if (lpht)
687 char* buf = debug_getbuf();
688 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
689 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
690 return buf;
691 } else return "(null)";
694 /* Return the corresponding text for a given scroll value */
695 static inline LPCSTR debugscrollcode(int nScrollCode)
697 switch(nScrollCode)
699 case SB_LINELEFT: return "SB_LINELEFT";
700 case SB_LINERIGHT: return "SB_LINERIGHT";
701 case SB_PAGELEFT: return "SB_PAGELEFT";
702 case SB_PAGERIGHT: return "SB_PAGERIGHT";
703 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
704 case SB_THUMBTRACK: return "SB_THUMBTRACK";
705 case SB_ENDSCROLL: return "SB_ENDSCROLL";
706 case SB_INTERNAL: return "SB_INTERNAL";
707 default: return "unknown";
712 /******** Notification functions i************************************/
714 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
716 LRESULT result;
718 TRACE("(code=%d)\n", code);
720 pnmh->hwndFrom = infoPtr->hwndSelf;
721 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
722 pnmh->code = code;
723 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
724 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
726 TRACE(" <= %ld\n", result);
728 return result;
731 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
733 NMHDR nmh;
734 return notify_hdr(infoPtr, code, &nmh);
737 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
739 NMITEMACTIVATE nmia;
740 LVITEMW item;
742 if (htInfo) {
743 nmia.uNewState = 0;
744 nmia.uOldState = 0;
745 nmia.uChanged = 0;
746 nmia.uKeyFlags = 0;
748 item.mask = LVIF_PARAM|LVIF_STATE;
749 item.iItem = htInfo->iItem;
750 item.iSubItem = 0;
751 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
752 nmia.lParam = item.lParam;
753 nmia.uOldState = item.state;
754 nmia.uNewState = item.state | LVIS_ACTIVATING;
755 nmia.uChanged = LVIF_STATE;
758 nmia.iItem = htInfo->iItem;
759 nmia.iSubItem = htInfo->iSubItem;
760 nmia.ptAction = htInfo->pt;
762 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
763 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
764 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
766 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
769 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
771 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
772 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
775 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
777 NMLISTVIEW nmlv;
778 LVITEMW item;
780 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
781 ZeroMemory(&nmlv, sizeof(nmlv));
782 nmlv.iItem = lvht->iItem;
783 nmlv.iSubItem = lvht->iSubItem;
784 nmlv.ptAction = lvht->pt;
785 item.mask = LVIF_PARAM;
786 item.iItem = lvht->iItem;
787 item.iSubItem = 0;
788 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
789 return notify_listview(infoPtr, code, &nmlv);
792 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
794 NMLISTVIEW nmlv;
795 LVITEMW item;
797 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
798 nmlv.iItem = nItem;
799 item.mask = LVIF_PARAM;
800 item.iItem = nItem;
801 item.iSubItem = 0;
802 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
803 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
806 static int get_ansi_notification(INT unicodeNotificationCode)
808 switch (unicodeNotificationCode)
810 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
811 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
812 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
813 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
814 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
815 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
817 ERR("unknown notification %x\n", unicodeNotificationCode);
818 assert(FALSE);
819 return 0;
823 With testing on Windows 2000 it looks like the notify format
824 has nothing to do with this message. It ALWAYS seems to be
825 in ansi format.
827 infoPtr : listview struct
828 notificationCode : *Unicode* notification code
829 pdi : dispinfo structure (can be unicode or ansi)
830 isW : TRUE if dispinfo is Unicode
832 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
834 BOOL bResult = FALSE;
835 BOOL convertToAnsi = FALSE;
836 INT cchTempBufMax = 0, savCchTextMax = 0;
837 LPWSTR pszTempBuf = NULL, savPszText = NULL;
839 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
840 convertToAnsi = isW;
842 if (convertToAnsi)
844 if (notificationCode != LVN_GETDISPINFOW)
846 cchTempBufMax = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText,
847 -1, NULL, 0, NULL, NULL);
849 else
851 cchTempBufMax = pdi->item.cchTextMax;
852 *pdi->item.pszText = 0; /* make sure we don't process garbage */
855 pszTempBuf = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR) *
856 cchTempBufMax);
857 if (!pszTempBuf) return FALSE;
859 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR)
860 pszTempBuf, cchTempBufMax, NULL, NULL);
862 savCchTextMax = pdi->item.cchTextMax;
863 savPszText = pdi->item.pszText;
864 pdi->item.pszText = pszTempBuf;
865 pdi->item.cchTextMax = cchTempBufMax;
868 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat !=
869 NFR_ANSI));
871 bResult = notify_hdr(infoPtr, get_ansi_notification(notificationCode),
872 (LPNMHDR)pdi);
874 if (convertToAnsi)
876 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
877 savPszText, savCchTextMax);
878 pdi->item.pszText = savPszText; /* restores our buffer */
879 pdi->item.cchTextMax = savCchTextMax;
880 HeapFree(GetProcessHeap(), 0, pszTempBuf);
882 return bResult;
885 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
886 const RECT *rcBounds, const LVITEMW *lplvItem)
888 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
889 lpnmlvcd->nmcd.hdc = hdc;
890 lpnmlvcd->nmcd.rc = *rcBounds;
891 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
892 lpnmlvcd->clrText = infoPtr->clrText;
893 if (!lplvItem) return;
894 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
895 lpnmlvcd->iSubItem = lplvItem->iSubItem;
896 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
897 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
898 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
899 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
902 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
904 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
905 DWORD result;
907 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
908 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
909 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
910 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
911 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
912 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
913 return result;
916 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
918 /* apprently, for selected items, we have to override the returned values */
919 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
921 if (infoPtr->bFocus)
923 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
924 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
926 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
928 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
929 lpnmlvcd->clrText = comctl32_color.clrBtnText;
933 /* Set the text attributes */
934 if (lpnmlvcd->clrTextBk != CLR_NONE)
936 SetBkMode(hdc, OPAQUE);
937 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
938 SetBkColor(hdc, infoPtr->clrTextBkDefault);
939 else
940 SetBkColor(hdc,lpnmlvcd->clrTextBk);
942 else
943 SetBkMode(hdc, TRANSPARENT);
944 SetTextColor(hdc, lpnmlvcd->clrText);
947 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
949 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
952 /******** Item iterator functions **********************************/
954 static RANGES ranges_create(int count);
955 static void ranges_destroy(RANGES ranges);
956 static BOOL ranges_add(RANGES ranges, RANGE range);
957 static BOOL ranges_del(RANGES ranges, RANGE range);
958 static void ranges_dump(RANGES ranges);
960 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
962 RANGE range = { nItem, nItem + 1 };
964 return ranges_add(ranges, range);
967 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
969 RANGE range = { nItem, nItem + 1 };
971 return ranges_del(ranges, range);
974 /***
975 * ITERATOR DOCUMENTATION
977 * The iterator functions allow for easy, and convenient iteration
978 * over items of iterest in the list. Typically, you create a
979 * iterator, use it, and destroy it, as such:
980 * ITERATOR i;
982 * iterator_xxxitems(&i, ...);
983 * while (iterator_{prev,next}(&i)
985 * //code which uses i.nItem
987 * iterator_destroy(&i);
989 * where xxx is either: framed, or visible.
990 * Note that it is important that the code destroys the iterator
991 * after it's done with it, as the creation of the iterator may
992 * allocate memory, which thus needs to be freed.
994 * You can iterate both forwards, and backwards through the list,
995 * by using iterator_next or iterator_prev respectively.
997 * Lower numbered items are draw on top of higher number items in
998 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
999 * items may overlap). So, to test items, you should use
1000 * iterator_next
1001 * which lists the items top to bottom (in Z-order).
1002 * For drawing items, you should use
1003 * iterator_prev
1004 * which lists the items bottom to top (in Z-order).
1005 * If you keep iterating over the items after the end-of-items
1006 * marker (-1) is returned, the iterator will start from the
1007 * beginning. Typically, you don't need to test for -1,
1008 * because iterator_{next,prev} will return TRUE if more items
1009 * are to be iterated over, or FALSE otherwise.
1011 * Note: the iterator is defined to be bidirectional. That is,
1012 * any number of prev followed by any number of next, or
1013 * five versa, should leave the iterator at the same item:
1014 * prev * n, next * n = next * n, prev * n
1016 * The iterator has a notion of a out-of-order, special item,
1017 * which sits at the start of the list. This is used in
1018 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1019 * which needs to be first, as it may overlap other items.
1021 * The code is a bit messy because we have:
1022 * - a special item to deal with
1023 * - simple range, or composite range
1024 * - empty range.
1025 * If you find bugs, or want to add features, please make sure you
1026 * always check/modify *both* iterator_prev, and iterator_next.
1029 /****
1030 * This function iterates through the items in increasing order,
1031 * but prefixed by the special item, then -1. That is:
1032 * special, 1, 2, 3, ..., n, -1.
1033 * Each item is listed only once.
1035 static inline BOOL iterator_next(ITERATOR* i)
1037 if (i->nItem == -1)
1039 i->nItem = i->nSpecial;
1040 if (i->nItem != -1) return TRUE;
1042 if (i->nItem == i->nSpecial)
1044 if (i->ranges) i->index = 0;
1045 goto pickarange;
1048 i->nItem++;
1049 testitem:
1050 if (i->nItem == i->nSpecial) i->nItem++;
1051 if (i->nItem < i->range.upper) return TRUE;
1053 pickarange:
1054 if (i->ranges)
1056 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1057 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1058 else goto end;
1060 else if (i->nItem >= i->range.upper) goto end;
1062 i->nItem = i->range.lower;
1063 if (i->nItem >= 0) goto testitem;
1064 end:
1065 i->nItem = -1;
1066 return FALSE;
1069 /****
1070 * This function iterates through the items in decreasing order,
1071 * followed by the special item, then -1. That is:
1072 * n, n-1, ..., 3, 2, 1, special, -1.
1073 * Each item is listed only once.
1075 static inline BOOL iterator_prev(ITERATOR* i)
1077 BOOL start = FALSE;
1079 if (i->nItem == -1)
1081 start = TRUE;
1082 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1083 goto pickarange;
1085 if (i->nItem == i->nSpecial)
1087 i->nItem = -1;
1088 return FALSE;
1091 testitem:
1092 i->nItem--;
1093 if (i->nItem == i->nSpecial) i->nItem--;
1094 if (i->nItem >= i->range.lower) return TRUE;
1096 pickarange:
1097 if (i->ranges)
1099 if (i->index > 0)
1100 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1101 else goto end;
1103 else if (!start && i->nItem < i->range.lower) goto end;
1105 i->nItem = i->range.upper;
1106 if (i->nItem > 0) goto testitem;
1107 end:
1108 return (i->nItem = i->nSpecial) != -1;
1111 static RANGE iterator_range(ITERATOR* i)
1113 RANGE range;
1115 if (!i->ranges) return i->range;
1117 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1118 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1119 return range;
1122 /***
1123 * Releases resources associated with this ierator.
1125 static inline void iterator_destroy(ITERATOR* i)
1127 ranges_destroy(i->ranges);
1130 /***
1131 * Create an empty iterator.
1133 static inline BOOL iterator_empty(ITERATOR* i)
1135 ZeroMemory(i, sizeof(*i));
1136 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1137 return TRUE;
1140 /***
1141 * Create an iterator over a range.
1143 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1145 iterator_empty(i);
1146 i->range = range;
1147 return TRUE;
1150 /***
1151 * Create an iterator over a bunch of ranges.
1152 * Please note that the iterator will take ownership of the ranges,
1153 * and will free them upon destruction.
1155 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1157 iterator_empty(i);
1158 i->ranges = ranges;
1159 return TRUE;
1162 /***
1163 * Creates an iterator over the items which intersect lprc.
1165 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1167 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1168 RECT frame = *lprc, rcItem, rcTemp;
1169 POINT Origin;
1171 /* in case we fail, we want to return an empty iterator */
1172 if (!iterator_empty(i)) return FALSE;
1174 LISTVIEW_GetOrigin(infoPtr, &Origin);
1176 TRACE("(lprc=%s)\n", debugrect(lprc));
1177 OffsetRect(&frame, -Origin.x, -Origin.y);
1179 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1181 INT nItem;
1183 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1185 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1186 if (IntersectRect(&rcTemp, &rcItem, lprc))
1187 i->nSpecial = infoPtr->nFocusedItem;
1189 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1190 /* to do better here, we need to have PosX, and PosY sorted */
1191 TRACE("building icon ranges:\n");
1192 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1194 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1195 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1196 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1197 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1198 if (IntersectRect(&rcTemp, &rcItem, &frame))
1199 ranges_additem(i->ranges, nItem);
1201 return TRUE;
1203 else if (uView == LVS_REPORT)
1205 RANGE range;
1207 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1208 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1210 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1211 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1212 if (range.upper <= range.lower) return TRUE;
1213 if (!iterator_rangeitems(i, range)) return FALSE;
1214 TRACE(" report=%s\n", debugrange(&i->range));
1216 else
1218 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1219 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1220 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1221 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1222 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1223 INT lower = nFirstCol * nPerCol + nFirstRow;
1224 RANGE item_range;
1225 INT nCol;
1227 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1228 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1230 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1232 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1233 TRACE("building list ranges:\n");
1234 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1236 item_range.lower = nCol * nPerCol + nFirstRow;
1237 if(item_range.lower >= infoPtr->nItemCount) break;
1238 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1239 TRACE(" list=%s\n", debugrange(&item_range));
1240 ranges_add(i->ranges, item_range);
1244 return TRUE;
1247 /***
1248 * Creates an iterator over the items which intersect the visible region of hdc.
1250 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1252 POINT Origin, Position;
1253 RECT rcItem, rcClip;
1254 INT rgntype;
1256 rgntype = GetClipBox(hdc, &rcClip);
1257 if (rgntype == NULLREGION) return iterator_empty(i);
1258 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1259 if (rgntype == SIMPLEREGION) return TRUE;
1261 /* first deal with the special item */
1262 if (i->nSpecial != -1)
1264 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1265 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1268 /* if we can't deal with the region, we'll just go with the simple range */
1269 LISTVIEW_GetOrigin(infoPtr, &Origin);
1270 TRACE("building visible range:\n");
1271 if (!i->ranges && i->range.lower < i->range.upper)
1273 if (!(i->ranges = ranges_create(50))) return TRUE;
1274 if (!ranges_add(i->ranges, i->range))
1276 ranges_destroy(i->ranges);
1277 i->ranges = 0;
1278 return TRUE;
1282 /* now delete the invisible items from the list */
1283 while(iterator_next(i))
1285 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1286 rcItem.left = Position.x + Origin.x;
1287 rcItem.top = Position.y + Origin.y;
1288 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1289 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1290 if (!RectVisible(hdc, &rcItem))
1291 ranges_delitem(i->ranges, i->nItem);
1293 /* the iterator should restart on the next iterator_next */
1294 TRACE("done\n");
1296 return TRUE;
1299 /******** Misc helper functions ************************************/
1301 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1302 WPARAM wParam, LPARAM lParam, BOOL isW)
1304 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1305 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1308 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1310 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1312 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1313 (uView == LVS_ICON || uView == LVS_SMALLICON);
1316 /******** Internal API functions ************************************/
1318 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1320 static COLUMN_INFO mainItem;
1322 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1323 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1324 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1327 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1329 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1332 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1334 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1337 /* Listview invalidation functions: use _only_ these functions to invalidate */
1339 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1341 return infoPtr->bRedraw;
1344 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1346 if(!is_redrawing(infoPtr)) return;
1347 TRACE(" invalidating rect=%s\n", debugrect(rect));
1348 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1351 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1353 RECT rcBox;
1355 if(!is_redrawing(infoPtr)) return;
1356 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1357 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1360 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1362 POINT Origin, Position;
1363 RECT rcBox;
1365 if(!is_redrawing(infoPtr)) return;
1366 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1367 LISTVIEW_GetOrigin(infoPtr, &Origin);
1368 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1369 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1370 rcBox.top = 0;
1371 rcBox.bottom = infoPtr->nItemHeight;
1372 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1373 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1376 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1378 LISTVIEW_InvalidateRect(infoPtr, NULL);
1381 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1383 RECT rcCol;
1385 if(!is_redrawing(infoPtr)) return;
1386 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1387 rcCol.top = infoPtr->rcList.top;
1388 rcCol.bottom = infoPtr->rcList.bottom;
1389 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1392 /***
1393 * DESCRIPTION:
1394 * Retrieves the number of items that can fit vertically in the client area.
1396 * PARAMETER(S):
1397 * [I] infoPtr : valid pointer to the listview structure
1399 * RETURN:
1400 * Number of items per row.
1402 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1404 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1406 return max(nListWidth/infoPtr->nItemWidth, 1);
1409 /***
1410 * DESCRIPTION:
1411 * Retrieves the number of items that can fit horizontally in the client
1412 * area.
1414 * PARAMETER(S):
1415 * [I] infoPtr : valid pointer to the listview structure
1417 * RETURN:
1418 * Number of items per column.
1420 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1422 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1424 return max(nListHeight / infoPtr->nItemHeight, 1);
1428 /*************************************************************************
1429 * LISTVIEW_ProcessLetterKeys
1431 * Processes keyboard messages generated by pressing the letter keys
1432 * on the keyboard.
1433 * What this does is perform a case insensitive search from the
1434 * current position with the following quirks:
1435 * - If two chars or more are pressed in quick succession we search
1436 * for the corresponding string (e.g. 'abc').
1437 * - If there is a delay we wipe away the current search string and
1438 * restart with just that char.
1439 * - If the user keeps pressing the same character, whether slowly or
1440 * fast, so that the search string is entirely composed of this
1441 * character ('aaaaa' for instance), then we search for first item
1442 * that starting with that character.
1443 * - If the user types the above character in quick succession, then
1444 * we must also search for the corresponding string ('aaaaa'), and
1445 * go to that string if there is a match.
1447 * PARAMETERS
1448 * [I] hwnd : handle to the window
1449 * [I] charCode : the character code, the actual character
1450 * [I] keyData : key data
1452 * RETURNS
1454 * Zero.
1456 * BUGS
1458 * - The current implementation has a list of characters it will
1459 * accept and it ignores averything else. In particular it will
1460 * ignore accentuated characters which seems to match what
1461 * Windows does. But I'm not sure it makes sense to follow
1462 * Windows there.
1463 * - We don't sound a beep when the search fails.
1465 * SEE ALSO
1467 * TREEVIEW_ProcessLetterKeys
1469 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1471 INT nItem;
1472 INT endidx,idx;
1473 LVITEMW item;
1474 WCHAR buffer[MAX_PATH];
1475 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1477 /* simple parameter checking */
1478 if (!charCode || !keyData) return 0;
1480 /* only allow the valid WM_CHARs through */
1481 if (!isalnum(charCode) &&
1482 charCode != '.' && charCode != '`' && charCode != '!' &&
1483 charCode != '@' && charCode != '#' && charCode != '$' &&
1484 charCode != '%' && charCode != '^' && charCode != '&' &&
1485 charCode != '*' && charCode != '(' && charCode != ')' &&
1486 charCode != '-' && charCode != '_' && charCode != '+' &&
1487 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1488 charCode != '}' && charCode != '[' && charCode != '{' &&
1489 charCode != '/' && charCode != '?' && charCode != '>' &&
1490 charCode != '<' && charCode != ',' && charCode != '~')
1491 return 0;
1493 /* if there's one item or less, there is no where to go */
1494 if (infoPtr->nItemCount <= 1) return 0;
1496 /* update the search parameters */
1497 infoPtr->lastKeyPressTimestamp = GetTickCount();
1498 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1499 if (infoPtr->nSearchParamLength < MAX_PATH)
1500 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1501 if (infoPtr->charCode != charCode)
1502 infoPtr->charCode = charCode = 0;
1503 } else {
1504 infoPtr->charCode=charCode;
1505 infoPtr->szSearchParam[0]=charCode;
1506 infoPtr->nSearchParamLength=1;
1507 /* Redundant with the 1 char string */
1508 charCode=0;
1511 /* and search from the current position */
1512 nItem=-1;
1513 if (infoPtr->nFocusedItem >= 0) {
1514 endidx=infoPtr->nFocusedItem;
1515 idx=endidx;
1516 /* if looking for single character match,
1517 * then we must always move forward
1519 if (infoPtr->nSearchParamLength == 1)
1520 idx++;
1521 } else {
1522 endidx=infoPtr->nItemCount;
1523 idx=0;
1525 do {
1526 if (idx == infoPtr->nItemCount) {
1527 if (endidx == infoPtr->nItemCount || endidx == 0)
1528 break;
1529 idx=0;
1532 /* get item */
1533 item.mask = LVIF_TEXT;
1534 item.iItem = idx;
1535 item.iSubItem = 0;
1536 item.pszText = buffer;
1537 item.cchTextMax = MAX_PATH;
1538 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1540 /* check for a match */
1541 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1542 nItem=idx;
1543 break;
1544 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1545 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1546 /* This would work but we must keep looking for a longer match */
1547 nItem=idx;
1549 idx++;
1550 } while (idx != endidx);
1552 if (nItem != -1)
1553 LISTVIEW_KeySelection(infoPtr, nItem);
1555 return 0;
1558 /*************************************************************************
1559 * LISTVIEW_UpdateHeaderSize [Internal]
1561 * Function to resize the header control
1563 * PARAMS
1564 * [I] hwnd : handle to a window
1565 * [I] nNewScrollPos : scroll pos to set
1567 * RETURNS
1568 * None.
1570 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1572 RECT winRect;
1573 POINT point[2];
1575 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1577 GetWindowRect(infoPtr->hwndHeader, &winRect);
1578 point[0].x = winRect.left;
1579 point[0].y = winRect.top;
1580 point[1].x = winRect.right;
1581 point[1].y = winRect.bottom;
1583 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1584 point[0].x = -nNewScrollPos;
1585 point[1].x += nNewScrollPos;
1587 SetWindowPos(infoPtr->hwndHeader,0,
1588 point[0].x,point[0].y,point[1].x,point[1].y,
1589 SWP_NOZORDER | SWP_NOACTIVATE);
1592 /***
1593 * DESCRIPTION:
1594 * Update the scrollbars. This functions should be called whenever
1595 * the content, size or view changes.
1597 * PARAMETER(S):
1598 * [I] infoPtr : valid pointer to the listview structure
1600 * RETURN:
1601 * None
1603 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1605 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1606 SCROLLINFO horzInfo, vertInfo;
1608 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1610 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1611 horzInfo.cbSize = sizeof(SCROLLINFO);
1612 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1614 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1615 if (uView == LVS_LIST)
1617 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1618 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1620 /* scroll by at least one column per page */
1621 if(horzInfo.nPage < infoPtr->nItemWidth)
1622 horzInfo.nPage = infoPtr->nItemWidth;
1624 horzInfo.nPage /= infoPtr->nItemWidth;
1626 else if (uView == LVS_REPORT)
1628 horzInfo.nMax = infoPtr->nItemWidth;
1630 else /* LVS_ICON, or LVS_SMALLICON */
1632 RECT rcView;
1634 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1637 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1638 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1639 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1640 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1642 /* Setting the horizontal scroll can change the listview size
1643 * (and potentially everything else) so we need to recompute
1644 * everything again for the vertical scroll
1647 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1648 vertInfo.cbSize = sizeof(SCROLLINFO);
1649 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1651 if (uView == LVS_REPORT)
1653 vertInfo.nMax = infoPtr->nItemCount;
1655 /* scroll by at least one page */
1656 if(vertInfo.nPage < infoPtr->nItemHeight)
1657 vertInfo.nPage = infoPtr->nItemHeight;
1659 vertInfo.nPage /= infoPtr->nItemHeight;
1661 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1663 RECT rcView;
1665 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1668 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1669 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1670 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1671 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1673 /* Update the Header Control */
1674 if (uView == LVS_REPORT)
1676 horzInfo.fMask = SIF_POS;
1677 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1678 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1683 /***
1684 * DESCRIPTION:
1685 * Shows/hides the focus rectangle.
1687 * PARAMETER(S):
1688 * [I] infoPtr : valid pointer to the listview structure
1689 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1691 * RETURN:
1692 * None
1694 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1696 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1697 HDC hdc;
1699 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1701 if (infoPtr->nFocusedItem < 0) return;
1703 /* we need some gymnastics in ICON mode to handle large items */
1704 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1706 RECT rcBox;
1708 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1709 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1711 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1712 return;
1716 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1718 /* for some reason, owner draw should work only in report mode */
1719 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1721 DRAWITEMSTRUCT dis;
1722 LVITEMW item;
1724 item.iItem = infoPtr->nFocusedItem;
1725 item.iSubItem = 0;
1726 item.mask = LVIF_PARAM;
1727 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1729 ZeroMemory(&dis, sizeof(dis));
1730 dis.CtlType = ODT_LISTVIEW;
1731 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1732 dis.itemID = item.iItem;
1733 dis.itemAction = ODA_FOCUS;
1734 if (fShow) dis.itemState |= ODS_FOCUS;
1735 dis.hwndItem = infoPtr->hwndSelf;
1736 dis.hDC = hdc;
1737 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1738 dis.itemData = item.lParam;
1740 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1742 else
1744 DrawFocusRect(hdc, &infoPtr->rcFocus);
1746 done:
1747 ReleaseDC(infoPtr->hwndSelf, hdc);
1750 /***
1751 * Invalidates all visible selected items.
1753 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1755 ITERATOR i;
1757 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1758 while(iterator_next(&i))
1760 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1761 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1763 iterator_destroy(&i);
1767 /***
1768 * DESCRIPTION: [INTERNAL]
1769 * Computes an item's (left,top) corner, relative to rcView.
1770 * That is, the position has NOT been made relative to the Origin.
1771 * This is deliberate, to avoid computing the Origin over, and
1772 * over again, when this function is call in a loop. Instead,
1773 * one ca factor the computation of the Origin before the loop,
1774 * and offset the value retured by this function, on every iteration.
1776 * PARAMETER(S):
1777 * [I] infoPtr : valid pointer to the listview structure
1778 * [I] nItem : item number
1779 * [O] lpptOrig : item top, left corner
1781 * RETURN:
1782 * None.
1784 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1786 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1788 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1790 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1792 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1793 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1795 else if (uView == LVS_LIST)
1797 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1798 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1799 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1801 else /* LVS_REPORT */
1803 lpptPosition->x = 0;
1804 lpptPosition->y = nItem * infoPtr->nItemHeight;
1808 /***
1809 * DESCRIPTION: [INTERNAL]
1810 * Compute the rectangles of an item. This is to localize all
1811 * the computations in one place. If you are not interested in some
1812 * of these values, simply pass in a NULL -- the fucntion is smart
1813 * enough to compute only what's necessary. The function computes
1814 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1815 * one, the BOX rectangle. This rectangle is very cheap to compute,
1816 * and is guaranteed to contain all the other rectangles. Computing
1817 * the ICON rect is also cheap, but all the others are potentaily
1818 * expensive. This gives an easy and effective optimization when
1819 * searching (like point inclusion, or rectangle intersection):
1820 * first test against the BOX, and if TRUE, test agains the desired
1821 * rectangle.
1822 * If the function does not have all the necessary information
1823 * to computed the requested rectangles, will crash with a
1824 * failed assertion. This is done so we catch all programming
1825 * errors, given that the function is called only from our code.
1827 * We have the following 'special' meanings for a few fields:
1828 * * If LVIS_FOCUSED is set, we assume the item has the focus
1829 * This is important in ICON mode, where it might get a larger
1830 * then usual rectange
1832 * Please note that subitem support works only in REPORT mode.
1834 * PARAMETER(S):
1835 * [I] infoPtr : valid pointer to the listview structure
1836 * [I] lpLVItem : item to compute the measures for
1837 * [O] lprcBox : ptr to Box rectangle
1838 * The internal LVIR_BOX rectangle
1839 * [0] lprcState : ptr to State icon rectangle
1840 * The internal LVIR_STATE rectangle
1841 * [O] lprcIcon : ptr to Icon rectangle
1842 * Same as LVM_GETITEMRECT with LVIR_ICON
1843 * [O] lprcLabel : ptr to Label rectangle
1844 * Same as LVM_GETITEMRECT with LVIR_LABEL
1846 * RETURN:
1847 * None.
1849 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1850 LPRECT lprcBox, LPRECT lprcState,
1851 LPRECT lprcIcon, LPRECT lprcLabel)
1853 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1854 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1855 RECT Box, State, Icon, Label;
1856 COLUMN_INFO *lpColumnInfo = NULL;
1858 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1860 /* Be smart and try to figure out the minimum we have to do */
1861 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1862 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1864 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1865 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1867 if (lprcLabel) doLabel = TRUE;
1868 if (doLabel || lprcIcon) doIcon = TRUE;
1869 if (doIcon || lprcState) doState = TRUE;
1871 /************************************************************/
1872 /* compute the box rectangle (it should be cheap to do) */
1873 /************************************************************/
1874 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1875 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1877 if (lpLVItem->iSubItem)
1879 Box = lpColumnInfo->rcHeader;
1881 else
1883 Box.left = 0;
1884 Box.right = infoPtr->nItemWidth;
1886 Box.top = 0;
1887 Box.bottom = infoPtr->nItemHeight;
1889 /************************************************************/
1890 /* compute STATEICON bounding box */
1891 /************************************************************/
1892 if (doState)
1894 if (uView == LVS_ICON)
1896 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1897 if (infoPtr->himlNormal)
1898 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1899 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1901 else
1903 /* we need the ident in report mode, if we don't have it, we fail */
1904 State.left = Box.left;
1905 if (uView == LVS_REPORT)
1907 if (lpLVItem->iSubItem == 0)
1909 State.left += REPORT_MARGINX;
1910 assert(lpLVItem->mask & LVIF_INDENT);
1911 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1914 State.top = Box.top;
1916 State.right = State.left;
1917 State.bottom = State.top;
1918 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1920 State.right += infoPtr->iconStateSize.cx;
1921 State.bottom += infoPtr->iconStateSize.cy;
1923 if (lprcState) *lprcState = State;
1924 TRACE(" - state=%s\n", debugrect(&State));
1927 /************************************************************/
1928 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1929 /************************************************************/
1930 if (doIcon)
1932 if (uView == LVS_ICON)
1934 Icon.left = Box.left;
1935 if (infoPtr->himlNormal)
1936 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1937 Icon.top = Box.top + ICON_TOP_PADDING;
1938 Icon.right = Icon.left;
1939 Icon.bottom = Icon.top;
1940 if (infoPtr->himlNormal)
1942 Icon.right += infoPtr->iconSize.cx;
1943 Icon.bottom += infoPtr->iconSize.cy;
1946 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1948 Icon.left = State.right;
1949 Icon.top = Box.top;
1950 Icon.right = Icon.left;
1951 if (infoPtr->himlSmall &&
1952 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1953 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1954 Icon.right += infoPtr->iconSize.cx;
1955 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1957 if(lprcIcon) *lprcIcon = Icon;
1958 TRACE(" - icon=%s\n", debugrect(&Icon));
1961 /************************************************************/
1962 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1963 /************************************************************/
1964 if (doLabel)
1966 SIZE labelSize = { 0, 0 };
1968 /* calculate how far to the right can the label strech */
1969 Label.right = Box.right;
1970 if (uView == LVS_REPORT)
1972 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1975 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1977 labelSize.cx = infoPtr->nItemWidth;
1978 labelSize.cy = infoPtr->nItemHeight;
1979 goto calc_label;
1982 /* we need the text in non owner draw mode */
1983 assert(lpLVItem->mask & LVIF_TEXT);
1984 if (is_textT(lpLVItem->pszText, TRUE))
1986 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1987 HDC hdc = GetDC(infoPtr->hwndSelf);
1988 HFONT hOldFont = SelectObject(hdc, hFont);
1989 UINT uFormat;
1990 RECT rcText;
1992 /* compute rough rectangle where the label will go */
1993 SetRectEmpty(&rcText);
1994 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
1995 rcText.bottom = infoPtr->nItemHeight;
1996 if (uView == LVS_ICON)
1997 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1999 /* now figure out the flags */
2000 if (uView == LVS_ICON)
2001 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2002 else
2003 uFormat = LV_SL_DT_FLAGS;
2005 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2007 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2008 labelSize.cy = rcText.bottom - rcText.top;
2010 SelectObject(hdc, hOldFont);
2011 ReleaseDC(infoPtr->hwndSelf, hdc);
2014 calc_label:
2015 if (uView == LVS_ICON)
2017 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2018 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2019 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2020 Label.right = Label.left + labelSize.cx;
2021 Label.bottom = Label.top + infoPtr->nItemHeight;
2022 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2024 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2025 labelSize.cy /= infoPtr->ntmHeight;
2026 labelSize.cy = max(labelSize.cy, 1);
2027 labelSize.cy *= infoPtr->ntmHeight;
2029 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2031 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2033 Label.left = Icon.right;
2034 Label.top = Box.top;
2035 Label.right = min(Label.left + labelSize.cx, Label.right);
2036 Label.bottom = Label.top + infoPtr->nItemHeight;
2039 if (lprcLabel) *lprcLabel = Label;
2040 TRACE(" - label=%s\n", debugrect(&Label));
2043 /* Fix the Box if necessary */
2044 if (lprcBox)
2046 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2047 else *lprcBox = Box;
2049 TRACE(" - box=%s\n", debugrect(&Box));
2052 /***
2053 * DESCRIPTION: [INTERNAL]
2055 * PARAMETER(S):
2056 * [I] infoPtr : valid pointer to the listview structure
2057 * [I] nItem : item number
2058 * [O] lprcBox : ptr to Box rectangle
2060 * RETURN:
2061 * None.
2063 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2065 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2066 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2067 POINT Position, Origin;
2068 LVITEMW lvItem;
2070 LISTVIEW_GetOrigin(infoPtr, &Origin);
2071 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2073 /* Be smart and try to figure out the minimum we have to do */
2074 lvItem.mask = 0;
2075 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2076 lvItem.mask |= LVIF_TEXT;
2077 lvItem.iItem = nItem;
2078 lvItem.iSubItem = 0;
2079 lvItem.pszText = szDispText;
2080 lvItem.cchTextMax = DISP_TEXT_SIZE;
2081 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2082 if (uView == LVS_ICON)
2084 lvItem.mask |= LVIF_STATE;
2085 lvItem.stateMask = LVIS_FOCUSED;
2086 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2088 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2090 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2094 /***
2095 * DESCRIPTION:
2096 * Returns the current icon position, and advances it along the top.
2097 * The returned position is not offset by Origin.
2099 * PARAMETER(S):
2100 * [I] infoPtr : valid pointer to the listview structure
2101 * [O] lpPos : will get the current icon position
2103 * RETURN:
2104 * None
2106 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2108 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2110 *lpPos = infoPtr->currIconPos;
2112 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2113 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2115 infoPtr->currIconPos.x = 0;
2116 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2120 /***
2121 * DESCRIPTION:
2122 * Returns the current icon position, and advances it down the left edge.
2123 * The returned position is not offset by Origin.
2125 * PARAMETER(S):
2126 * [I] infoPtr : valid pointer to the listview structure
2127 * [O] lpPos : will get the current icon position
2129 * RETURN:
2130 * None
2132 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2134 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2136 *lpPos = infoPtr->currIconPos;
2138 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2139 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2141 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2142 infoPtr->currIconPos.y = 0;
2146 /***
2147 * DESCRIPTION:
2148 * Moves an icon to the specified position.
2149 * It takes care of invalidating the item, etc.
2151 * PARAMETER(S):
2152 * [I] infoPtr : valid pointer to the listview structure
2153 * [I] nItem : the item to move
2154 * [I] lpPos : the new icon position
2155 * [I] isNew : flags the item as being new
2157 * RETURN:
2158 * Success: TRUE
2159 * Failure: FALSE
2161 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2163 POINT old;
2165 if (!isNew)
2167 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2168 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2170 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2171 LISTVIEW_InvalidateItem(infoPtr, nItem);
2174 /* Allocating a POINTER for every item is too resource intensive,
2175 * so we'll keep the (x,y) in different arrays */
2176 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2177 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2179 LISTVIEW_InvalidateItem(infoPtr, nItem);
2181 return TRUE;
2184 /***
2185 * DESCRIPTION:
2186 * Arranges listview items in icon display mode.
2188 * PARAMETER(S):
2189 * [I] infoPtr : valid pointer to the listview structure
2190 * [I] nAlignCode : alignment code
2192 * RETURN:
2193 * SUCCESS : TRUE
2194 * FAILURE : FALSE
2196 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2198 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2199 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2200 POINT pos;
2201 INT i;
2203 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2205 TRACE("nAlignCode=%d\n", nAlignCode);
2207 if (nAlignCode == LVA_DEFAULT)
2209 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2210 else nAlignCode = LVA_ALIGNTOP;
2213 switch (nAlignCode)
2215 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2216 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2217 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2218 default: return FALSE;
2221 infoPtr->bAutoarrange = TRUE;
2222 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2223 for (i = 0; i < infoPtr->nItemCount; i++)
2225 next_pos(infoPtr, &pos);
2226 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2229 return TRUE;
2232 /***
2233 * DESCRIPTION:
2234 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2236 * PARAMETER(S):
2237 * [I] infoPtr : valid pointer to the listview structure
2238 * [O] lprcView : bounding rectangle
2240 * RETURN:
2241 * SUCCESS : TRUE
2242 * FAILURE : FALSE
2244 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2246 INT i, x, y;
2248 SetRectEmpty(lprcView);
2250 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2252 case LVS_ICON:
2253 case LVS_SMALLICON:
2254 for (i = 0; i < infoPtr->nItemCount; i++)
2256 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2257 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2258 lprcView->right = max(lprcView->right, x);
2259 lprcView->bottom = max(lprcView->bottom, y);
2261 if (infoPtr->nItemCount > 0)
2263 lprcView->right += infoPtr->nItemWidth;
2264 lprcView->bottom += infoPtr->nItemHeight;
2266 break;
2268 case LVS_LIST:
2269 y = LISTVIEW_GetCountPerColumn(infoPtr);
2270 x = infoPtr->nItemCount / y;
2271 if (infoPtr->nItemCount % y) x++;
2272 lprcView->right = x * infoPtr->nItemWidth;
2273 lprcView->bottom = y * infoPtr->nItemHeight;
2274 break;
2276 case LVS_REPORT:
2277 lprcView->right = infoPtr->nItemWidth;
2278 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2279 break;
2283 /***
2284 * DESCRIPTION:
2285 * Retrieves the bounding rectangle of all the items.
2287 * PARAMETER(S):
2288 * [I] infoPtr : valid pointer to the listview structure
2289 * [O] lprcView : bounding rectangle
2291 * RETURN:
2292 * SUCCESS : TRUE
2293 * FAILURE : FALSE
2295 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2297 POINT ptOrigin;
2299 TRACE("(lprcView=%p)\n", lprcView);
2301 if (!lprcView) return FALSE;
2303 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2304 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2305 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2307 TRACE("lprcView=%s\n", debugrect(lprcView));
2309 return TRUE;
2312 /***
2313 * DESCRIPTION:
2314 * Retrieves the subitem pointer associated with the subitem index.
2316 * PARAMETER(S):
2317 * [I] hdpaSubItems : DPA handle for a specific item
2318 * [I] nSubItem : index of subitem
2320 * RETURN:
2321 * SUCCESS : subitem pointer
2322 * FAILURE : NULL
2324 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2326 SUBITEM_INFO *lpSubItem;
2327 INT i;
2329 /* we should binary search here if need be */
2330 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2332 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2333 if (lpSubItem->iSubItem == nSubItem)
2334 return lpSubItem;
2337 return NULL;
2341 /***
2342 * DESCRIPTION:
2343 * Caclulates the desired item width.
2345 * PARAMETER(S):
2346 * [I] infoPtr : valid pointer to the listview structure
2348 * RETURN:
2349 * The desired item width.
2351 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2353 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2354 INT nItemWidth = 0;
2356 TRACE("uView=%d\n", uView);
2358 if (uView == LVS_ICON)
2359 nItemWidth = infoPtr->iconSpacing.cx;
2360 else if (uView == LVS_REPORT)
2362 RECT rcHeader;
2364 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2366 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2367 nItemWidth = rcHeader.right;
2370 else /* LVS_SMALLICON, or LVS_LIST */
2372 INT i;
2374 for (i = 0; i < infoPtr->nItemCount; i++)
2375 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2377 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2378 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2380 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2383 return max(nItemWidth, 1);
2386 /***
2387 * DESCRIPTION:
2388 * Caclulates the desired item height.
2390 * PARAMETER(S):
2391 * [I] infoPtr : valid pointer to the listview structure
2393 * RETURN:
2394 * The desired item height.
2396 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2398 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2399 INT nItemHeight;
2401 TRACE("uView=%d\n", uView);
2403 if (uView == LVS_ICON)
2404 nItemHeight = infoPtr->iconSpacing.cy;
2405 else
2407 nItemHeight = infoPtr->ntmHeight;
2408 if (infoPtr->himlState)
2409 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2410 if (infoPtr->himlSmall)
2411 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2412 if (infoPtr->himlState || infoPtr->himlSmall)
2413 nItemHeight += HEIGHT_PADDING;
2416 return max(nItemHeight, 1);
2419 /***
2420 * DESCRIPTION:
2421 * Updates the width, and height of an item.
2423 * PARAMETER(S):
2424 * [I] infoPtr : valid pointer to the listview structure
2426 * RETURN:
2427 * None.
2429 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2431 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2432 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2436 /***
2437 * DESCRIPTION:
2438 * Retrieves and saves important text metrics info for the current
2439 * Listview font.
2441 * PARAMETER(S):
2442 * [I] infoPtr : valid pointer to the listview structure
2445 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2447 HDC hdc = GetDC(infoPtr->hwndSelf);
2448 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2449 HFONT hOldFont = SelectObject(hdc, hFont);
2450 TEXTMETRICW tm;
2452 if (GetTextMetricsW(hdc, &tm))
2454 infoPtr->ntmHeight = tm.tmHeight;
2455 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2457 SelectObject(hdc, hOldFont);
2458 ReleaseDC(infoPtr->hwndSelf, hdc);
2460 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2463 /***
2464 * DESCRIPTION:
2465 * A compare function for ranges
2467 * PARAMETER(S)
2468 * [I] range1 : pointer to range 1;
2469 * [I] range2 : pointer to range 2;
2470 * [I] flags : flags
2472 * RETURNS:
2473 * > 0 : if range 1 > range 2
2474 * < 0 : if range 2 > range 1
2475 * = 0 : if range intersects range 2
2477 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2479 INT cmp;
2481 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2482 cmp = -1;
2483 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2484 cmp = 1;
2485 else
2486 cmp = 0;
2488 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2490 return cmp;
2493 #if DEBUG_RANGES
2494 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2495 #else
2496 #define ranges_check(ranges, desc) do { } while(0)
2497 #endif
2499 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2501 INT i;
2502 RANGE *prev, *curr;
2504 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2505 assert (ranges);
2506 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2507 ranges_dump(ranges);
2508 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2509 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2510 assert (prev->lower >= 0 && prev->lower < prev->upper);
2511 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2513 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2514 assert (prev->upper <= curr->lower);
2515 assert (curr->lower < curr->upper);
2516 prev = curr;
2518 TRACE("--- Done checking---\n");
2521 static RANGES ranges_create(int count)
2523 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2524 if (!ranges) return NULL;
2525 ranges->hdpa = DPA_Create(count);
2526 if (ranges->hdpa) return ranges;
2527 Free(ranges);
2528 return NULL;
2531 static void ranges_clear(RANGES ranges)
2533 INT i;
2535 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2536 Free(DPA_GetPtr(ranges->hdpa, i));
2537 DPA_DeleteAllPtrs(ranges->hdpa);
2541 static void ranges_destroy(RANGES ranges)
2543 if (!ranges) return;
2544 ranges_clear(ranges);
2545 DPA_Destroy(ranges->hdpa);
2546 Free(ranges);
2549 static RANGES ranges_clone(RANGES ranges)
2551 RANGES clone;
2552 INT i;
2554 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2556 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2558 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2559 if (!newrng) goto fail;
2560 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2561 DPA_SetPtr(clone->hdpa, i, newrng);
2563 return clone;
2565 fail:
2566 TRACE ("clone failed\n");
2567 ranges_destroy(clone);
2568 return NULL;
2571 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2573 INT i;
2575 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2576 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2578 return ranges;
2581 static void ranges_dump(RANGES ranges)
2583 INT i;
2585 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2586 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2589 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2591 RANGE srchrng = { nItem, nItem + 1 };
2593 TRACE("(nItem=%d)\n", nItem);
2594 ranges_check(ranges, "before contain");
2595 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2598 static INT ranges_itemcount(RANGES ranges)
2600 INT i, count = 0;
2602 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2604 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2605 count += sel->upper - sel->lower;
2608 return count;
2611 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2613 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2614 INT index;
2616 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2617 if (index == -1) return TRUE;
2619 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2621 chkrng = DPA_GetPtr(ranges->hdpa, index);
2622 if (chkrng->lower >= nItem)
2623 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2624 if (chkrng->upper > nItem)
2625 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2627 return TRUE;
2630 static BOOL ranges_add(RANGES ranges, RANGE range)
2632 RANGE srchrgn;
2633 INT index;
2635 TRACE("(%s)\n", debugrange(&range));
2636 ranges_check(ranges, "before add");
2638 /* try find overlapping regions first */
2639 srchrgn.lower = range.lower - 1;
2640 srchrgn.upper = range.upper + 1;
2641 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2643 if (index == -1)
2645 RANGE *newrgn;
2647 TRACE("Adding new range\n");
2649 /* create the brand new range to insert */
2650 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2651 if(!newrgn) goto fail;
2652 *newrgn = range;
2654 /* figure out where to insert it */
2655 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2656 TRACE("index=%d\n", index);
2657 if (index == -1) index = 0;
2659 /* and get it over with */
2660 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2662 Free(newrgn);
2663 goto fail;
2666 else
2668 RANGE *chkrgn, *mrgrgn;
2669 INT fromindex, mergeindex;
2671 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2672 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2674 chkrgn->lower = min(range.lower, chkrgn->lower);
2675 chkrgn->upper = max(range.upper, chkrgn->upper);
2677 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2679 /* merge now common anges */
2680 fromindex = 0;
2681 srchrgn.lower = chkrgn->lower - 1;
2682 srchrgn.upper = chkrgn->upper + 1;
2686 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2687 if (mergeindex == -1) break;
2688 if (mergeindex == index)
2690 fromindex = index + 1;
2691 continue;
2694 TRACE("Merge with index %i\n", mergeindex);
2696 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2697 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2698 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2699 Free(mrgrgn);
2700 DPA_DeletePtr(ranges->hdpa, mergeindex);
2701 if (mergeindex < index) index --;
2702 } while(1);
2705 ranges_check(ranges, "after add");
2706 return TRUE;
2708 fail:
2709 ranges_check(ranges, "failed add");
2710 return FALSE;
2713 static BOOL ranges_del(RANGES ranges, RANGE range)
2715 RANGE *chkrgn;
2716 INT index;
2718 TRACE("(%s)\n", debugrange(&range));
2719 ranges_check(ranges, "before del");
2721 /* we don't use DPAS_SORTED here, since we need *
2722 * to find the first overlapping range */
2723 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2724 while(index != -1)
2726 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2728 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2730 /* case 1: Same range */
2731 if ( (chkrgn->upper == range.upper) &&
2732 (chkrgn->lower == range.lower) )
2734 DPA_DeletePtr(ranges->hdpa, index);
2735 break;
2737 /* case 2: engulf */
2738 else if ( (chkrgn->upper <= range.upper) &&
2739 (chkrgn->lower >= range.lower) )
2741 DPA_DeletePtr(ranges->hdpa, index);
2743 /* case 3: overlap upper */
2744 else if ( (chkrgn->upper <= range.upper) &&
2745 (chkrgn->lower < range.lower) )
2747 chkrgn->upper = range.lower;
2749 /* case 4: overlap lower */
2750 else if ( (chkrgn->upper > range.upper) &&
2751 (chkrgn->lower >= range.lower) )
2753 chkrgn->lower = range.upper;
2754 break;
2756 /* case 5: fully internal */
2757 else
2759 RANGE tmprgn = *chkrgn, *newrgn;
2761 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2762 newrgn->lower = chkrgn->lower;
2763 newrgn->upper = range.lower;
2764 chkrgn->lower = range.upper;
2765 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2767 Free(newrgn);
2768 goto fail;
2770 chkrgn = &tmprgn;
2771 break;
2774 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2777 ranges_check(ranges, "after del");
2778 return TRUE;
2780 fail:
2781 ranges_check(ranges, "failed del");
2782 return FALSE;
2785 /***
2786 * DESCRIPTION:
2787 * Removes all selection ranges
2789 * Parameters(s):
2790 * [I] infoPtr : valid pointer to the listview structure
2791 * [I] toSkip : item range to skip removing the selection
2793 * RETURNS:
2794 * SUCCESS : TRUE
2795 * FAILURE : TRUE
2797 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2799 LVITEMW lvItem;
2800 ITERATOR i;
2801 RANGES clone;
2803 TRACE("()\n");
2805 lvItem.state = 0;
2806 lvItem.stateMask = LVIS_SELECTED;
2808 /* need to clone the DPA because callbacks can change it */
2809 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2810 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2811 while(iterator_next(&i))
2812 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2813 /* note that the iterator destructor will free the cloned range */
2814 iterator_destroy(&i);
2816 return TRUE;
2819 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2821 RANGES toSkip;
2823 if (!(toSkip = ranges_create(1))) return FALSE;
2824 if (nItem != -1) ranges_additem(toSkip, nItem);
2825 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2826 ranges_destroy(toSkip);
2827 return TRUE;
2830 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2832 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2835 /***
2836 * DESCRIPTION:
2837 * Retrieves the number of items that are marked as selected.
2839 * PARAMETER(S):
2840 * [I] infoPtr : valid pointer to the listview structure
2842 * RETURN:
2843 * Number of items selected.
2845 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2847 INT nSelectedCount = 0;
2849 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2851 INT i;
2852 for (i = 0; i < infoPtr->nItemCount; i++)
2854 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2855 nSelectedCount++;
2858 else
2859 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2861 TRACE("nSelectedCount=%d\n", nSelectedCount);
2862 return nSelectedCount;
2865 /***
2866 * DESCRIPTION:
2867 * Manages the item focus.
2869 * PARAMETER(S):
2870 * [I] infoPtr : valid pointer to the listview structure
2871 * [I] nItem : item index
2873 * RETURN:
2874 * TRUE : focused item changed
2875 * FALSE : focused item has NOT changed
2877 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2879 INT oldFocus = infoPtr->nFocusedItem;
2880 LVITEMW lvItem;
2882 if (nItem == infoPtr->nFocusedItem) return FALSE;
2884 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2885 lvItem.stateMask = LVIS_FOCUSED;
2886 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2888 return oldFocus != infoPtr->nFocusedItem;
2891 /* Helper function for LISTVIEW_ShiftIndices *only* */
2892 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2894 if (nShiftItem < nItem) return nShiftItem;
2896 if (nShiftItem > nItem) return nShiftItem + direction;
2898 if (direction > 0) return nShiftItem + direction;
2900 return min(nShiftItem, infoPtr->nItemCount - 1);
2904 * DESCRIPTION:
2905 * Updates the various indices after an item has been inserted or deleted.
2907 * PARAMETER(S):
2908 * [I] infoPtr : valid pointer to the listview structure
2909 * [I] nItem : item index
2910 * [I] direction : Direction of shift, +1 or -1.
2912 * RETURN:
2913 * None
2915 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2917 INT nNewFocus;
2918 BOOL bOldChange;
2920 /* temporarily disable change notification while shifting items */
2921 bOldChange = infoPtr->bDoChangeNotify;
2922 infoPtr->bDoChangeNotify = FALSE;
2924 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2926 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2928 assert(abs(direction) == 1);
2930 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2932 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2933 if (nNewFocus != infoPtr->nFocusedItem)
2934 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2936 /* But we are not supposed to modify nHotItem! */
2938 infoPtr->bDoChangeNotify = bOldChange;
2943 * DESCRIPTION:
2944 * Adds a block of selections.
2946 * PARAMETER(S):
2947 * [I] infoPtr : valid pointer to the listview structure
2948 * [I] nItem : item index
2950 * RETURN:
2951 * None
2953 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2955 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2956 INT nLast = max(infoPtr->nSelectionMark, nItem);
2957 INT i;
2958 LVITEMW item;
2960 if (nFirst == -1) nFirst = nItem;
2962 item.state = LVIS_SELECTED;
2963 item.stateMask = LVIS_SELECTED;
2965 /* FIXME: this is not correct LVS_OWNERDATA
2966 * setting the item states individually will generate
2967 * a LVN_ITEMCHANGED notification for each one. Instead,
2968 * we have to send a LVN_ODSTATECHANGED notification.
2969 * See MSDN documentation for LVN_ITEMCHANGED.
2971 for (i = nFirst; i <= nLast; i++)
2972 LISTVIEW_SetItemState(infoPtr,i,&item);
2976 /***
2977 * DESCRIPTION:
2978 * Sets a single group selection.
2980 * PARAMETER(S):
2981 * [I] infoPtr : valid pointer to the listview structure
2982 * [I] nItem : item index
2984 * RETURN:
2985 * None
2987 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2989 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2990 RANGES selection;
2991 LVITEMW item;
2992 ITERATOR i;
2994 if (!(selection = ranges_create(100))) return;
2996 item.state = LVIS_SELECTED;
2997 item.stateMask = LVIS_SELECTED;
2999 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3001 if (infoPtr->nSelectionMark == -1)
3003 infoPtr->nSelectionMark = nItem;
3004 ranges_additem(selection, nItem);
3006 else
3008 RANGE sel;
3010 sel.lower = min(infoPtr->nSelectionMark, nItem);
3011 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3012 ranges_add(selection, sel);
3015 else
3017 RECT rcItem, rcSel, rcSelMark;
3018 POINT ptItem;
3020 rcItem.left = LVIR_BOUNDS;
3021 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3022 rcSelMark.left = LVIR_BOUNDS;
3023 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3024 UnionRect(&rcSel, &rcItem, &rcSelMark);
3025 iterator_frameditems(&i, infoPtr, &rcSel);
3026 while(iterator_next(&i))
3028 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3029 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3031 iterator_destroy(&i);
3034 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3035 iterator_rangesitems(&i, selection);
3036 while(iterator_next(&i))
3037 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3038 /* this will also destroy the selection */
3039 iterator_destroy(&i);
3041 LISTVIEW_SetItemFocus(infoPtr, nItem);
3044 /***
3045 * DESCRIPTION:
3046 * Sets a single selection.
3048 * PARAMETER(S):
3049 * [I] infoPtr : valid pointer to the listview structure
3050 * [I] nItem : item index
3052 * RETURN:
3053 * None
3055 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3057 LVITEMW lvItem;
3059 TRACE("nItem=%d\n", nItem);
3061 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3063 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3064 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3065 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3067 infoPtr->nSelectionMark = nItem;
3070 /***
3071 * DESCRIPTION:
3072 * Set selection(s) with keyboard.
3074 * PARAMETER(S):
3075 * [I] infoPtr : valid pointer to the listview structure
3076 * [I] nItem : item index
3078 * RETURN:
3079 * SUCCESS : TRUE (needs to be repainted)
3080 * FAILURE : FALSE (nothing has changed)
3082 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3084 /* FIXME: pass in the state */
3085 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3086 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3087 BOOL bResult = FALSE;
3089 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3091 if (infoPtr->dwStyle & LVS_SINGLESEL)
3093 bResult = TRUE;
3094 LISTVIEW_SetSelection(infoPtr, nItem);
3096 else
3098 if (wShift)
3100 bResult = TRUE;
3101 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3103 else if (wCtrl)
3105 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3107 else
3109 bResult = TRUE;
3110 LISTVIEW_SetSelection(infoPtr, nItem);
3113 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3116 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3117 return bResult;
3121 /***
3122 * DESCRIPTION:
3123 * Called when the mouse is being actively tracked and has hovered for a specified
3124 * amount of time
3126 * PARAMETER(S):
3127 * [I] infoPtr : valid pointer to the listview structure
3128 * [I] fwKeys : key indicator
3129 * [I] pts : mouse position
3131 * RETURN:
3132 * 0 if the message was processed, non-zero if there was an error
3134 * INFO:
3135 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3136 * over the item for a certain period of time.
3139 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3141 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3142 /* FIXME: select the item!!! */
3143 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3145 return 0;
3148 /***
3149 * DESCRIPTION:
3150 * Called whenever WM_MOUSEMOVE is received.
3152 * PARAMETER(S):
3153 * [I] infoPtr : valid pointer to the listview structure
3154 * [I] fwKeys : key indicator
3155 * [I] pts : mouse position
3157 * RETURN:
3158 * 0 if the message is processed, non-zero if there was an error
3160 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3162 TRACKMOUSEEVENT trackinfo;
3164 /* see if we are supposed to be tracking mouse hovering */
3165 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3166 /* fill in the trackinfo struct */
3167 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3168 trackinfo.dwFlags = TME_QUERY;
3169 trackinfo.hwndTrack = infoPtr->hwndSelf;
3170 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3172 /* see if we are already tracking this hwnd */
3173 _TrackMouseEvent(&trackinfo);
3175 if(!(trackinfo.dwFlags & TME_HOVER)) {
3176 trackinfo.dwFlags = TME_HOVER;
3178 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3179 _TrackMouseEvent(&trackinfo);
3183 return 0;
3187 /***
3188 * Tests wheather the item is assignable to a list with style lStyle
3190 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3192 if ( (lpLVItem->mask & LVIF_TEXT) &&
3193 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3194 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3196 return TRUE;
3200 /***
3201 * DESCRIPTION:
3202 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3204 * PARAMETER(S):
3205 * [I] infoPtr : valid pointer to the listview structure
3206 * [I] lpLVItem : valid pointer to new item atttributes
3207 * [I] isNew : the item being set is being inserted
3208 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3209 * [O] bChanged : will be set to TRUE if the item really changed
3211 * RETURN:
3212 * SUCCESS : TRUE
3213 * FAILURE : FALSE
3215 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3217 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3218 ITEM_INFO *lpItem;
3219 NMLISTVIEW nmlv;
3220 UINT uChanged = 0;
3221 LVITEMW item;
3223 TRACE("()\n");
3225 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3227 if (lpLVItem->mask == 0) return TRUE;
3229 if (infoPtr->dwStyle & LVS_OWNERDATA)
3231 /* a virtual listview we stores only selection and focus */
3232 if (lpLVItem->mask & ~LVIF_STATE)
3233 return FALSE;
3234 lpItem = NULL;
3236 else
3238 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3239 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3240 assert (lpItem);
3243 /* we need to get the lParam and state of the item */
3244 item.iItem = lpLVItem->iItem;
3245 item.iSubItem = lpLVItem->iSubItem;
3246 item.mask = LVIF_STATE | LVIF_PARAM;
3247 item.stateMask = ~0;
3248 item.state = 0;
3249 item.lParam = 0;
3250 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3252 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3253 /* determine what fields will change */
3254 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3255 uChanged |= LVIF_STATE;
3257 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3258 uChanged |= LVIF_IMAGE;
3260 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3261 uChanged |= LVIF_PARAM;
3263 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3264 uChanged |= LVIF_INDENT;
3266 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3267 uChanged |= LVIF_TEXT;
3269 TRACE("uChanged=0x%x\n", uChanged);
3270 if (!uChanged) return TRUE;
3271 *bChanged = TRUE;
3273 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3274 nmlv.iItem = lpLVItem->iItem;
3275 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3276 nmlv.uOldState = item.state;
3277 nmlv.uChanged = uChanged;
3278 nmlv.lParam = item.lParam;
3280 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3281 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3282 /* are enabled */
3283 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3284 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3285 return FALSE;
3287 /* copy information */
3288 if (lpLVItem->mask & LVIF_TEXT)
3289 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3291 if (lpLVItem->mask & LVIF_IMAGE)
3292 lpItem->hdr.iImage = lpLVItem->iImage;
3294 if (lpLVItem->mask & LVIF_PARAM)
3295 lpItem->lParam = lpLVItem->lParam;
3297 if (lpLVItem->mask & LVIF_INDENT)
3298 lpItem->iIndent = lpLVItem->iIndent;
3300 if (uChanged & LVIF_STATE)
3302 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3304 lpItem->state &= ~lpLVItem->stateMask;
3305 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3307 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3309 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3310 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3312 else if (lpLVItem->stateMask & LVIS_SELECTED)
3313 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3315 /* if we are asked to change focus, and we manage it, do it */
3316 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3318 if (lpLVItem->state & LVIS_FOCUSED)
3320 LISTVIEW_SetItemFocus(infoPtr, -1);
3321 infoPtr->nFocusedItem = lpLVItem->iItem;
3322 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3324 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3325 infoPtr->nFocusedItem = -1;
3329 /* if we're inserting the item, we're done */
3330 if (isNew) return TRUE;
3332 /* send LVN_ITEMCHANGED notification */
3333 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3334 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3336 return TRUE;
3339 /***
3340 * DESCRIPTION:
3341 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3343 * PARAMETER(S):
3344 * [I] infoPtr : valid pointer to the listview structure
3345 * [I] lpLVItem : valid pointer to new subitem atttributes
3346 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3347 * [O] bChanged : will be set to TRUE if the item really changed
3349 * RETURN:
3350 * SUCCESS : TRUE
3351 * FAILURE : FALSE
3353 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3355 HDPA hdpaSubItems;
3356 SUBITEM_INFO *lpSubItem;
3358 /* we do not support subitems for virtual listviews */
3359 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3361 /* set subitem only if column is present */
3362 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3364 /* First do some sanity checks */
3365 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3366 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3368 /* get the subitem structure, and create it if not there */
3369 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3370 assert (hdpaSubItems);
3372 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3373 if (!lpSubItem)
3375 SUBITEM_INFO *tmpSubItem;
3376 INT i;
3378 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3379 if (!lpSubItem) return FALSE;
3380 /* we could binary search here, if need be...*/
3381 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3383 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3384 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3386 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3388 Free(lpSubItem);
3389 return FALSE;
3391 lpSubItem->iSubItem = lpLVItem->iSubItem;
3392 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3393 *bChanged = TRUE;
3396 if (lpLVItem->mask & LVIF_IMAGE)
3397 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3399 lpSubItem->hdr.iImage = lpLVItem->iImage;
3400 *bChanged = TRUE;
3403 if (lpLVItem->mask & LVIF_TEXT)
3404 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3406 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3407 *bChanged = TRUE;
3410 return TRUE;
3413 /***
3414 * DESCRIPTION:
3415 * Sets item attributes.
3417 * PARAMETER(S):
3418 * [I] infoPtr : valid pointer to the listview structure
3419 * [I] lpLVItem : new item atttributes
3420 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3422 * RETURN:
3423 * SUCCESS : TRUE
3424 * FAILURE : FALSE
3426 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3428 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3429 LPWSTR pszText = NULL;
3430 BOOL bResult, bChanged = FALSE;
3432 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3434 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3435 return FALSE;
3437 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3438 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3440 pszText = lpLVItem->pszText;
3441 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3444 /* actually set the fields */
3445 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3447 if (lpLVItem->iSubItem)
3448 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3449 else
3450 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3452 /* redraw item, if necessary */
3453 if (bChanged && !infoPtr->bIsDrawing)
3455 /* this little optimization eliminates some nasty flicker */
3456 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3457 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3458 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3459 else
3460 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3462 /* restore text */
3463 if (pszText)
3465 textfreeT(lpLVItem->pszText, isW);
3466 ((LVITEMW *)lpLVItem)->pszText = pszText;
3469 return bResult;
3472 /***
3473 * DESCRIPTION:
3474 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3476 * PARAMETER(S):
3477 * [I] infoPtr : valid pointer to the listview structure
3479 * RETURN:
3480 * item index
3482 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3484 LONG lStyle = infoPtr->dwStyle;
3485 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3486 INT nItem = 0;
3487 SCROLLINFO scrollInfo;
3489 scrollInfo.cbSize = sizeof(SCROLLINFO);
3490 scrollInfo.fMask = SIF_POS;
3492 if (uView == LVS_LIST)
3494 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3495 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3497 else if (uView == LVS_REPORT)
3499 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3500 nItem = scrollInfo.nPos;
3502 else
3504 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3505 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3508 TRACE("nItem=%d\n", nItem);
3510 return nItem;
3514 /***
3515 * DESCRIPTION:
3516 * Erases the background of the given rectangle
3518 * PARAMETER(S):
3519 * [I] infoPtr : valid pointer to the listview structure
3520 * [I] hdc : device context handle
3521 * [I] lprcBox : clipping rectangle
3523 * RETURN:
3524 * Success: TRUE
3525 * Failure: FALSE
3527 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3529 if (!infoPtr->hBkBrush) return FALSE;
3531 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3533 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3536 /***
3537 * DESCRIPTION:
3538 * Draws an item.
3540 * PARAMETER(S):
3541 * [I] infoPtr : valid pointer to the listview structure
3542 * [I] hdc : device context handle
3543 * [I] nItem : item index
3544 * [I] nSubItem : subitem index
3545 * [I] pos : item position in client coordinates
3546 * [I] cdmode : custom draw mode
3548 * RETURN:
3549 * Success: TRUE
3550 * Failure: FALSE
3552 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3554 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3555 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3556 WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3557 DWORD cdsubitemmode = CDRF_DODEFAULT;
3558 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3559 NMLVCUSTOMDRAW nmlvcd;
3560 HIMAGELIST himl;
3561 LVITEMW lvItem;
3563 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3565 /* get information needed for drawing the item */
3566 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3567 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3568 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3569 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3570 lvItem.iItem = nItem;
3571 lvItem.iSubItem = nSubItem;
3572 lvItem.state = 0;
3573 lvItem.lParam = 0;
3574 lvItem.cchTextMax = DISP_TEXT_SIZE;
3575 lvItem.pszText = szDispText;
3576 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3577 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3578 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3579 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = szCallback;
3580 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3582 /* now check if we need to update the focus rectangle */
3583 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3585 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3586 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3587 OffsetRect(&rcBox, pos.x, pos.y);
3588 OffsetRect(&rcState, pos.x, pos.y);
3589 OffsetRect(&rcIcon, pos.x, pos.y);
3590 OffsetRect(&rcLabel, pos.x, pos.y);
3591 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3592 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3594 /* fill in the custom draw structure */
3595 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3597 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3598 if (cdmode & CDRF_NOTIFYITEMDRAW)
3599 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3600 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3601 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3602 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3603 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3605 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3606 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3608 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3609 prepaint_setup(infoPtr, hdc, &nmlvcd);
3611 /* in full row select, subitems, will just use main item's colors */
3612 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3613 nmlvcd.clrTextBk = CLR_NONE;
3615 /* state icons */
3616 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3618 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3619 if (uStateImage)
3621 TRACE("uStateImage=%d\n", uStateImage);
3622 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3626 /* small icons */
3627 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3628 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3630 TRACE("iImage=%d\n", lvItem.iImage);
3631 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3632 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3635 /* Don't bother painting item being edited */
3636 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3638 /* draw the selection background, if we're drawing the main item */
3639 if (nSubItem == 0)
3641 rcSelect = rcLabel;
3642 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3643 rcSelect.right = rcBox.right;
3645 if (nmlvcd.clrTextBk != CLR_NONE)
3646 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3647 if(lprcFocus) *lprcFocus = rcSelect;
3650 /* figure out the text drawing flags */
3651 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3652 if (uView == LVS_ICON)
3653 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3654 else if (nSubItem)
3656 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3658 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3659 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3660 default: uFormat |= DT_LEFT;
3663 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3665 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3666 else rcLabel.left += LABEL_HOR_PADDING;
3668 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3669 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3671 postpaint:
3672 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3673 notify_postpaint(infoPtr, &nmlvcd);
3674 return TRUE;
3677 /***
3678 * DESCRIPTION:
3679 * Draws listview items when in owner draw mode.
3681 * PARAMETER(S):
3682 * [I] infoPtr : valid pointer to the listview structure
3683 * [I] hdc : device context handle
3685 * RETURN:
3686 * None
3688 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3690 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3691 DWORD cditemmode = CDRF_DODEFAULT;
3692 NMLVCUSTOMDRAW nmlvcd;
3693 POINT Origin, Position;
3694 DRAWITEMSTRUCT dis;
3695 LVITEMW item;
3697 TRACE("()\n");
3699 ZeroMemory(&dis, sizeof(dis));
3701 /* Get scroll info once before loop */
3702 LISTVIEW_GetOrigin(infoPtr, &Origin);
3704 /* iterate through the invalidated rows */
3705 while(iterator_next(i))
3707 item.iItem = i->nItem;
3708 item.iSubItem = 0;
3709 item.mask = LVIF_PARAM | LVIF_STATE;
3710 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3711 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3713 dis.CtlType = ODT_LISTVIEW;
3714 dis.CtlID = uID;
3715 dis.itemID = item.iItem;
3716 dis.itemAction = ODA_DRAWENTIRE;
3717 dis.itemState = 0;
3718 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3719 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3720 dis.hwndItem = infoPtr->hwndSelf;
3721 dis.hDC = hdc;
3722 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3723 dis.rcItem.left = Position.x + Origin.x;
3724 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3725 dis.rcItem.top = Position.y + Origin.y;
3726 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3727 dis.itemData = item.lParam;
3729 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3732 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3733 * structure for the rest. of the paint cycle
3735 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3736 if (cdmode & CDRF_NOTIFYITEMDRAW)
3737 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3739 if (!(cditemmode & CDRF_SKIPDEFAULT))
3741 prepaint_setup (infoPtr, hdc, &nmlvcd);
3742 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3745 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3746 notify_postpaint(infoPtr, &nmlvcd);
3750 /***
3751 * DESCRIPTION:
3752 * Draws listview items when in report display mode.
3754 * PARAMETER(S):
3755 * [I] infoPtr : valid pointer to the listview structure
3756 * [I] hdc : device context handle
3757 * [I] cdmode : custom draw mode
3759 * RETURN:
3760 * None
3762 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3764 INT rgntype;
3765 RECT rcClip, rcItem;
3766 POINT Origin, Position;
3767 RANGE colRange;
3768 ITERATOR j;
3770 TRACE("()\n");
3772 /* figure out what to draw */
3773 rgntype = GetClipBox(hdc, &rcClip);
3774 if (rgntype == NULLREGION) return;
3776 /* Get scroll info once before loop */
3777 LISTVIEW_GetOrigin(infoPtr, &Origin);
3779 /* narrow down the columns we need to paint */
3780 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3782 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3783 if (rcItem.right + Origin.x >= rcClip.left) break;
3785 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3787 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3788 if (rcItem.left + Origin.x < rcClip.right) break;
3790 iterator_rangeitems(&j, colRange);
3792 /* in full row select, we _have_ to draw the main item */
3793 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3794 j.nSpecial = 0;
3796 /* iterate through the invalidated rows */
3797 while(iterator_next(i))
3799 /* iterate through the invalidated columns */
3800 while(iterator_next(&j))
3802 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3803 Position.x += Origin.x;
3804 Position.y += Origin.y;
3806 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3808 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3809 rcItem.top = 0;
3810 rcItem.bottom = infoPtr->nItemHeight;
3811 OffsetRect(&rcItem, Position.x, Position.y);
3812 if (!RectVisible(hdc, &rcItem)) continue;
3815 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3818 iterator_destroy(&j);
3821 /***
3822 * DESCRIPTION:
3823 * Draws listview items when in list display mode.
3825 * PARAMETER(S):
3826 * [I] infoPtr : valid pointer to the listview structure
3827 * [I] hdc : device context handle
3828 * [I] cdmode : custom draw mode
3830 * RETURN:
3831 * None
3833 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3835 POINT Origin, Position;
3837 /* Get scroll info once before loop */
3838 LISTVIEW_GetOrigin(infoPtr, &Origin);
3840 while(iterator_prev(i))
3842 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3843 Position.x += Origin.x;
3844 Position.y += Origin.y;
3846 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3851 /***
3852 * DESCRIPTION:
3853 * Draws listview items.
3855 * PARAMETER(S):
3856 * [I] infoPtr : valid pointer to the listview structure
3857 * [I] hdc : device context handle
3859 * RETURN:
3860 * NoneX
3862 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3864 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3865 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3866 NMLVCUSTOMDRAW nmlvcd;
3867 HFONT hOldFont;
3868 DWORD cdmode;
3869 INT oldBkMode;
3870 RECT rcClient;
3871 ITERATOR i;
3873 LISTVIEW_DUMP(infoPtr);
3875 infoPtr->bIsDrawing = TRUE;
3877 /* save dc values we're gonna trash while drawing */
3878 hOldFont = SelectObject(hdc, infoPtr->hFont);
3879 oldBkMode = GetBkMode(hdc);
3880 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3881 oldTextColor = GetTextColor(hdc);
3883 oldClrTextBk = infoPtr->clrTextBk;
3884 oldClrText = infoPtr->clrText;
3886 infoPtr->cditemmode = CDRF_DODEFAULT;
3888 GetClientRect(infoPtr->hwndSelf, &rcClient);
3889 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3890 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3891 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3892 prepaint_setup(infoPtr, hdc, &nmlvcd);
3894 /* Use these colors to draw the items */
3895 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3896 infoPtr->clrText = nmlvcd.clrText;
3898 /* nothing to draw */
3899 if(infoPtr->nItemCount == 0) goto enddraw;
3901 /* figure out what we need to draw */
3902 iterator_visibleitems(&i, infoPtr, hdc);
3904 /* send cache hint notification */
3905 if (infoPtr->dwStyle & LVS_OWNERDATA)
3907 RANGE range = iterator_range(&i);
3908 NMLVCACHEHINT nmlv;
3910 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3911 nmlv.iFrom = range.lower;
3912 nmlv.iTo = range.upper - 1;
3913 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3916 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3917 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
3918 else
3920 if (uView == LVS_REPORT)
3921 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
3922 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3923 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
3925 /* if we have a focus rect, draw it */
3926 if (infoPtr->bFocus)
3927 DrawFocusRect(hdc, &infoPtr->rcFocus);
3929 iterator_destroy(&i);
3931 enddraw:
3932 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3933 notify_postpaint(infoPtr, &nmlvcd);
3935 infoPtr->clrTextBk = oldClrTextBk;
3936 infoPtr->clrText = oldClrText;
3938 SelectObject(hdc, hOldFont);
3939 SetBkMode(hdc, oldBkMode);
3940 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3941 SetTextColor(hdc, oldTextColor);
3942 infoPtr->bIsDrawing = FALSE;
3946 /***
3947 * DESCRIPTION:
3948 * Calculates the approximate width and height of a given number of items.
3950 * PARAMETER(S):
3951 * [I] infoPtr : valid pointer to the listview structure
3952 * [I] nItemCount : number of items
3953 * [I] wWidth : width
3954 * [I] wHeight : height
3956 * RETURN:
3957 * Returns a DWORD. The width in the low word and the height in high word.
3959 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3960 WORD wWidth, WORD wHeight)
3962 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3963 INT nItemCountPerColumn = 1;
3964 INT nColumnCount = 0;
3965 DWORD dwViewRect = 0;
3967 if (nItemCount == -1)
3968 nItemCount = infoPtr->nItemCount;
3970 if (uView == LVS_LIST)
3972 if (wHeight == 0xFFFF)
3974 /* use current height */
3975 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3978 if (wHeight < infoPtr->nItemHeight)
3979 wHeight = infoPtr->nItemHeight;
3981 if (nItemCount > 0)
3983 if (infoPtr->nItemHeight > 0)
3985 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3986 if (nItemCountPerColumn == 0)
3987 nItemCountPerColumn = 1;
3989 if (nItemCount % nItemCountPerColumn != 0)
3990 nColumnCount = nItemCount / nItemCountPerColumn;
3991 else
3992 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3996 /* Microsoft padding magic */
3997 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3998 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4000 dwViewRect = MAKELONG(wWidth, wHeight);
4002 else if (uView == LVS_REPORT)
4003 FIXME("uView == LVS_REPORT: not implemented\n");
4004 else if (uView == LVS_SMALLICON)
4005 FIXME("uView == LVS_SMALLICON: not implemented\n");
4006 else if (uView == LVS_ICON)
4007 FIXME("uView == LVS_ICON: not implemented\n");
4009 return dwViewRect;
4013 /***
4014 * DESCRIPTION:
4015 * Create a drag image list for the specified item.
4017 * PARAMETER(S):
4018 * [I] infoPtr : valid pointer to the listview structure
4019 * [I] iItem : index of item
4020 * [O] lppt : Upperr-left corner of the image
4022 * RETURN:
4023 * Returns a handle to the image list if successful, NULL otherwise.
4025 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4027 RECT rcItem;
4028 SIZE size;
4029 POINT pos;
4030 HDC hdc, hdcOrig;
4031 HBITMAP hbmp, hOldbmp;
4032 HIMAGELIST dragList = 0;
4033 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4035 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4036 return 0;
4038 rcItem.left = LVIR_BOUNDS;
4039 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4040 return 0;
4042 lppt->x = rcItem.left;
4043 lppt->y = rcItem.top;
4045 size.cx = rcItem.right - rcItem.left;
4046 size.cy = rcItem.bottom - rcItem.top;
4048 hdcOrig = GetDC(infoPtr->hwndSelf);
4049 hdc = CreateCompatibleDC(hdcOrig);
4050 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4051 hOldbmp = SelectObject(hdc, hbmp);
4053 rcItem.left = rcItem.top = 0;
4054 rcItem.right = size.cx;
4055 rcItem.bottom = size.cy;
4056 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4058 pos.x = pos.y = 0;
4059 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4061 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4062 SelectObject(hdc, hOldbmp);
4063 ImageList_Add(dragList, hbmp, 0);
4065 else
4066 SelectObject(hdc, hOldbmp);
4068 DeleteObject(hbmp);
4069 DeleteDC(hdc);
4070 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4072 TRACE("ret=%p\n", dragList);
4074 return dragList;
4078 /***
4079 * DESCRIPTION:
4080 * Removes all listview items and subitems.
4082 * PARAMETER(S):
4083 * [I] infoPtr : valid pointer to the listview structure
4085 * RETURN:
4086 * SUCCESS : TRUE
4087 * FAILURE : FALSE
4089 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4091 NMLISTVIEW nmlv;
4092 HDPA hdpaSubItems = NULL;
4093 BOOL bSuppress;
4094 ITEMHDR *hdrItem;
4095 INT i, j;
4097 TRACE("()\n");
4099 /* we do it directly, to avoid notifications */
4100 ranges_clear(infoPtr->selectionRanges);
4101 infoPtr->nSelectionMark = -1;
4102 infoPtr->nFocusedItem = -1;
4103 SetRectEmpty(&infoPtr->rcFocus);
4104 /* But we are supposed to leave nHotItem as is! */
4107 /* send LVN_DELETEALLITEMS notification */
4108 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4109 nmlv.iItem = -1;
4110 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4112 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4114 /* send LVN_DELETEITEM notification, if not supressed */
4115 if (!bSuppress) notify_deleteitem(infoPtr, i);
4116 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4118 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4119 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4121 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4122 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4123 Free(hdrItem);
4125 DPA_Destroy(hdpaSubItems);
4126 DPA_DeletePtr(infoPtr->hdpaItems, i);
4128 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4129 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4130 infoPtr->nItemCount --;
4133 LISTVIEW_UpdateScroll(infoPtr);
4135 LISTVIEW_InvalidateList(infoPtr);
4137 return TRUE;
4140 /***
4141 * DESCRIPTION:
4142 * Scrolls, and updates the columns, when a column is changing width.
4144 * PARAMETER(S):
4145 * [I] infoPtr : valid pointer to the listview structure
4146 * [I] nColumn : column to scroll
4147 * [I] dx : amount of scroll, in pixels
4149 * RETURN:
4150 * None.
4152 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4154 COLUMN_INFO *lpColumnInfo;
4155 RECT rcOld, rcCol;
4156 INT nCol;
4158 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4159 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4160 rcCol = lpColumnInfo->rcHeader;
4161 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4162 rcCol.left = rcCol.right;
4164 /* ajust the other columns */
4165 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4167 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4168 lpColumnInfo->rcHeader.left += dx;
4169 lpColumnInfo->rcHeader.right += dx;
4172 /* do not update screen if not in report mode */
4173 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4175 /* if we have a focus, must first erase the focus rect */
4176 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4178 /* Need to reset the item width when inserting a new column */
4179 infoPtr->nItemWidth += dx;
4181 LISTVIEW_UpdateScroll(infoPtr);
4183 /* scroll to cover the deleted column, and invalidate for redraw */
4184 rcOld = infoPtr->rcList;
4185 rcOld.left = rcCol.left;
4186 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4188 /* we can restore focus now */
4189 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4192 /***
4193 * DESCRIPTION:
4194 * Removes a column from the listview control.
4196 * PARAMETER(S):
4197 * [I] infoPtr : valid pointer to the listview structure
4198 * [I] nColumn : column index
4200 * RETURN:
4201 * SUCCESS : TRUE
4202 * FAILURE : FALSE
4204 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4206 RECT rcCol;
4208 TRACE("nColumn=%d\n", nColumn);
4210 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4211 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4213 /* While the MSDN specifically says that column zero should not be deleted,
4214 it does in fact work on WinNT, and at least one app depends on it. On
4215 WinNT, deleting column zero deletes the last column of items but the
4216 first header. Since no app will ever depend on that bizarre behavior,
4217 we just delete the last column including the header.
4219 if (nColumn == 0)
4220 nColumn = DPA_GetPtrCount(infoPtr->hdpaColumns) - 1;
4222 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4224 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4225 return FALSE;
4227 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4228 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4230 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4232 SUBITEM_INFO *lpSubItem, *lpDelItem;
4233 HDPA hdpaSubItems;
4234 INT nItem, nSubItem, i;
4236 if (nColumn == 0)
4237 return LISTVIEW_DeleteAllItems(infoPtr);
4239 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4241 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4242 nSubItem = 0;
4243 lpDelItem = 0;
4244 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4246 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4247 if (lpSubItem->iSubItem == nColumn)
4249 nSubItem = i;
4250 lpDelItem = lpSubItem;
4252 else if (lpSubItem->iSubItem > nColumn)
4254 lpSubItem->iSubItem--;
4258 /* if we found our subitem, zapp it */
4259 if (nSubItem > 0)
4261 /* free string */
4262 if (is_textW(lpDelItem->hdr.pszText))
4263 Free(lpDelItem->hdr.pszText);
4265 /* free item */
4266 Free(lpDelItem);
4268 /* free dpa memory */
4269 DPA_DeletePtr(hdpaSubItems, nSubItem);
4274 /* update the other column info */
4275 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4277 return TRUE;
4280 /***
4281 * DESCRIPTION:
4282 * Invalidates the listview after an item's insertion or deletion.
4284 * PARAMETER(S):
4285 * [I] infoPtr : valid pointer to the listview structure
4286 * [I] nItem : item index
4287 * [I] dir : -1 if deleting, 1 if inserting
4289 * RETURN:
4290 * None
4292 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4294 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4295 INT nPerCol, nItemCol, nItemRow;
4296 RECT rcScroll;
4297 POINT Origin;
4299 /* if we don't refresh, what's the point of scrolling? */
4300 if (!is_redrawing(infoPtr)) return;
4302 assert (abs(dir) == 1);
4304 /* arrange icons if autoarrange is on */
4305 if (is_autoarrange(infoPtr))
4307 BOOL arrange = TRUE;
4308 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4309 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4310 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4313 /* scrollbars need updating */
4314 LISTVIEW_UpdateScroll(infoPtr);
4316 /* figure out the item's position */
4317 if (uView == LVS_REPORT)
4318 nPerCol = infoPtr->nItemCount + 1;
4319 else if (uView == LVS_LIST)
4320 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4321 else /* LVS_ICON, or LVS_SMALLICON */
4322 return;
4324 nItemCol = nItem / nPerCol;
4325 nItemRow = nItem % nPerCol;
4326 LISTVIEW_GetOrigin(infoPtr, &Origin);
4328 /* move the items below up a slot */
4329 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4330 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4331 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4332 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4333 OffsetRect(&rcScroll, Origin.x, Origin.y);
4334 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4335 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4337 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4338 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4339 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4342 /* report has only that column, so we're done */
4343 if (uView == LVS_REPORT) return;
4345 /* now for LISTs, we have to deal with the columns to the right */
4346 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4347 rcScroll.top = 0;
4348 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4349 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4350 OffsetRect(&rcScroll, Origin.x, Origin.y);
4351 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4352 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4353 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4356 /***
4357 * DESCRIPTION:
4358 * Removes an item from the listview control.
4360 * PARAMETER(S):
4361 * [I] infoPtr : valid pointer to the listview structure
4362 * [I] nItem : item index
4364 * RETURN:
4365 * SUCCESS : TRUE
4366 * FAILURE : FALSE
4368 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4370 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4371 LVITEMW item;
4373 TRACE("(nItem=%d)\n", nItem);
4375 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4377 /* remove selection, and focus */
4378 item.state = 0;
4379 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4380 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4382 /* send LVN_DELETEITEM notification. */
4383 notify_deleteitem(infoPtr, nItem);
4385 /* we need to do this here, because we'll be deleting stuff */
4386 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4387 LISTVIEW_InvalidateItem(infoPtr, nItem);
4389 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4391 HDPA hdpaSubItems;
4392 ITEMHDR *hdrItem;
4393 INT i;
4395 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4396 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4398 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4399 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4400 Free(hdrItem);
4402 DPA_Destroy(hdpaSubItems);
4405 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4407 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4408 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4411 infoPtr->nItemCount--;
4412 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4414 /* now is the invalidation fun */
4415 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4416 return TRUE;
4420 /***
4421 * DESCRIPTION:
4422 * Callback implementation for editlabel control
4424 * PARAMETER(S):
4425 * [I] infoPtr : valid pointer to the listview structure
4426 * [I] pszText : modified text
4427 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4429 * RETURN:
4430 * SUCCESS : TRUE
4431 * FAILURE : FALSE
4433 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4435 NMLVDISPINFOW dispInfo;
4437 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4439 ZeroMemory(&dispInfo, sizeof(dispInfo));
4440 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4441 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4442 dispInfo.item.iSubItem = 0;
4443 dispInfo.item.stateMask = ~0;
4444 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4445 /* add the text from the edit in */
4446 dispInfo.item.mask |= LVIF_TEXT;
4447 dispInfo.item.pszText = pszText;
4448 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4450 /* Do we need to update the Item Text */
4451 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4452 if (!pszText) return TRUE;
4454 ZeroMemory(&dispInfo, sizeof(dispInfo));
4455 dispInfo.item.mask = LVIF_TEXT;
4456 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4457 dispInfo.item.iSubItem = 0;
4458 dispInfo.item.pszText = pszText;
4459 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4460 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4463 /***
4464 * DESCRIPTION:
4465 * Begin in place editing of specified list view item
4467 * PARAMETER(S):
4468 * [I] infoPtr : valid pointer to the listview structure
4469 * [I] nItem : item index
4470 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4472 * RETURN:
4473 * SUCCESS : TRUE
4474 * FAILURE : FALSE
4476 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4478 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4479 NMLVDISPINFOW dispInfo;
4480 RECT rect;
4482 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4484 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4485 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4487 infoPtr->nEditLabelItem = nItem;
4489 /* Is the EditBox still there, if so remove it */
4490 if(infoPtr->hwndEdit != 0)
4492 SetFocus(infoPtr->hwndSelf);
4493 infoPtr->hwndEdit = 0;
4496 LISTVIEW_SetSelection(infoPtr, nItem);
4497 LISTVIEW_SetItemFocus(infoPtr, nItem);
4498 LISTVIEW_InvalidateItem(infoPtr, nItem);
4500 rect.left = LVIR_LABEL;
4501 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4503 ZeroMemory(&dispInfo, sizeof(dispInfo));
4504 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4505 dispInfo.item.iItem = nItem;
4506 dispInfo.item.iSubItem = 0;
4507 dispInfo.item.stateMask = ~0;
4508 dispInfo.item.pszText = szDispText;
4509 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4510 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4512 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4513 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4514 if (!infoPtr->hwndEdit) return 0;
4516 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4518 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4519 infoPtr->hwndEdit = 0;
4520 return 0;
4523 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4524 SetFocus(infoPtr->hwndEdit);
4525 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4526 return infoPtr->hwndEdit;
4530 /***
4531 * DESCRIPTION:
4532 * Ensures the specified item is visible, scrolling into view if necessary.
4534 * PARAMETER(S):
4535 * [I] infoPtr : valid pointer to the listview structure
4536 * [I] nItem : item index
4537 * [I] bPartial : partially or entirely visible
4539 * RETURN:
4540 * SUCCESS : TRUE
4541 * FAILURE : FALSE
4543 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4545 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4546 INT nScrollPosHeight = 0;
4547 INT nScrollPosWidth = 0;
4548 INT nHorzAdjust = 0;
4549 INT nVertAdjust = 0;
4550 INT nHorzDiff = 0;
4551 INT nVertDiff = 0;
4552 RECT rcItem, rcTemp;
4554 rcItem.left = LVIR_BOUNDS;
4555 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4557 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4559 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4561 /* scroll left/right, but in LVS_REPORT mode */
4562 if (uView == LVS_LIST)
4563 nScrollPosWidth = infoPtr->nItemWidth;
4564 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4565 nScrollPosWidth = 1;
4567 if (rcItem.left < infoPtr->rcList.left)
4569 nHorzAdjust = -1;
4570 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4572 else
4574 nHorzAdjust = 1;
4575 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4579 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4581 /* scroll up/down, but not in LVS_LIST mode */
4582 if (uView == LVS_REPORT)
4583 nScrollPosHeight = infoPtr->nItemHeight;
4584 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4585 nScrollPosHeight = 1;
4587 if (rcItem.top < infoPtr->rcList.top)
4589 nVertAdjust = -1;
4590 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4592 else
4594 nVertAdjust = 1;
4595 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4599 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4601 if (nScrollPosWidth)
4603 INT diff = nHorzDiff / nScrollPosWidth;
4604 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4605 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4608 if (nScrollPosHeight)
4610 INT diff = nVertDiff / nScrollPosHeight;
4611 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4612 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4615 return TRUE;
4618 /***
4619 * DESCRIPTION:
4620 * Searches for an item with specific characteristics.
4622 * PARAMETER(S):
4623 * [I] hwnd : window handle
4624 * [I] nStart : base item index
4625 * [I] lpFindInfo : item information to look for
4627 * RETURN:
4628 * SUCCESS : index of item
4629 * FAILURE : -1
4631 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4632 const LVFINDINFOW *lpFindInfo)
4634 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4635 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4636 BOOL bWrap = FALSE, bNearest = FALSE;
4637 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4638 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4639 POINT Position, Destination;
4640 LVITEMW lvItem;
4642 if (!lpFindInfo || nItem < 0) return -1;
4644 lvItem.mask = 0;
4645 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4647 lvItem.mask |= LVIF_TEXT;
4648 lvItem.pszText = szDispText;
4649 lvItem.cchTextMax = DISP_TEXT_SIZE;
4652 if (lpFindInfo->flags & LVFI_WRAP)
4653 bWrap = TRUE;
4655 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4656 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4658 POINT Origin;
4659 RECT rcArea;
4661 LISTVIEW_GetOrigin(infoPtr, &Origin);
4662 Destination.x = lpFindInfo->pt.x - Origin.x;
4663 Destination.y = lpFindInfo->pt.y - Origin.y;
4664 switch(lpFindInfo->vkDirection)
4666 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4667 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4668 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4669 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4670 case VK_HOME: Destination.x = Destination.y = 0; break;
4671 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4672 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4673 case VK_END:
4674 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4675 Destination.x = rcArea.right;
4676 Destination.y = rcArea.bottom;
4677 break;
4678 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4680 bNearest = TRUE;
4683 /* if LVFI_PARAM is specified, all other flags are ignored */
4684 if (lpFindInfo->flags & LVFI_PARAM)
4686 lvItem.mask |= LVIF_PARAM;
4687 bNearest = FALSE;
4688 lvItem.mask &= ~LVIF_TEXT;
4691 again:
4692 for (; nItem < nLast; nItem++)
4694 lvItem.iItem = nItem;
4695 lvItem.iSubItem = 0;
4696 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4698 if (lvItem.mask & LVIF_PARAM)
4700 if (lpFindInfo->lParam == lvItem.lParam)
4701 return nItem;
4702 else
4703 continue;
4706 if (lvItem.mask & LVIF_TEXT)
4708 if (lpFindInfo->flags & LVFI_PARTIAL)
4710 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4712 else
4714 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4718 if (!bNearest) return nItem;
4720 /* This is very inefficient. To do a good job here,
4721 * we need a sorted array of (x,y) item positions */
4722 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4724 /* compute the distance^2 to the destination */
4725 xdist = Destination.x - Position.x;
4726 ydist = Destination.y - Position.y;
4727 dist = xdist * xdist + ydist * ydist;
4729 /* remember the distance, and item if it's closer */
4730 if (dist < mindist)
4732 mindist = dist;
4733 nNearestItem = nItem;
4737 if (bWrap)
4739 nItem = 0;
4740 nLast = min(nStart + 1, infoPtr->nItemCount);
4741 bWrap = FALSE;
4742 goto again;
4745 return nNearestItem;
4748 /***
4749 * DESCRIPTION:
4750 * Searches for an item with specific characteristics.
4752 * PARAMETER(S):
4753 * [I] hwnd : window handle
4754 * [I] nStart : base item index
4755 * [I] lpFindInfo : item information to look for
4757 * RETURN:
4758 * SUCCESS : index of item
4759 * FAILURE : -1
4761 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4762 const LVFINDINFOA *lpFindInfo)
4764 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4765 LVFINDINFOW fiw;
4766 INT res;
4768 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4769 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4770 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4771 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4772 return res;
4775 /***
4776 * DESCRIPTION:
4777 * Retrieves the background image of the listview control.
4779 * PARAMETER(S):
4780 * [I] infoPtr : valid pointer to the listview structure
4781 * [O] lpBkImage : background image attributes
4783 * RETURN:
4784 * SUCCESS : TRUE
4785 * FAILURE : FALSE
4787 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4788 /* { */
4789 /* FIXME (listview, "empty stub!\n"); */
4790 /* return FALSE; */
4791 /* } */
4793 /***
4794 * DESCRIPTION:
4795 * Retrieves column attributes.
4797 * PARAMETER(S):
4798 * [I] infoPtr : valid pointer to the listview structure
4799 * [I] nColumn : column index
4800 * [IO] lpColumn : column information
4801 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4802 * otherwise it is in fact a LPLVCOLUMNA
4804 * RETURN:
4805 * SUCCESS : TRUE
4806 * FAILURE : FALSE
4808 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4810 COLUMN_INFO *lpColumnInfo;
4811 HDITEMW hdi;
4813 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4814 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4816 /* initialize memory */
4817 ZeroMemory(&hdi, sizeof(hdi));
4819 if (lpColumn->mask & LVCF_TEXT)
4821 hdi.mask |= HDI_TEXT;
4822 hdi.pszText = lpColumn->pszText;
4823 hdi.cchTextMax = lpColumn->cchTextMax;
4826 if (lpColumn->mask & LVCF_IMAGE)
4827 hdi.mask |= HDI_IMAGE;
4829 if (lpColumn->mask & LVCF_ORDER)
4830 hdi.mask |= HDI_ORDER;
4832 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4834 if (lpColumn->mask & LVCF_FMT)
4835 lpColumn->fmt = lpColumnInfo->fmt;
4837 if (lpColumn->mask & LVCF_WIDTH)
4838 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4840 if (lpColumn->mask & LVCF_IMAGE)
4841 lpColumn->iImage = hdi.iImage;
4843 if (lpColumn->mask & LVCF_ORDER)
4844 lpColumn->iOrder = hdi.iOrder;
4846 return TRUE;
4850 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4852 INT i;
4854 if (!lpiArray)
4855 return FALSE;
4857 /* FIXME: little hack */
4858 for (i = 0; i < iCount; i++)
4859 lpiArray[i] = i;
4861 return TRUE;
4864 /***
4865 * DESCRIPTION:
4866 * Retrieves the column width.
4868 * PARAMETER(S):
4869 * [I] infoPtr : valid pointer to the listview structure
4870 * [I] int : column index
4872 * RETURN:
4873 * SUCCESS : column width
4874 * FAILURE : zero
4876 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4878 INT nColumnWidth = 0;
4879 RECT rcHeader;
4881 TRACE("nColumn=%d\n", nColumn);
4883 /* we have a 'column' in LIST and REPORT mode only */
4884 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4886 case LVS_LIST:
4887 nColumnWidth = infoPtr->nItemWidth;
4888 break;
4889 case LVS_REPORT:
4890 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
4891 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4892 nColumnWidth = rcHeader.right - rcHeader.left;
4893 break;
4896 TRACE("nColumnWidth=%d\n", nColumnWidth);
4897 return nColumnWidth;
4900 /***
4901 * DESCRIPTION:
4902 * In list or report display mode, retrieves the number of items that can fit
4903 * vertically in the visible area. In icon or small icon display mode,
4904 * retrieves the total number of visible items.
4906 * PARAMETER(S):
4907 * [I] infoPtr : valid pointer to the listview structure
4909 * RETURN:
4910 * Number of fully visible items.
4912 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4914 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4916 case LVS_ICON:
4917 case LVS_SMALLICON:
4918 return infoPtr->nItemCount;
4919 case LVS_REPORT:
4920 return LISTVIEW_GetCountPerColumn(infoPtr);
4921 case LVS_LIST:
4922 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4924 assert(FALSE);
4925 return 0;
4928 /***
4929 * DESCRIPTION:
4930 * Retrieves an image list handle.
4932 * PARAMETER(S):
4933 * [I] infoPtr : valid pointer to the listview structure
4934 * [I] nImageList : image list identifier
4936 * RETURN:
4937 * SUCCESS : image list handle
4938 * FAILURE : NULL
4940 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4942 switch (nImageList)
4944 case LVSIL_NORMAL: return infoPtr->himlNormal;
4945 case LVSIL_SMALL: return infoPtr->himlSmall;
4946 case LVSIL_STATE: return infoPtr->himlState;
4948 return NULL;
4951 /* LISTVIEW_GetISearchString */
4953 /***
4954 * DESCRIPTION:
4955 * Retrieves item attributes.
4957 * PARAMETER(S):
4958 * [I] hwnd : window handle
4959 * [IO] lpLVItem : item info
4960 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4961 * if FALSE, the lpLVItem is a LPLVITEMA.
4963 * NOTE:
4964 * This is the internal 'GetItem' interface -- it tries to
4965 * be smart, and avoids text copies, if possible, by modifing
4966 * lpLVItem->pszText to point to the text string. Please note
4967 * that this is not always possible (e.g. OWNERDATA), so on
4968 * entry you *must* supply valid values for pszText, and cchTextMax.
4969 * The only difference to the documented interface is that upon
4970 * return, you should use *only* the lpLVItem->pszText, rather than
4971 * the buffer pointer you provided on input. Most code already does
4972 * that, so it's not a problem.
4973 * For the two cases when the text must be copied (that is,
4974 * for LVM_GETITEM, and LVMGETITEMTEXT), use LISTVIEW_GetItemExtT.
4976 * RETURN:
4977 * SUCCESS : TRUE
4978 * FAILURE : FALSE
4980 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4982 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
4983 NMLVDISPINFOW dispInfo;
4984 ITEM_INFO *lpItem;
4985 ITEMHDR* pItemHdr;
4986 HDPA hdpaSubItems;
4987 INT isubitem;
4989 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4991 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4992 return FALSE;
4994 if (lpLVItem->mask == 0) return TRUE;
4996 /* make a local copy */
4997 isubitem = lpLVItem->iSubItem;
4999 /* a quick optimization if all we're asked is the focus state
5000 * these queries are worth optimising since they are common,
5001 * and can be answered in constant time, without the heavy accesses */
5002 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5003 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5005 lpLVItem->state = 0;
5006 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5007 lpLVItem->state |= LVIS_FOCUSED;
5008 return TRUE;
5011 ZeroMemory(&dispInfo, sizeof(dispInfo));
5013 /* if the app stores all the data, handle it separately */
5014 if (infoPtr->dwStyle & LVS_OWNERDATA)
5016 dispInfo.item.state = 0;
5018 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5019 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5021 /* NOTE: copy only fields which we _know_ are initialized, some apps
5022 * depend on the uninitialized fields being 0 */
5023 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5024 dispInfo.item.iItem = lpLVItem->iItem;
5025 dispInfo.item.iSubItem = isubitem;
5026 if (lpLVItem->mask & LVIF_TEXT)
5028 dispInfo.item.pszText = lpLVItem->pszText;
5029 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5031 if (lpLVItem->mask & LVIF_STATE)
5032 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5033 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5034 dispInfo.item.stateMask = lpLVItem->stateMask;
5035 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5037 /* full size structure expected - _WIN32IE >= 0x560 */
5038 *lpLVItem = dispInfo.item;
5040 else if (lpLVItem->mask & LVIF_INDENT)
5042 /* indent member expected - _WIN32IE >= 0x300 */
5043 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5045 else
5047 /* minimal structure expected */
5048 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5050 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5053 /* make sure lParam is zeroed out */
5054 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5056 /* we store only a little state, so if we're not asked, we're done */
5057 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5059 /* if focus is handled by us, report it */
5060 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5062 lpLVItem->state &= ~LVIS_FOCUSED;
5063 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5064 lpLVItem->state |= LVIS_FOCUSED;
5067 /* and do the same for selection, if we handle it */
5068 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5070 lpLVItem->state &= ~LVIS_SELECTED;
5071 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5072 lpLVItem->state |= LVIS_SELECTED;
5075 return TRUE;
5078 /* find the item and subitem structures before we proceed */
5079 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5080 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5081 assert (lpItem);
5083 if (isubitem)
5085 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5086 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5087 if (!lpSubItem)
5089 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5090 isubitem = 0;
5093 else
5094 pItemHdr = &lpItem->hdr;
5096 /* Do we need to query the state from the app? */
5097 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5099 dispInfo.item.mask |= LVIF_STATE;
5100 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5103 /* Do we need to enquire about the image? */
5104 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5105 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5107 dispInfo.item.mask |= LVIF_IMAGE;
5108 dispInfo.item.iImage = I_IMAGECALLBACK;
5111 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5112 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5114 dispInfo.item.mask |= LVIF_TEXT;
5115 dispInfo.item.pszText = lpLVItem->pszText;
5116 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5117 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5118 *dispInfo.item.pszText = '\0';
5121 /* If we don't have all the requested info, query the application */
5122 if (dispInfo.item.mask != 0)
5124 dispInfo.item.iItem = lpLVItem->iItem;
5125 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5126 dispInfo.item.lParam = lpItem->lParam;
5127 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5128 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5131 /* we should not store values for subitems */
5132 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5134 /* Now, handle the iImage field */
5135 if (dispInfo.item.mask & LVIF_IMAGE)
5137 lpLVItem->iImage = dispInfo.item.iImage;
5138 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5139 pItemHdr->iImage = dispInfo.item.iImage;
5141 else if (lpLVItem->mask & LVIF_IMAGE)
5143 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5144 lpLVItem->iImage = pItemHdr->iImage;
5145 else
5146 lpLVItem->iImage = 0;
5149 /* The pszText field */
5150 if (dispInfo.item.mask & LVIF_TEXT)
5152 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5153 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5155 lpLVItem->pszText = dispInfo.item.pszText;
5157 else if (lpLVItem->mask & LVIF_TEXT)
5159 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5160 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5163 /* if this is a subitem, we're done */
5164 if (isubitem) return TRUE;
5166 /* Next is the lParam field */
5167 if (dispInfo.item.mask & LVIF_PARAM)
5169 lpLVItem->lParam = dispInfo.item.lParam;
5170 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5171 lpItem->lParam = dispInfo.item.lParam;
5173 else if (lpLVItem->mask & LVIF_PARAM)
5174 lpLVItem->lParam = lpItem->lParam;
5176 /* ... the state field (this one is different due to uCallbackmask) */
5177 if (lpLVItem->mask & LVIF_STATE)
5179 lpLVItem->state = lpItem->state;
5180 if (dispInfo.item.mask & LVIF_STATE)
5182 lpLVItem->state &= ~dispInfo.item.stateMask;
5183 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5185 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5187 lpLVItem->state &= ~LVIS_FOCUSED;
5188 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5189 lpLVItem->state |= LVIS_FOCUSED;
5191 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5193 lpLVItem->state &= ~LVIS_SELECTED;
5194 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5195 lpLVItem->state |= LVIS_SELECTED;
5199 /* and last, but not least, the indent field */
5200 if (lpLVItem->mask & LVIF_INDENT)
5201 lpLVItem->iIndent = lpItem->iIndent;
5203 return TRUE;
5206 /***
5207 * DESCRIPTION:
5208 * Retrieves item attributes.
5210 * PARAMETER(S):
5211 * [I] hwnd : window handle
5212 * [IO] lpLVItem : item info
5213 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5214 * if FALSE, the lpLVItem is a LPLVITEMA.
5216 * NOTE:
5217 * This is the external 'GetItem' interface -- it properly copies
5218 * the text in the provided buffer.
5220 * RETURN:
5221 * SUCCESS : TRUE
5222 * FAILURE : FALSE
5224 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5226 LPWSTR pszText;
5227 BOOL bResult;
5229 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5230 return FALSE;
5232 pszText = lpLVItem->pszText;
5233 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5234 if (bResult && lpLVItem->pszText != pszText)
5235 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5236 lpLVItem->pszText = pszText;
5238 return bResult;
5242 /***
5243 * DESCRIPTION:
5244 * Retrieves the position (upper-left) of the listview control item.
5245 * Note that for LVS_ICON style, the upper-left is that of the icon
5246 * and not the bounding box.
5248 * PARAMETER(S):
5249 * [I] infoPtr : valid pointer to the listview structure
5250 * [I] nItem : item index
5251 * [O] lpptPosition : coordinate information
5253 * RETURN:
5254 * SUCCESS : TRUE
5255 * FAILURE : FALSE
5257 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5259 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5260 POINT Origin;
5262 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5264 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5266 LISTVIEW_GetOrigin(infoPtr, &Origin);
5267 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5269 if (uView == LVS_ICON)
5271 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5272 lpptPosition->y += ICON_TOP_PADDING;
5274 lpptPosition->x += Origin.x;
5275 lpptPosition->y += Origin.y;
5277 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5278 return TRUE;
5282 /***
5283 * DESCRIPTION:
5284 * Retrieves the bounding rectangle for a listview control item.
5286 * PARAMETER(S):
5287 * [I] infoPtr : valid pointer to the listview structure
5288 * [I] nItem : item index
5289 * [IO] lprc : bounding rectangle coordinates
5290 * lprc->left specifies the portion of the item for which the bounding
5291 * rectangle will be retrieved.
5293 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5294 * including the icon and label.
5296 * * For LVS_ICON
5297 * * Experiment shows that native control returns:
5298 * * width = min (48, length of text line)
5299 * * .left = position.x - (width - iconsize.cx)/2
5300 * * .right = .left + width
5301 * * height = #lines of text * ntmHeight + icon height + 8
5302 * * .top = position.y - 2
5303 * * .bottom = .top + height
5304 * * separation between items .y = itemSpacing.cy - height
5305 * * .x = itemSpacing.cx - width
5306 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5308 * * For LVS_ICON
5309 * * Experiment shows that native control returns:
5310 * * width = iconSize.cx + 16
5311 * * .left = position.x - (width - iconsize.cx)/2
5312 * * .right = .left + width
5313 * * height = iconSize.cy + 4
5314 * * .top = position.y - 2
5315 * * .bottom = .top + height
5316 * * separation between items .y = itemSpacing.cy - height
5317 * * .x = itemSpacing.cx - width
5318 * LVIR_LABEL Returns the bounding rectangle of the item text.
5320 * * For LVS_ICON
5321 * * Experiment shows that native control returns:
5322 * * width = text length
5323 * * .left = position.x - width/2
5324 * * .right = .left + width
5325 * * height = ntmH * linecount + 2
5326 * * .top = position.y + iconSize.cy + 6
5327 * * .bottom = .top + height
5328 * * separation between items .y = itemSpacing.cy - height
5329 * * .x = itemSpacing.cx - width
5330 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5331 * rectangles, but excludes columns in report view.
5333 * RETURN:
5334 * SUCCESS : TRUE
5335 * FAILURE : FALSE
5337 * NOTES
5338 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5339 * upon whether the window has the focus currently and on whether the item
5340 * is the one with the focus. Ensure that the control's record of which
5341 * item has the focus agrees with the items' records.
5343 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5345 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5346 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5347 BOOL doLabel = TRUE, oversizedBox = FALSE;
5348 POINT Position, Origin;
5349 LVITEMW lvItem;
5350 RECT label_rect;
5352 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5354 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5356 LISTVIEW_GetOrigin(infoPtr, &Origin);
5357 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5359 /* Be smart and try to figure out the minimum we have to do */
5360 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5361 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5362 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5363 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5364 oversizedBox = TRUE;
5366 /* get what we need from the item before hand, so we make
5367 * only one request. This can speed up things, if data
5368 * is stored on the app side */
5369 lvItem.mask = 0;
5370 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5371 if (doLabel) lvItem.mask |= LVIF_TEXT;
5372 lvItem.iItem = nItem;
5373 lvItem.iSubItem = 0;
5374 lvItem.pszText = szDispText;
5375 lvItem.cchTextMax = DISP_TEXT_SIZE;
5376 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5377 /* we got the state already up, simulate it here, to avoid a reget */
5378 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5380 lvItem.mask |= LVIF_STATE;
5381 lvItem.stateMask = LVIS_FOCUSED;
5382 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5385 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5386 lprc->left = LVIR_BOUNDS;
5387 switch(lprc->left)
5389 case LVIR_ICON:
5390 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5391 break;
5393 case LVIR_LABEL:
5394 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5395 break;
5397 case LVIR_BOUNDS:
5398 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5399 break;
5401 case LVIR_SELECTBOUNDS:
5402 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5403 UnionRect(lprc, lprc, &label_rect);
5404 break;
5406 default:
5407 WARN("Unknown value: %ld\n", lprc->left);
5408 return FALSE;
5411 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5413 TRACE(" rect=%s\n", debugrect(lprc));
5415 return TRUE;
5418 /***
5419 * DESCRIPTION:
5420 * Retrieves the spacing between listview control items.
5422 * PARAMETER(S):
5423 * [I] infoPtr : valid pointer to the listview structure
5424 * [IO] lprc : rectangle to receive the output
5425 * on input, lprc->top = nSubItem
5426 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5428 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5429 * not only those of the first column.
5430 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5432 * RETURN:
5433 * TRUE: success
5434 * FALSE: failure
5436 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5438 POINT Position;
5439 LVITEMW lvItem;
5441 if (!lprc) return FALSE;
5443 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5444 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5445 if (lprc->top == 0)
5446 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5448 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5450 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5452 lvItem.mask = 0;
5453 lvItem.iItem = nItem;
5454 lvItem.iSubItem = lprc->top;
5456 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5457 switch(lprc->left)
5459 case LVIR_ICON:
5460 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5461 break;
5463 case LVIR_LABEL:
5464 case LVIR_BOUNDS:
5465 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5466 break;
5468 default:
5469 ERR("Unknown bounds=%ld\n", lprc->left);
5470 return FALSE;
5473 OffsetRect(lprc, Position.x, Position.y);
5474 return TRUE;
5478 /***
5479 * DESCRIPTION:
5480 * Retrieves the width of a label.
5482 * PARAMETER(S):
5483 * [I] infoPtr : valid pointer to the listview structure
5485 * RETURN:
5486 * SUCCESS : string width (in pixels)
5487 * FAILURE : zero
5489 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5491 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5492 LVITEMW lvItem;
5494 TRACE("(nItem=%d)\n", nItem);
5496 lvItem.mask = LVIF_TEXT;
5497 lvItem.iItem = nItem;
5498 lvItem.iSubItem = 0;
5499 lvItem.pszText = szDispText;
5500 lvItem.cchTextMax = DISP_TEXT_SIZE;
5501 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5503 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5506 /***
5507 * DESCRIPTION:
5508 * Retrieves the spacing between listview control items.
5510 * PARAMETER(S):
5511 * [I] infoPtr : valid pointer to the listview structure
5512 * [I] bSmall : flag for small or large icon
5514 * RETURN:
5515 * Horizontal + vertical spacing
5517 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5519 LONG lResult;
5521 if (!bSmall)
5523 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5525 else
5527 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5528 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5529 else
5530 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5532 return lResult;
5535 /***
5536 * DESCRIPTION:
5537 * Retrieves the state of a listview control item.
5539 * PARAMETER(S):
5540 * [I] infoPtr : valid pointer to the listview structure
5541 * [I] nItem : item index
5542 * [I] uMask : state mask
5544 * RETURN:
5545 * State specified by the mask.
5547 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5549 LVITEMW lvItem;
5551 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5553 lvItem.iItem = nItem;
5554 lvItem.iSubItem = 0;
5555 lvItem.mask = LVIF_STATE;
5556 lvItem.stateMask = uMask;
5557 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5559 return lvItem.state & uMask;
5562 /***
5563 * DESCRIPTION:
5564 * Retrieves the text of a listview control item or subitem.
5566 * PARAMETER(S):
5567 * [I] hwnd : window handle
5568 * [I] nItem : item index
5569 * [IO] lpLVItem : item information
5570 * [I] isW : TRUE if lpLVItem is Unicode
5572 * RETURN:
5573 * SUCCESS : string length
5574 * FAILURE : 0
5576 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5578 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5580 lpLVItem->mask = LVIF_TEXT;
5581 lpLVItem->iItem = nItem;
5582 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5584 return textlenT(lpLVItem->pszText, isW);
5587 /***
5588 * DESCRIPTION:
5589 * Searches for an item based on properties + relationships.
5591 * PARAMETER(S):
5592 * [I] infoPtr : valid pointer to the listview structure
5593 * [I] nItem : item index
5594 * [I] uFlags : relationship flag
5596 * RETURN:
5597 * SUCCESS : item index
5598 * FAILURE : -1
5600 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5602 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5603 UINT uMask = 0;
5604 LVFINDINFOW lvFindInfo;
5605 INT nCountPerColumn;
5606 INT nCountPerRow;
5607 INT i;
5609 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5610 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5612 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5614 if (uFlags & LVNI_CUT)
5615 uMask |= LVIS_CUT;
5617 if (uFlags & LVNI_DROPHILITED)
5618 uMask |= LVIS_DROPHILITED;
5620 if (uFlags & LVNI_FOCUSED)
5621 uMask |= LVIS_FOCUSED;
5623 if (uFlags & LVNI_SELECTED)
5624 uMask |= LVIS_SELECTED;
5626 /* if we're asked for the focused item, that's only one,
5627 * so it's worth optimizing */
5628 if (uFlags & LVNI_FOCUSED)
5630 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5631 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5634 if (uFlags & LVNI_ABOVE)
5636 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5638 while (nItem >= 0)
5640 nItem--;
5641 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5642 return nItem;
5645 else
5647 /* Special case for autoarrange - move 'til the top of a list */
5648 if (is_autoarrange(infoPtr))
5650 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5651 while (nItem - nCountPerRow >= 0)
5653 nItem -= nCountPerRow;
5654 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5655 return nItem;
5657 return -1;
5659 lvFindInfo.flags = LVFI_NEARESTXY;
5660 lvFindInfo.vkDirection = VK_UP;
5661 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5662 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5664 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5665 return nItem;
5669 else if (uFlags & LVNI_BELOW)
5671 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5673 while (nItem < infoPtr->nItemCount)
5675 nItem++;
5676 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5677 return nItem;
5680 else
5682 /* Special case for autoarrange - move 'til the bottom of a list */
5683 if (is_autoarrange(infoPtr))
5685 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5686 while (nItem + nCountPerRow < infoPtr->nItemCount )
5688 nItem += nCountPerRow;
5689 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5690 return nItem;
5692 return -1;
5694 lvFindInfo.flags = LVFI_NEARESTXY;
5695 lvFindInfo.vkDirection = VK_DOWN;
5696 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5697 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5699 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5700 return nItem;
5704 else if (uFlags & LVNI_TOLEFT)
5706 if (uView == LVS_LIST)
5708 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5709 while (nItem - nCountPerColumn >= 0)
5711 nItem -= nCountPerColumn;
5712 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5713 return nItem;
5716 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5718 /* Special case for autoarrange - move 'ti the beginning of a row */
5719 if (is_autoarrange(infoPtr))
5721 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5722 while (nItem % nCountPerRow > 0)
5724 nItem --;
5725 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5726 return nItem;
5728 return -1;
5730 lvFindInfo.flags = LVFI_NEARESTXY;
5731 lvFindInfo.vkDirection = VK_LEFT;
5732 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5733 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5735 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5736 return nItem;
5740 else if (uFlags & LVNI_TORIGHT)
5742 if (uView == LVS_LIST)
5744 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5745 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5747 nItem += nCountPerColumn;
5748 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5749 return nItem;
5752 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5754 /* Special case for autoarrange - move 'til the end of a row */
5755 if (is_autoarrange(infoPtr))
5757 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5758 while (nItem % nCountPerRow < nCountPerRow - 1 )
5760 nItem ++;
5761 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5762 return nItem;
5764 return -1;
5766 lvFindInfo.flags = LVFI_NEARESTXY;
5767 lvFindInfo.vkDirection = VK_RIGHT;
5768 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5769 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5771 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5772 return nItem;
5776 else
5778 nItem++;
5780 /* search by index */
5781 for (i = nItem; i < infoPtr->nItemCount; i++)
5783 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5784 return i;
5788 return -1;
5791 /* LISTVIEW_GetNumberOfWorkAreas */
5793 /***
5794 * DESCRIPTION:
5795 * Retrieves the origin coordinates when in icon or small icon display mode.
5797 * PARAMETER(S):
5798 * [I] infoPtr : valid pointer to the listview structure
5799 * [O] lpptOrigin : coordinate information
5801 * RETURN:
5802 * None.
5804 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5806 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5807 INT nHorzPos = 0, nVertPos = 0;
5808 SCROLLINFO scrollInfo;
5810 scrollInfo.cbSize = sizeof(SCROLLINFO);
5811 scrollInfo.fMask = SIF_POS;
5813 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5814 nHorzPos = scrollInfo.nPos;
5815 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5816 nVertPos = scrollInfo.nPos;
5818 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5820 lpptOrigin->x = infoPtr->rcList.left;
5821 lpptOrigin->y = infoPtr->rcList.top;
5822 if (uView == LVS_LIST)
5823 nHorzPos *= infoPtr->nItemWidth;
5824 else if (uView == LVS_REPORT)
5825 nVertPos *= infoPtr->nItemHeight;
5827 lpptOrigin->x -= nHorzPos;
5828 lpptOrigin->y -= nVertPos;
5830 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5833 /***
5834 * DESCRIPTION:
5835 * Retrieves the width of a string.
5837 * PARAMETER(S):
5838 * [I] hwnd : window handle
5839 * [I] lpszText : text string to process
5840 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5842 * RETURN:
5843 * SUCCESS : string width (in pixels)
5844 * FAILURE : zero
5846 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5848 SIZE stringSize;
5850 stringSize.cx = 0;
5851 if (is_textT(lpszText, isW))
5853 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5854 HDC hdc = GetDC(infoPtr->hwndSelf);
5855 HFONT hOldFont = SelectObject(hdc, hFont);
5857 if (isW)
5858 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5859 else
5860 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5861 SelectObject(hdc, hOldFont);
5862 ReleaseDC(infoPtr->hwndSelf, hdc);
5864 return stringSize.cx;
5867 /***
5868 * DESCRIPTION:
5869 * Determines which listview item is located at the specified position.
5871 * PARAMETER(S):
5872 * [I] infoPtr : valid pointer to the listview structure
5873 * [IO] lpht : hit test information
5874 * [I] subitem : fill out iSubItem.
5875 * [I] select : return the index only if the hit selects the item
5877 * NOTE:
5878 * (mm 20001022): We must not allow iSubItem to be touched, for
5879 * an app might pass only a structure with space up to iItem!
5880 * (MS Office 97 does that for instance in the file open dialog)
5882 * RETURN:
5883 * SUCCESS : item index
5884 * FAILURE : -1
5886 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5888 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5889 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5890 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5891 POINT Origin, Position, opt;
5892 LVITEMW lvItem;
5893 ITERATOR i;
5894 INT iItem;
5896 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5898 lpht->flags = 0;
5899 lpht->iItem = -1;
5900 if (subitem) lpht->iSubItem = 0;
5902 if (infoPtr->rcList.left > lpht->pt.x)
5903 lpht->flags |= LVHT_TOLEFT;
5904 else if (infoPtr->rcList.right < lpht->pt.x)
5905 lpht->flags |= LVHT_TORIGHT;
5907 if (infoPtr->rcList.top > lpht->pt.y)
5908 lpht->flags |= LVHT_ABOVE;
5909 else if (infoPtr->rcList.bottom < lpht->pt.y)
5910 lpht->flags |= LVHT_BELOW;
5912 TRACE("lpht->flags=0x%x\n", lpht->flags);
5913 if (lpht->flags) return -1;
5915 lpht->flags |= LVHT_NOWHERE;
5917 LISTVIEW_GetOrigin(infoPtr, &Origin);
5919 /* first deal with the large items */
5920 rcSearch.left = lpht->pt.x;
5921 rcSearch.top = lpht->pt.y;
5922 rcSearch.right = rcSearch.left + 1;
5923 rcSearch.bottom = rcSearch.top + 1;
5925 iterator_frameditems(&i, infoPtr, &rcSearch);
5926 iterator_next(&i); /* go to first item in the sequence */
5927 iItem = i.nItem;
5928 iterator_destroy(&i);
5930 TRACE("lpht->iItem=%d\n", iItem);
5931 if (iItem == -1) return -1;
5933 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5934 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5935 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5936 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5937 lvItem.iItem = iItem;
5938 lvItem.iSubItem = 0;
5939 lvItem.pszText = szDispText;
5940 lvItem.cchTextMax = DISP_TEXT_SIZE;
5941 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5942 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5944 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5945 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
5946 opt.x = lpht->pt.x - Position.x - Origin.x;
5947 opt.y = lpht->pt.y - Position.y - Origin.y;
5949 if (uView == LVS_REPORT)
5950 rcBounds = rcBox;
5951 else
5952 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5953 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5954 if (!PtInRect(&rcBounds, opt)) return -1;
5956 if (PtInRect(&rcIcon, opt))
5957 lpht->flags |= LVHT_ONITEMICON;
5958 else if (PtInRect(&rcLabel, opt))
5959 lpht->flags |= LVHT_ONITEMLABEL;
5960 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5961 lpht->flags |= LVHT_ONITEMSTATEICON;
5962 if (lpht->flags & LVHT_ONITEM)
5963 lpht->flags &= ~LVHT_NOWHERE;
5965 TRACE("lpht->flags=0x%x\n", lpht->flags);
5966 if (uView == LVS_REPORT && subitem)
5968 INT j;
5970 rcBounds.right = rcBounds.left;
5971 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
5973 rcBounds.left = rcBounds.right;
5974 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5975 if (PtInRect(&rcBounds, opt))
5977 lpht->iSubItem = j;
5978 break;
5983 if (select && !(uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)))
5985 if (uView == LVS_REPORT)
5987 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5988 UnionRect(&rcBounds, &rcBounds, &rcState);
5990 if (!PtInRect(&rcBounds, opt)) iItem = -1;
5992 return lpht->iItem = iItem;
5996 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
5997 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
5998 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
5999 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6000 their own sort proc. when sending LVM_SORTITEMS.
6002 /* Platform SDK:
6003 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6005 LVS_SORTXXX must be specified,
6006 LVS_OWNERDRAW is not set,
6007 <item>.pszText is not LPSTR_TEXTCALLBACK.
6009 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6010 are sorted based on item text..."
6012 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6014 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6015 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6016 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6018 /* if we're sorting descending, negate the return value */
6019 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6022 /***
6023 * nESCRIPTION:
6024 * Inserts a new item in the listview control.
6026 * PARAMETER(S):
6027 * [I] infoPtr : valid pointer to the listview structure
6028 * [I] lpLVItem : item information
6029 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6031 * RETURN:
6032 * SUCCESS : new item index
6033 * FAILURE : -1
6035 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6037 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6038 INT nItem;
6039 HDPA hdpaSubItems;
6040 NMLISTVIEW nmlv;
6041 ITEM_INFO *lpItem;
6042 BOOL is_sorted, has_changed;
6043 LVITEMW item;
6045 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6047 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6049 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6050 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6052 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6054 if ( !(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO))) )
6055 return -1;
6057 /* insert item in listview control data structure */
6058 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6059 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6061 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6062 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6064 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6065 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6066 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6067 if (nItem == -1) goto fail;
6068 infoPtr->nItemCount++;
6070 /* shift indices first so they don't get tangled */
6071 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6073 /* set the item attributes */
6074 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6076 /* full size structure expected - _WIN32IE >= 0x560 */
6077 item = *lpLVItem;
6079 else if (lpLVItem->mask & LVIF_INDENT)
6081 /* indent member expected - _WIN32IE >= 0x300 */
6082 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6084 else
6086 /* minimal structure expected */
6087 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6089 item.iItem = nItem;
6090 item.state &= ~LVIS_STATEIMAGEMASK;
6091 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6093 /* if we're sorted, sort the list, and update the index */
6094 if (is_sorted)
6096 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6097 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6098 assert(nItem != -1);
6101 /* make room for the position, if we are in the right mode */
6102 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6104 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6105 goto undo;
6106 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6108 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6109 goto undo;
6113 /* send LVN_INSERTITEM notification */
6114 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6115 nmlv.iItem = nItem;
6116 nmlv.lParam = lpItem->lParam;
6117 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6119 /* align items (set position of each item) */
6120 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6122 POINT pt;
6124 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6125 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6126 else
6127 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6129 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6132 /* now is the invalidation fun */
6133 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6134 return nItem;
6136 undo:
6137 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6138 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6139 infoPtr->nItemCount--;
6140 fail:
6141 DPA_DeletePtr(hdpaSubItems, 0);
6142 DPA_Destroy (hdpaSubItems);
6143 Free (lpItem);
6144 return -1;
6147 /***
6148 * DESCRIPTION:
6149 * Redraws a range of items.
6151 * PARAMETER(S):
6152 * [I] infoPtr : valid pointer to the listview structure
6153 * [I] nFirst : first item
6154 * [I] nLast : last item
6156 * RETURN:
6157 * SUCCESS : TRUE
6158 * FAILURE : FALSE
6160 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6162 INT i;
6164 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6165 max(nFirst, nLast) >= infoPtr->nItemCount)
6166 return FALSE;
6168 for (i = nFirst; i <= nLast; i++)
6169 LISTVIEW_InvalidateItem(infoPtr, i);
6171 return TRUE;
6174 /***
6175 * DESCRIPTION:
6176 * Scroll the content of a listview.
6178 * PARAMETER(S):
6179 * [I] infoPtr : valid pointer to the listview structure
6180 * [I] dx : horizontal scroll amount in pixels
6181 * [I] dy : vertical scroll amount in pixels
6183 * RETURN:
6184 * SUCCESS : TRUE
6185 * FAILURE : FALSE
6187 * COMMENTS:
6188 * If the control is in report mode (LVS_REPORT) the control can
6189 * be scrolled only in line increments. "dy" will be rounded to the
6190 * nearest number of pixels that are a whole line. Ex: if line height
6191 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6192 * is passed the the scroll will be 0. (per MSDN 7/2002)
6194 * For: (per experimentaion with native control and CSpy ListView)
6195 * LVS_ICON dy=1 = 1 pixel (vertical only)
6196 * dx ignored
6197 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6198 * dx ignored
6199 * LVS_LIST dx=1 = 1 column (horizontal only)
6200 * but will only scroll 1 column per message
6201 * no matter what the value.
6202 * dy must be 0 or FALSE returned.
6203 * LVS_REPORT dx=1 = 1 pixel
6204 * dy= see above
6207 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6209 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6210 case LVS_REPORT:
6211 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6212 dy /= infoPtr->nItemHeight;
6213 break;
6214 case LVS_LIST:
6215 if (dy != 0) return FALSE;
6216 break;
6217 default: /* icon */
6218 dx = 0;
6219 break;
6222 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6223 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6225 return TRUE;
6228 /***
6229 * DESCRIPTION:
6230 * Sets the background color.
6232 * PARAMETER(S):
6233 * [I] infoPtr : valid pointer to the listview structure
6234 * [I] clrBk : background color
6236 * RETURN:
6237 * SUCCESS : TRUE
6238 * FAILURE : FALSE
6240 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6242 TRACE("(clrBk=%lx)\n", clrBk);
6244 if(infoPtr->clrBk != clrBk) {
6245 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6246 infoPtr->clrBk = clrBk;
6247 if (clrBk == CLR_NONE)
6248 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
6249 else
6250 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6251 LISTVIEW_InvalidateList(infoPtr);
6254 return TRUE;
6257 /* LISTVIEW_SetBkImage */
6259 /*** Helper for {Insert,Set}ColumnT *only* */
6260 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6262 if (lpColumn->mask & LVCF_FMT)
6264 /* format member is valid */
6265 lphdi->mask |= HDI_FORMAT;
6267 /* set text alignment (leftmost column must be left-aligned) */
6268 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
6269 lphdi->fmt |= HDF_LEFT;
6270 else if (lpColumn->fmt & LVCFMT_RIGHT)
6271 lphdi->fmt |= HDF_RIGHT;
6272 else if (lpColumn->fmt & LVCFMT_CENTER)
6273 lphdi->fmt |= HDF_CENTER;
6275 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6276 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6278 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6280 lphdi->fmt |= HDF_IMAGE;
6281 lphdi->iImage = I_IMAGECALLBACK;
6285 if (lpColumn->mask & LVCF_WIDTH)
6287 lphdi->mask |= HDI_WIDTH;
6288 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6290 /* make it fill the remainder of the controls width */
6291 RECT rcHeader;
6292 INT item_index;
6294 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6296 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6297 lphdi->cxy += rcHeader.right - rcHeader.left;
6300 /* retrieve the layout of the header */
6301 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6302 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6304 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6306 else
6307 lphdi->cxy = lpColumn->cx;
6310 if (lpColumn->mask & LVCF_TEXT)
6312 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6313 lphdi->fmt |= HDF_STRING;
6314 lphdi->pszText = lpColumn->pszText;
6315 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6318 if (lpColumn->mask & LVCF_IMAGE)
6320 lphdi->mask |= HDI_IMAGE;
6321 lphdi->iImage = lpColumn->iImage;
6324 if (lpColumn->mask & LVCF_ORDER)
6326 lphdi->mask |= HDI_ORDER;
6327 lphdi->iOrder = lpColumn->iOrder;
6332 /***
6333 * DESCRIPTION:
6334 * Inserts a new column.
6336 * PARAMETER(S):
6337 * [I] infoPtr : valid pointer to the listview structure
6338 * [I] nColumn : column index
6339 * [I] lpColumn : column information
6340 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6342 * RETURN:
6343 * SUCCESS : new column index
6344 * FAILURE : -1
6346 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6347 const LVCOLUMNW *lpColumn, BOOL isW)
6349 COLUMN_INFO *lpColumnInfo;
6350 INT nNewColumn;
6351 HDITEMW hdi;
6353 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6355 if (!lpColumn || nColumn < 0) return -1;
6356 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6358 ZeroMemory(&hdi, sizeof(HDITEMW));
6359 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6361 /* insert item in header control */
6362 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6363 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6364 (WPARAM)nColumn, (LPARAM)&hdi);
6365 if (nNewColumn == -1) return -1;
6366 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6368 /* create our own column info */
6369 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6370 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6372 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6373 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6375 /* now we have to actually adjust the data */
6376 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6378 SUBITEM_INFO *lpSubItem;
6379 HDPA hdpaSubItems;
6380 INT nItem, i;
6382 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6384 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6385 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6387 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6388 if (lpSubItem->iSubItem >= nNewColumn)
6389 lpSubItem->iSubItem++;
6394 /* make space for the new column */
6395 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6397 return nNewColumn;
6399 fail:
6400 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6401 if (lpColumnInfo)
6403 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6404 Free(lpColumnInfo);
6406 return -1;
6409 /***
6410 * DESCRIPTION:
6411 * Sets the attributes of a header item.
6413 * PARAMETER(S):
6414 * [I] infoPtr : valid pointer to the listview structure
6415 * [I] nColumn : column index
6416 * [I] lpColumn : column attributes
6417 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6419 * RETURN:
6420 * SUCCESS : TRUE
6421 * FAILURE : FALSE
6423 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6424 const LVCOLUMNW *lpColumn, BOOL isW)
6426 HDITEMW hdi, hdiget;
6427 BOOL bResult;
6429 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6431 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6433 ZeroMemory(&hdi, sizeof(HDITEMW));
6434 if (lpColumn->mask & LVCF_FMT)
6436 hdi.mask |= HDI_FORMAT;
6437 hdiget.mask = HDI_FORMAT;
6438 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6439 hdi.fmt = hdiget.fmt & HDF_STRING;
6441 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6443 /* set header item attributes */
6444 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6445 if (!bResult) return FALSE;
6447 if (lpColumn->mask & LVCF_FMT)
6449 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6450 int oldFmt = lpColumnInfo->fmt;
6452 lpColumnInfo->fmt = lpColumn->fmt;
6453 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6455 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6456 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6460 return TRUE;
6463 /***
6464 * DESCRIPTION:
6465 * Sets the column order array
6467 * PARAMETERS:
6468 * [I] infoPtr : valid pointer to the listview structure
6469 * [I] iCount : number of elements in column order array
6470 * [I] lpiArray : pointer to column order array
6472 * RETURN:
6473 * SUCCESS : TRUE
6474 * FAILURE : FALSE
6476 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6478 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6480 if (!lpiArray)
6481 return FALSE;
6483 return TRUE;
6487 /***
6488 * DESCRIPTION:
6489 * Sets the width of a column
6491 * PARAMETERS:
6492 * [I] infoPtr : valid pointer to the listview structure
6493 * [I] nColumn : column index
6494 * [I] cx : column width
6496 * RETURN:
6497 * SUCCESS : TRUE
6498 * FAILURE : FALSE
6500 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6502 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6503 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6504 INT max_cx = 0;
6505 HDITEMW hdi;
6507 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6509 /* set column width only if in report or list mode */
6510 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6512 /* take care of invalid cx values */
6513 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6514 else if (uView == LVS_LIST && cx < 1) return FALSE;
6516 /* resize all columns if in LVS_LIST mode */
6517 if(uView == LVS_LIST)
6519 infoPtr->nItemWidth = cx;
6520 LISTVIEW_InvalidateList(infoPtr);
6521 return TRUE;
6524 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6526 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6528 INT nLabelWidth;
6529 LVITEMW lvItem;
6531 lvItem.mask = LVIF_TEXT;
6532 lvItem.iItem = 0;
6533 lvItem.iSubItem = nColumn;
6534 lvItem.pszText = szDispText;
6535 lvItem.cchTextMax = DISP_TEXT_SIZE;
6536 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6538 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6539 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6540 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6542 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6543 max_cx += infoPtr->iconSize.cx;
6544 max_cx += TRAILING_LABEL_PADDING;
6547 /* autosize based on listview items width */
6548 if(cx == LVSCW_AUTOSIZE)
6549 cx = max_cx;
6550 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6552 /* if iCol is the last column make it fill the remainder of the controls width */
6553 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6555 RECT rcHeader;
6556 POINT Origin;
6558 LISTVIEW_GetOrigin(infoPtr, &Origin);
6559 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6561 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6563 else
6565 /* Despite what the MS docs say, if this is not the last
6566 column, then MS resizes the column to the width of the
6567 largest text string in the column, including headers
6568 and items. This is different from LVSCW_AUTOSIZE in that
6569 LVSCW_AUTOSIZE ignores the header string length. */
6570 cx = 0;
6572 /* retrieve header text */
6573 hdi.mask = HDI_TEXT;
6574 hdi.cchTextMax = DISP_TEXT_SIZE;
6575 hdi.pszText = szDispText;
6576 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6578 HDC hdc = GetDC(infoPtr->hwndSelf);
6579 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6580 SIZE size;
6582 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6583 cx = size.cx + TRAILING_HEADER_PADDING;
6584 /* FIXME: Take into account the header image, if one is present */
6585 SelectObject(hdc, old_font);
6586 ReleaseDC(infoPtr->hwndSelf, hdc);
6588 cx = max (cx, max_cx);
6592 if (cx < 0) return FALSE;
6594 /* call header to update the column change */
6595 hdi.mask = HDI_WIDTH;
6596 hdi.cxy = cx;
6597 TRACE("hdi.cxy=%d\n", hdi.cxy);
6598 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6601 /***
6602 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6605 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6607 HDC hdc_wnd, hdc;
6608 HBITMAP hbm_im, hbm_mask, hbm_orig;
6609 RECT rc;
6610 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6611 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6612 HIMAGELIST himl;
6614 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6615 ILC_COLOR | ILC_MASK, 2, 2);
6616 hdc_wnd = GetDC(infoPtr->hwndSelf);
6617 hdc = CreateCompatibleDC(hdc_wnd);
6618 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6619 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6620 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6622 rc.left = rc.top = 0;
6623 rc.right = GetSystemMetrics(SM_CXSMICON);
6624 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6626 hbm_orig = SelectObject(hdc, hbm_mask);
6627 FillRect(hdc, &rc, hbr_white);
6628 InflateRect(&rc, -3, -3);
6629 FillRect(hdc, &rc, hbr_black);
6631 SelectObject(hdc, hbm_im);
6632 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6633 SelectObject(hdc, hbm_orig);
6634 ImageList_Add(himl, hbm_im, hbm_mask);
6636 SelectObject(hdc, hbm_im);
6637 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6638 SelectObject(hdc, hbm_orig);
6639 ImageList_Add(himl, hbm_im, hbm_mask);
6641 DeleteObject(hbm_mask);
6642 DeleteObject(hbm_im);
6643 DeleteDC(hdc);
6645 return himl;
6648 /***
6649 * DESCRIPTION:
6650 * Sets the extended listview style.
6652 * PARAMETERS:
6653 * [I] infoPtr : valid pointer to the listview structure
6654 * [I] dwMask : mask
6655 * [I] dwStyle : style
6657 * RETURN:
6658 * SUCCESS : previous style
6659 * FAILURE : 0
6661 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6663 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6665 /* set new style */
6666 if (dwMask)
6667 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6668 else
6669 infoPtr->dwLvExStyle = dwStyle;
6671 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6673 HIMAGELIST himl = 0;
6674 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6675 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6676 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6679 return dwOldStyle;
6682 /***
6683 * DESCRIPTION:
6684 * Sets the new hot cursor used during hot tracking and hover selection.
6686 * PARAMETER(S):
6687 * [I] infoPtr : valid pointer to the listview structure
6688 * [I} hCurosr : the new hot cursor handle
6690 * RETURN:
6691 * Returns the previous hot cursor
6693 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6695 HCURSOR oldCursor = infoPtr->hHotCursor;
6697 infoPtr->hHotCursor = hCursor;
6699 return oldCursor;
6703 /***
6704 * DESCRIPTION:
6705 * Sets the hot item index.
6707 * PARAMETERS:
6708 * [I] infoPtr : valid pointer to the listview structure
6709 * [I] iIndex : index
6711 * RETURN:
6712 * SUCCESS : previous hot item index
6713 * FAILURE : -1 (no hot item)
6715 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6717 INT iOldIndex = infoPtr->nHotItem;
6719 infoPtr->nHotItem = iIndex;
6721 return iOldIndex;
6725 /***
6726 * DESCRIPTION:
6727 * Sets the amount of time the cursor must hover over an item before it is selected.
6729 * PARAMETER(S):
6730 * [I] infoPtr : valid pointer to the listview structure
6731 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6733 * RETURN:
6734 * Returns the previous hover time
6736 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6738 DWORD oldHoverTime = infoPtr->dwHoverTime;
6740 infoPtr->dwHoverTime = dwHoverTime;
6742 return oldHoverTime;
6745 /***
6746 * DESCRIPTION:
6747 * Sets spacing for icons of LVS_ICON style.
6749 * PARAMETER(S):
6750 * [I] infoPtr : valid pointer to the listview structure
6751 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6752 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6754 * RETURN:
6755 * MAKELONG(oldcx, oldcy)
6757 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6759 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6760 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6762 TRACE("requested=(%d,%d)\n", cx, cy);
6764 /* this is supported only for LVS_ICON style */
6765 if (uView != LVS_ICON) return oldspacing;
6767 /* set to defaults, if instructed to */
6768 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6769 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6771 /* if 0 then compute width
6772 * FIXME: Should scan each item and determine max width of
6773 * icon or label, then make that the width */
6774 if (cx == 0)
6775 cx = infoPtr->iconSpacing.cx;
6777 /* if 0 then compute height */
6778 if (cy == 0)
6779 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6780 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6783 infoPtr->iconSpacing.cx = cx;
6784 infoPtr->iconSpacing.cy = cy;
6786 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6787 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6788 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6789 infoPtr->ntmHeight);
6791 /* these depend on the iconSpacing */
6792 LISTVIEW_UpdateItemSize(infoPtr);
6794 return oldspacing;
6797 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6799 INT cx, cy;
6801 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6803 size->cx = cx;
6804 size->cy = cy;
6806 else
6808 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6809 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6813 /***
6814 * DESCRIPTION:
6815 * Sets image lists.
6817 * PARAMETER(S):
6818 * [I] infoPtr : valid pointer to the listview structure
6819 * [I] nType : image list type
6820 * [I] himl : image list handle
6822 * RETURN:
6823 * SUCCESS : old image list
6824 * FAILURE : NULL
6826 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6828 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6829 INT oldHeight = infoPtr->nItemHeight;
6830 HIMAGELIST himlOld = 0;
6832 TRACE("(nType=%d, himl=%p\n", nType, himl);
6834 switch (nType)
6836 case LVSIL_NORMAL:
6837 himlOld = infoPtr->himlNormal;
6838 infoPtr->himlNormal = himl;
6839 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6840 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6841 break;
6843 case LVSIL_SMALL:
6844 himlOld = infoPtr->himlSmall;
6845 infoPtr->himlSmall = himl;
6846 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6847 break;
6849 case LVSIL_STATE:
6850 himlOld = infoPtr->himlState;
6851 infoPtr->himlState = himl;
6852 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6853 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6854 break;
6856 default:
6857 ERR("Unknown icon type=%d\n", nType);
6858 return NULL;
6861 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6862 if (infoPtr->nItemHeight != oldHeight)
6863 LISTVIEW_UpdateScroll(infoPtr);
6865 return himlOld;
6868 /***
6869 * DESCRIPTION:
6870 * Preallocates memory (does *not* set the actual count of items !)
6872 * PARAMETER(S):
6873 * [I] infoPtr : valid pointer to the listview structure
6874 * [I] nItems : item count (projected number of items to allocate)
6875 * [I] dwFlags : update flags
6877 * RETURN:
6878 * SUCCESS : TRUE
6879 * FAILURE : FALSE
6881 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6883 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6885 if (infoPtr->dwStyle & LVS_OWNERDATA)
6887 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6888 INT nOldCount = infoPtr->nItemCount;
6890 if (nItems < nOldCount)
6892 RANGE range = { nItems, nOldCount };
6893 ranges_del(infoPtr->selectionRanges, range);
6894 if (infoPtr->nFocusedItem >= nItems)
6896 infoPtr->nFocusedItem = -1;
6897 SetRectEmpty(&infoPtr->rcFocus);
6901 infoPtr->nItemCount = nItems;
6902 LISTVIEW_UpdateScroll(infoPtr);
6904 /* the flags are valid only in ownerdata report and list modes */
6905 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6907 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6908 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6910 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6911 LISTVIEW_InvalidateList(infoPtr);
6912 else
6914 INT nFrom, nTo;
6915 POINT Origin;
6916 RECT rcErase;
6918 LISTVIEW_GetOrigin(infoPtr, &Origin);
6919 nFrom = min(nOldCount, nItems);
6920 nTo = max(nOldCount, nItems);
6922 if (uView == LVS_REPORT)
6924 rcErase.left = 0;
6925 rcErase.top = nFrom * infoPtr->nItemHeight;
6926 rcErase.right = infoPtr->nItemWidth;
6927 rcErase.bottom = nTo * infoPtr->nItemHeight;
6928 OffsetRect(&rcErase, Origin.x, Origin.y);
6929 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6930 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6932 else /* LVS_LIST */
6934 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6936 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6937 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6938 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6939 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6940 OffsetRect(&rcErase, Origin.x, Origin.y);
6941 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6942 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6944 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6945 rcErase.top = 0;
6946 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6947 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6948 OffsetRect(&rcErase, Origin.x, Origin.y);
6949 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6950 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6954 else
6956 /* According to MSDN for non-LVS_OWNERDATA this is just
6957 * a performance issue. The control allocates its internal
6958 * data structures for the number of items specified. It
6959 * cuts down on the number of memory allocations. Therefore
6960 * we will just issue a WARN here
6962 WARN("for non-ownerdata performance option not implemented.\n");
6965 return TRUE;
6968 /***
6969 * DESCRIPTION:
6970 * Sets the position of an item.
6972 * PARAMETER(S):
6973 * [I] infoPtr : valid pointer to the listview structure
6974 * [I] nItem : item index
6975 * [I] pt : coordinate
6977 * RETURN:
6978 * SUCCESS : TRUE
6979 * FAILURE : FALSE
6981 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6983 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6984 POINT Origin;
6986 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
6988 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
6989 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
6991 LISTVIEW_GetOrigin(infoPtr, &Origin);
6993 /* This point value seems to be an undocumented feature.
6994 * The best guess is that it means either at the origin,
6995 * or at true beginning of the list. I will assume the origin. */
6996 if ((pt.x == -1) && (pt.y == -1))
6997 pt = Origin;
6999 if (uView == LVS_ICON)
7001 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7002 pt.y -= ICON_TOP_PADDING;
7004 pt.x -= Origin.x;
7005 pt.y -= Origin.y;
7007 infoPtr->bAutoarrange = FALSE;
7009 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7012 /***
7013 * DESCRIPTION:
7014 * Sets the state of one or many items.
7016 * PARAMETER(S):
7017 * [I] infoPtr : valid pointer to the listview structure
7018 * [I] nItem : item index
7019 * [I] lpLVItem : item or subitem info
7021 * RETURN:
7022 * SUCCESS : TRUE
7023 * FAILURE : FALSE
7025 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7027 BOOL bResult = TRUE;
7028 LVITEMW lvItem;
7030 lvItem.iItem = nItem;
7031 lvItem.iSubItem = 0;
7032 lvItem.mask = LVIF_STATE;
7033 lvItem.state = lpLVItem->state;
7034 lvItem.stateMask = lpLVItem->stateMask;
7035 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7037 if (nItem == -1)
7039 /* apply to all items */
7040 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7041 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7043 else
7044 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7046 return bResult;
7049 /***
7050 * DESCRIPTION:
7051 * Sets the text of an item or subitem.
7053 * PARAMETER(S):
7054 * [I] hwnd : window handle
7055 * [I] nItem : item index
7056 * [I] lpLVItem : item or subitem info
7057 * [I] isW : TRUE if input is Unicode
7059 * RETURN:
7060 * SUCCESS : TRUE
7061 * FAILURE : FALSE
7063 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7065 LVITEMW lvItem;
7067 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7069 lvItem.iItem = nItem;
7070 lvItem.iSubItem = lpLVItem->iSubItem;
7071 lvItem.mask = LVIF_TEXT;
7072 lvItem.pszText = lpLVItem->pszText;
7073 lvItem.cchTextMax = lpLVItem->cchTextMax;
7075 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7077 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7080 /***
7081 * DESCRIPTION:
7082 * Set item index that marks the start of a multiple selection.
7084 * PARAMETER(S):
7085 * [I] infoPtr : valid pointer to the listview structure
7086 * [I] nIndex : index
7088 * RETURN:
7089 * Index number or -1 if there is no selection mark.
7091 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7093 INT nOldIndex = infoPtr->nSelectionMark;
7095 TRACE("(nIndex=%d)\n", nIndex);
7097 infoPtr->nSelectionMark = nIndex;
7099 return nOldIndex;
7102 /***
7103 * DESCRIPTION:
7104 * Sets the text background color.
7106 * PARAMETER(S):
7107 * [I] infoPtr : valid pointer to the listview structure
7108 * [I] clrTextBk : text background color
7110 * RETURN:
7111 * SUCCESS : TRUE
7112 * FAILURE : FALSE
7114 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7116 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7118 if (infoPtr->clrTextBk != clrTextBk)
7120 infoPtr->clrTextBk = clrTextBk;
7121 LISTVIEW_InvalidateList(infoPtr);
7124 return TRUE;
7127 /***
7128 * DESCRIPTION:
7129 * Sets the text foreground color.
7131 * PARAMETER(S):
7132 * [I] infoPtr : valid pointer to the listview structure
7133 * [I] clrText : text color
7135 * RETURN:
7136 * SUCCESS : TRUE
7137 * FAILURE : FALSE
7139 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7141 TRACE("(clrText=%lx)\n", clrText);
7143 if (infoPtr->clrText != clrText)
7145 infoPtr->clrText = clrText;
7146 LISTVIEW_InvalidateList(infoPtr);
7149 return TRUE;
7152 /***
7153 * DESCRIPTION:
7154 * Determines which listview item is located at the specified position.
7156 * PARAMETER(S):
7157 * [I] infoPtr : valid pointer to the listview structure
7158 * [I] hwndNewToolTip : handle to new ToolTip
7160 * RETURN:
7161 * old tool tip
7163 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7165 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7166 infoPtr->hwndToolTip = hwndNewToolTip;
7167 return hwndOldToolTip;
7170 /* LISTVIEW_SetUnicodeFormat */
7171 /* LISTVIEW_SetWorkAreas */
7173 /***
7174 * DESCRIPTION:
7175 * Callback internally used by LISTVIEW_SortItems()
7177 * PARAMETER(S):
7178 * [I] first : pointer to first ITEM_INFO to compare
7179 * [I] second : pointer to second ITEM_INFO to compare
7180 * [I] lParam : HWND of control
7182 * RETURN:
7183 * if first comes before second : negative
7184 * if first comes after second : positive
7185 * if first and second are equivalent : zero
7187 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7189 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW((HWND)lParam, 0);
7190 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7191 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7193 /* Forward the call to the client defined callback */
7194 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7197 /***
7198 * DESCRIPTION:
7199 * Sorts the listview items.
7201 * PARAMETER(S):
7202 * [I] infoPtr : valid pointer to the listview structure
7203 * [I] pfnCompare : application-defined value
7204 * [I] lParamSort : pointer to comparision callback
7206 * RETURN:
7207 * SUCCESS : TRUE
7208 * FAILURE : FALSE
7210 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7212 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7213 HDPA hdpaSubItems;
7214 ITEM_INFO *lpItem;
7215 LPVOID selectionMarkItem;
7216 LVITEMW item;
7217 int i;
7219 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7221 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7223 if (!infoPtr->hdpaItems) return FALSE;
7225 /* if there are 0 or 1 items, there is no need to sort */
7226 if (infoPtr->nItemCount < 2) return TRUE;
7228 if (infoPtr->nFocusedItem >= 0)
7230 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7231 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7232 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7234 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7235 /* clear the lpItem->state for non-selected ones */
7236 /* remove the selection ranges */
7238 infoPtr->pfnCompare = pfnCompare;
7239 infoPtr->lParamSort = lParamSort;
7240 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr->hwndSelf);
7242 /* Adjust selections and indices so that they are the way they should
7243 * be after the sort (otherwise, the list items move around, but
7244 * whatever is at the item's previous original position will be
7245 * selected instead)
7247 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7248 for (i=0; i < infoPtr->nItemCount; i++)
7250 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7251 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7253 if (lpItem->state & LVIS_SELECTED)
7255 item.state = LVIS_SELECTED;
7256 item.stateMask = LVIS_SELECTED;
7257 LISTVIEW_SetItemState(infoPtr, i, &item);
7259 if (lpItem->state & LVIS_FOCUSED)
7261 infoPtr->nFocusedItem = i;
7262 lpItem->state &= ~LVIS_FOCUSED;
7265 if (selectionMarkItem != NULL)
7266 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7267 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7269 /* refresh the display */
7270 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7271 LISTVIEW_InvalidateList(infoPtr);
7273 return TRUE;
7276 /***
7277 * DESCRIPTION:
7278 * Updates an items or rearranges the listview control.
7280 * PARAMETER(S):
7281 * [I] infoPtr : valid pointer to the listview structure
7282 * [I] nItem : item index
7284 * RETURN:
7285 * SUCCESS : TRUE
7286 * FAILURE : FALSE
7288 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7290 TRACE("(nItem=%d)\n", nItem);
7292 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7294 /* rearrange with default alignment style */
7295 if (is_autoarrange(infoPtr))
7296 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7297 else
7298 LISTVIEW_InvalidateItem(infoPtr, nItem);
7300 return TRUE;
7304 /***
7305 * DESCRIPTION:
7306 * Creates the listview control.
7308 * PARAMETER(S):
7309 * [I] hwnd : window handle
7310 * [I] lpcs : the create parameters
7312 * RETURN:
7313 * Success: 0
7314 * Failure: -1
7316 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7318 LISTVIEW_INFO *infoPtr;
7319 UINT uView = lpcs->style & LVS_TYPEMASK;
7320 LOGFONTW logFont;
7322 TRACE("(lpcs=%p)\n", lpcs);
7324 /* initialize info pointer */
7325 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7326 if (!infoPtr) return -1;
7328 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
7330 infoPtr->hwndSelf = hwnd;
7331 infoPtr->dwStyle = lpcs->style;
7332 /* determine the type of structures to use */
7333 infoPtr->hwndNotify = lpcs->hwndParent;
7334 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7335 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7337 /* initialize color information */
7338 infoPtr->clrBk = CLR_NONE;
7339 infoPtr->clrText = comctl32_color.clrWindowText;
7340 infoPtr->clrTextBk = CLR_DEFAULT;
7341 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7343 /* set default values */
7344 infoPtr->nFocusedItem = -1;
7345 infoPtr->nSelectionMark = -1;
7346 infoPtr->nHotItem = -1;
7347 infoPtr->bRedraw = TRUE;
7348 infoPtr->bNoItemMetrics = TRUE;
7349 infoPtr->bDoChangeNotify = TRUE;
7350 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7351 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7352 infoPtr->nEditLabelItem = -1;
7353 infoPtr->dwHoverTime = -1; /* default system hover time */
7355 /* get default font (icon title) */
7356 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7357 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7358 infoPtr->hFont = infoPtr->hDefaultFont;
7359 LISTVIEW_SaveTextMetrics(infoPtr);
7361 /* create header */
7362 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7363 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7364 0, 0, 0, 0, hwnd, NULL,
7365 lpcs->hInstance, NULL);
7366 if (!infoPtr->hwndHeader) goto fail;
7368 /* set header unicode format */
7369 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7371 /* set header font */
7372 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7374 /* allocate memory for the data structure */
7375 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7376 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7377 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7378 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7379 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7381 /* initialize the icon sizes */
7382 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7383 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7385 /* init item size to avoid division by 0 */
7386 LISTVIEW_UpdateItemSize (infoPtr);
7388 if (uView == LVS_REPORT)
7390 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7392 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7394 else
7396 /* set HDS_HIDDEN flag to hide the header bar */
7397 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7398 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7402 return 0;
7404 fail:
7405 DestroyWindow(infoPtr->hwndHeader);
7406 ranges_destroy(infoPtr->selectionRanges);
7407 DPA_Destroy(infoPtr->hdpaItems);
7408 DPA_Destroy(infoPtr->hdpaPosX);
7409 DPA_Destroy(infoPtr->hdpaPosY);
7410 DPA_Destroy(infoPtr->hdpaColumns);
7411 Free(infoPtr);
7412 return -1;
7415 /***
7416 * DESCRIPTION:
7417 * Erases the background of the listview control.
7419 * PARAMETER(S):
7420 * [I] infoPtr : valid pointer to the listview structure
7421 * [I] hdc : device context handle
7423 * RETURN:
7424 * SUCCESS : TRUE
7425 * FAILURE : FALSE
7427 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7429 RECT rc;
7431 TRACE("(hdc=%p)\n", hdc);
7433 if (!GetClipBox(hdc, &rc)) return FALSE;
7435 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7439 /***
7440 * DESCRIPTION:
7441 * Helper function for LISTVIEW_[HV]Scroll *only*.
7442 * Performs vertical/horizontal scrolling by a give amount.
7444 * PARAMETER(S):
7445 * [I] infoPtr : valid pointer to the listview structure
7446 * [I] dx : amount of horizontal scroll
7447 * [I] dy : amount of vertical scroll
7449 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7451 /* now we can scroll the list */
7452 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7453 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7454 /* if we have focus, adjust rect */
7455 OffsetRect(&infoPtr->rcFocus, dx, dy);
7456 UpdateWindow(infoPtr->hwndSelf);
7459 /***
7460 * DESCRIPTION:
7461 * Performs vertical scrolling.
7463 * PARAMETER(S):
7464 * [I] infoPtr : valid pointer to the listview structure
7465 * [I] nScrollCode : scroll code
7466 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7467 * [I] hScrollWnd : scrollbar control window handle
7469 * RETURN:
7470 * Zero
7472 * NOTES:
7473 * SB_LINEUP/SB_LINEDOWN:
7474 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7475 * for LVS_REPORT is 1 line
7476 * for LVS_LIST cannot occur
7479 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7480 INT nScrollDiff, HWND hScrollWnd)
7482 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7483 INT nOldScrollPos, nNewScrollPos;
7484 SCROLLINFO scrollInfo;
7485 BOOL is_an_icon;
7487 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7488 debugscrollcode(nScrollCode), nScrollDiff);
7490 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7492 scrollInfo.cbSize = sizeof(SCROLLINFO);
7493 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7495 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7497 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7499 nOldScrollPos = scrollInfo.nPos;
7500 switch (nScrollCode)
7502 case SB_INTERNAL:
7503 break;
7505 case SB_LINEUP:
7506 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7507 break;
7509 case SB_LINEDOWN:
7510 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7511 break;
7513 case SB_PAGEUP:
7514 nScrollDiff = -scrollInfo.nPage;
7515 break;
7517 case SB_PAGEDOWN:
7518 nScrollDiff = scrollInfo.nPage;
7519 break;
7521 case SB_THUMBPOSITION:
7522 case SB_THUMBTRACK:
7523 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7524 break;
7526 default:
7527 nScrollDiff = 0;
7530 /* quit right away if pos isn't changing */
7531 if (nScrollDiff == 0) return 0;
7533 /* calculate new position, and handle overflows */
7534 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7535 if (nScrollDiff > 0) {
7536 if (nNewScrollPos < nOldScrollPos ||
7537 nNewScrollPos > scrollInfo.nMax)
7538 nNewScrollPos = scrollInfo.nMax;
7539 } else {
7540 if (nNewScrollPos > nOldScrollPos ||
7541 nNewScrollPos < scrollInfo.nMin)
7542 nNewScrollPos = scrollInfo.nMin;
7545 /* set the new position, and reread in case it changed */
7546 scrollInfo.fMask = SIF_POS;
7547 scrollInfo.nPos = nNewScrollPos;
7548 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7550 /* carry on only if it really changed */
7551 if (nNewScrollPos == nOldScrollPos) return 0;
7553 /* now adjust to client coordinates */
7554 nScrollDiff = nOldScrollPos - nNewScrollPos;
7555 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7557 /* and scroll the window */
7558 scroll_list(infoPtr, 0, nScrollDiff);
7560 return 0;
7563 /***
7564 * DESCRIPTION:
7565 * Performs horizontal scrolling.
7567 * PARAMETER(S):
7568 * [I] infoPtr : valid pointer to the listview structure
7569 * [I] nScrollCode : scroll code
7570 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7571 * [I] hScrollWnd : scrollbar control window handle
7573 * RETURN:
7574 * Zero
7576 * NOTES:
7577 * SB_LINELEFT/SB_LINERIGHT:
7578 * for LVS_ICON, LVS_SMALLICON 1 pixel
7579 * for LVS_REPORT is 1 pixel
7580 * for LVS_LIST is 1 column --> which is a 1 because the
7581 * scroll is based on columns not pixels
7584 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7585 INT nScrollDiff, HWND hScrollWnd)
7587 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7588 INT nOldScrollPos, nNewScrollPos;
7589 SCROLLINFO scrollInfo;
7591 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7592 debugscrollcode(nScrollCode), nScrollDiff);
7594 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7596 scrollInfo.cbSize = sizeof(SCROLLINFO);
7597 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7599 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7601 nOldScrollPos = scrollInfo.nPos;
7603 switch (nScrollCode)
7605 case SB_INTERNAL:
7606 break;
7608 case SB_LINELEFT:
7609 nScrollDiff = -1;
7610 break;
7612 case SB_LINERIGHT:
7613 nScrollDiff = 1;
7614 break;
7616 case SB_PAGELEFT:
7617 nScrollDiff = -scrollInfo.nPage;
7618 break;
7620 case SB_PAGERIGHT:
7621 nScrollDiff = scrollInfo.nPage;
7622 break;
7624 case SB_THUMBPOSITION:
7625 case SB_THUMBTRACK:
7626 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7627 break;
7629 default:
7630 nScrollDiff = 0;
7633 /* quit right away if pos isn't changing */
7634 if (nScrollDiff == 0) return 0;
7636 /* calculate new position, and handle overflows */
7637 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7638 if (nScrollDiff > 0) {
7639 if (nNewScrollPos < nOldScrollPos ||
7640 nNewScrollPos > scrollInfo.nMax)
7641 nNewScrollPos = scrollInfo.nMax;
7642 } else {
7643 if (nNewScrollPos > nOldScrollPos ||
7644 nNewScrollPos < scrollInfo.nMin)
7645 nNewScrollPos = scrollInfo.nMin;
7648 /* set the new position, and reread in case it changed */
7649 scrollInfo.fMask = SIF_POS;
7650 scrollInfo.nPos = nNewScrollPos;
7651 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7653 /* carry on only if it really changed */
7654 if (nNewScrollPos == nOldScrollPos) return 0;
7656 if(uView == LVS_REPORT)
7657 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7659 /* now adjust to client coordinates */
7660 nScrollDiff = nOldScrollPos - nNewScrollPos;
7661 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7663 /* and scroll the window */
7664 scroll_list(infoPtr, nScrollDiff, 0);
7666 return 0;
7669 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7671 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7672 INT gcWheelDelta = 0;
7673 UINT pulScrollLines = 3;
7674 SCROLLINFO scrollInfo;
7676 TRACE("(wheelDelta=%d)\n", wheelDelta);
7678 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7679 gcWheelDelta -= wheelDelta;
7681 scrollInfo.cbSize = sizeof(SCROLLINFO);
7682 scrollInfo.fMask = SIF_POS;
7684 switch(uView)
7686 case LVS_ICON:
7687 case LVS_SMALLICON:
7689 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7690 * should be fixed in the future.
7692 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7693 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7694 break;
7696 case LVS_REPORT:
7697 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7699 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7700 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7701 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7703 break;
7705 case LVS_LIST:
7706 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7707 break;
7709 return 0;
7712 /***
7713 * DESCRIPTION:
7714 * ???
7716 * PARAMETER(S):
7717 * [I] infoPtr : valid pointer to the listview structure
7718 * [I] nVirtualKey : virtual key
7719 * [I] lKeyData : key data
7721 * RETURN:
7722 * Zero
7724 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7726 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7727 INT nItem = -1;
7728 NMLVKEYDOWN nmKeyDown;
7730 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7732 /* send LVN_KEYDOWN notification */
7733 nmKeyDown.wVKey = nVirtualKey;
7734 nmKeyDown.flags = 0;
7735 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7737 switch (nVirtualKey)
7739 case VK_RETURN:
7740 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7742 notify(infoPtr, NM_RETURN);
7743 notify(infoPtr, LVN_ITEMACTIVATE);
7745 break;
7747 case VK_HOME:
7748 if (infoPtr->nItemCount > 0)
7749 nItem = 0;
7750 break;
7752 case VK_END:
7753 if (infoPtr->nItemCount > 0)
7754 nItem = infoPtr->nItemCount - 1;
7755 break;
7757 case VK_LEFT:
7758 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7759 break;
7761 case VK_UP:
7762 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7763 break;
7765 case VK_RIGHT:
7766 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7767 break;
7769 case VK_DOWN:
7770 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7771 break;
7773 case VK_PRIOR:
7774 if (uView == LVS_REPORT)
7775 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7776 else
7777 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7778 * LISTVIEW_GetCountPerRow(infoPtr);
7779 if(nItem < 0) nItem = 0;
7780 break;
7782 case VK_NEXT:
7783 if (uView == LVS_REPORT)
7784 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7785 else
7786 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7787 * LISTVIEW_GetCountPerRow(infoPtr);
7788 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7789 break;
7792 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7793 LISTVIEW_KeySelection(infoPtr, nItem);
7795 return 0;
7798 /***
7799 * DESCRIPTION:
7800 * Kills the focus.
7802 * PARAMETER(S):
7803 * [I] infoPtr : valid pointer to the listview structure
7805 * RETURN:
7806 * Zero
7808 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7810 TRACE("()\n");
7812 /* if we did not have the focus, there's nothing to do */
7813 if (!infoPtr->bFocus) return 0;
7815 /* send NM_KILLFOCUS notification */
7816 notify(infoPtr, NM_KILLFOCUS);
7818 /* if we have a focus rectagle, get rid of it */
7819 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7821 /* set window focus flag */
7822 infoPtr->bFocus = FALSE;
7824 /* invalidate the selected items before reseting focus flag */
7825 LISTVIEW_InvalidateSelectedItems(infoPtr);
7827 return 0;
7831 /***
7832 * DESCRIPTION:
7833 * Track mouse/dragging
7835 * PARAMETER(S):
7836 * [I] infoPtr : valid pointer to the listview structure
7837 * [I] pt : mouse coordinate
7839 * RETURN:
7840 * Zero
7842 static LRESULT LISTVIEW_TrackMouse(LISTVIEW_INFO *infoPtr, POINT pt)
7844 INT cxDrag = GetSystemMetrics(SM_CXDRAG);
7845 INT cyDrag = GetSystemMetrics(SM_CYDRAG);
7846 RECT r;
7847 MSG msg;
7849 TRACE("\n");
7851 r.top = pt.y - cyDrag;
7852 r.left = pt.x - cxDrag;
7853 r.bottom = pt.y + cyDrag;
7854 r.right = pt.x + cxDrag;
7856 SetCapture(infoPtr->hwndSelf);
7858 while (1)
7860 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
7862 if (msg.message == WM_MOUSEMOVE)
7864 pt.x = (short)LOWORD(msg.lParam);
7865 pt.y = (short)HIWORD(msg.lParam);
7866 if (PtInRect(&r, pt))
7867 continue;
7868 else
7870 ReleaseCapture();
7871 return 1;
7874 else if (msg.message >= WM_LBUTTONDOWN &&
7875 msg.message <= WM_RBUTTONDBLCLK)
7877 break;
7880 DispatchMessageW(&msg);
7883 if (GetCapture() != infoPtr->hwndSelf)
7884 return 0;
7887 ReleaseCapture();
7888 return 0;
7892 /***
7893 * DESCRIPTION:
7894 * Processes double click messages (left mouse button).
7896 * PARAMETER(S):
7897 * [I] infoPtr : valid pointer to the listview structure
7898 * [I] wKey : key flag
7899 * [I] pts : mouse coordinate
7901 * RETURN:
7902 * Zero
7904 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7906 LVHITTESTINFO htInfo;
7908 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7910 /* send NM_RELEASEDCAPTURE notification */
7911 notify(infoPtr, NM_RELEASEDCAPTURE);
7913 htInfo.pt.x = pts.x;
7914 htInfo.pt.y = pts.y;
7916 /* send NM_DBLCLK notification */
7917 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7918 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7920 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7921 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
7923 return 0;
7926 /***
7927 * DESCRIPTION:
7928 * Processes mouse down messages (left mouse button).
7930 * PARAMETER(S):
7931 * [I] infoPtr : valid pointer to the listview structure
7932 * [I] wKey : key flag
7933 * [I] pts : mouse coordinate
7935 * RETURN:
7936 * Zero
7938 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7940 LVHITTESTINFO lvHitTestInfo;
7941 static BOOL bGroupSelect = TRUE;
7942 POINT pt = { pts.x, pts.y };
7943 INT nItem;
7945 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7947 /* send NM_RELEASEDCAPTURE notification */
7948 notify(infoPtr, NM_RELEASEDCAPTURE);
7950 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
7952 /* set left button down flag */
7953 infoPtr->bLButtonDown = TRUE;
7955 lvHitTestInfo.pt.x = pts.x;
7956 lvHitTestInfo.pt.y = pts.y;
7958 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
7959 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
7960 infoPtr->nEditLabelItem = -1;
7961 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
7963 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
7965 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
7966 if(state == 1 || state == 2)
7968 LVITEMW lvitem;
7969 state ^= 3;
7970 lvitem.state = state << 12;
7971 lvitem.stateMask = LVIS_STATEIMAGEMASK;
7972 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
7974 return 0;
7976 if (LISTVIEW_TrackMouse(infoPtr, lvHitTestInfo.pt))
7978 NMLISTVIEW nmlv;
7980 ZeroMemory(&nmlv, sizeof(nmlv));
7981 nmlv.iItem = nItem;
7982 nmlv.ptAction.x = lvHitTestInfo.pt.x;
7983 nmlv.ptAction.y = lvHitTestInfo.pt.y;
7985 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
7987 return 0;
7990 if (infoPtr->dwStyle & LVS_SINGLESEL)
7992 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
7993 infoPtr->nEditLabelItem = nItem;
7994 else
7995 LISTVIEW_SetSelection(infoPtr, nItem);
7997 else
7999 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8001 if (bGroupSelect)
8003 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8004 LISTVIEW_SetItemFocus(infoPtr, nItem);
8005 infoPtr->nSelectionMark = nItem;
8007 else
8009 LVITEMW item;
8011 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8012 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8014 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8015 infoPtr->nSelectionMark = nItem;
8018 else if (wKey & MK_CONTROL)
8020 LVITEMW item;
8022 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8024 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8025 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8026 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8027 infoPtr->nSelectionMark = nItem;
8029 else if (wKey & MK_SHIFT)
8031 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8033 else
8035 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8036 infoPtr->nEditLabelItem = nItem;
8038 /* set selection (clears other pre-existing selections) */
8039 LISTVIEW_SetSelection(infoPtr, nItem);
8043 else
8045 /* remove all selections */
8046 LISTVIEW_DeselectAll(infoPtr);
8047 ReleaseCapture();
8050 return 0;
8053 /***
8054 * DESCRIPTION:
8055 * Processes mouse up messages (left mouse button).
8057 * PARAMETER(S):
8058 * [I] infoPtr : valid pointer to the listview structure
8059 * [I] wKey : key flag
8060 * [I] pts : mouse coordinate
8062 * RETURN:
8063 * Zero
8065 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8067 LVHITTESTINFO lvHitTestInfo;
8069 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
8071 if (!infoPtr->bLButtonDown) return 0;
8073 lvHitTestInfo.pt.x = pts.x;
8074 lvHitTestInfo.pt.y = pts.y;
8076 /* send NM_CLICK notification */
8077 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8078 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8080 /* set left button flag */
8081 infoPtr->bLButtonDown = FALSE;
8083 /* if we clicked on a selected item, edit the label */
8084 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8085 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8087 return 0;
8090 /***
8091 * DESCRIPTION:
8092 * Destroys the listview control (called after WM_DESTROY).
8094 * PARAMETER(S):
8095 * [I] infoPtr : valid pointer to the listview structure
8097 * RETURN:
8098 * Zero
8100 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8102 TRACE("()\n");
8104 /* delete all items */
8105 LISTVIEW_DeleteAllItems(infoPtr);
8107 /* destroy data structure */
8108 DPA_Destroy(infoPtr->hdpaItems);
8109 DPA_Destroy(infoPtr->hdpaPosX);
8110 DPA_Destroy(infoPtr->hdpaPosY);
8111 DPA_Destroy(infoPtr->hdpaColumns);
8112 ranges_destroy(infoPtr->selectionRanges);
8114 /* destroy image lists */
8115 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8117 if (infoPtr->himlNormal)
8118 ImageList_Destroy(infoPtr->himlNormal);
8119 if (infoPtr->himlSmall)
8120 ImageList_Destroy(infoPtr->himlSmall);
8121 if (infoPtr->himlState)
8122 ImageList_Destroy(infoPtr->himlState);
8125 /* destroy font, bkgnd brush */
8126 infoPtr->hFont = 0;
8127 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8128 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8130 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
8132 /* free listview info pointer*/
8133 Free(infoPtr);
8135 return 0;
8138 /***
8139 * DESCRIPTION:
8140 * Handles notifications from header.
8142 * PARAMETER(S):
8143 * [I] infoPtr : valid pointer to the listview structure
8144 * [I] nCtrlId : control identifier
8145 * [I] lpnmh : notification information
8147 * RETURN:
8148 * Zero
8150 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8152 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8154 TRACE("(lpnmh=%p)\n", lpnmh);
8156 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8158 switch (lpnmh->hdr.code)
8160 case HDN_TRACKW:
8161 case HDN_TRACKA:
8162 case HDN_ITEMCHANGEDW:
8163 case HDN_ITEMCHANGEDA:
8165 COLUMN_INFO *lpColumnInfo;
8166 INT dx, cxy;
8168 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8170 HDITEMW hdi;
8172 hdi.mask = HDI_WIDTH;
8173 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8174 cxy = hdi.cxy;
8176 else
8177 cxy = lpnmh->pitem->cxy;
8179 /* determine how much we change since the last know position */
8180 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8181 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8182 if (dx != 0)
8184 RECT rcCol = lpColumnInfo->rcHeader;
8186 lpColumnInfo->rcHeader.right += dx;
8187 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8188 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8190 /* this trick works for left aligned columns only */
8191 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8193 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8194 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8196 rcCol.top = infoPtr->rcList.top;
8197 rcCol.bottom = infoPtr->rcList.bottom;
8198 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8202 break;
8204 case HDN_ITEMCLICKW:
8205 case HDN_ITEMCLICKA:
8207 /* Handle sorting by Header Column */
8208 NMLISTVIEW nmlv;
8210 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8211 nmlv.iItem = -1;
8212 nmlv.iSubItem = lpnmh->iItem;
8213 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8215 break;
8218 return 0;
8221 /***
8222 * DESCRIPTION:
8223 * Determines the type of structure to use.
8225 * PARAMETER(S):
8226 * [I] infoPtr : valid pointer to the listview structureof the sender
8227 * [I] hwndFrom : listview window handle
8228 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8230 * RETURN:
8231 * Zero
8233 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8235 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8237 if (nCommand != NF_REQUERY) return 0;
8239 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8241 return 0;
8244 /***
8245 * DESCRIPTION:
8246 * Paints/Repaints the listview control.
8248 * PARAMETER(S):
8249 * [I] infoPtr : valid pointer to the listview structure
8250 * [I] hdc : device context handle
8252 * RETURN:
8253 * Zero
8255 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8257 TRACE("(hdc=%p)\n", hdc);
8259 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8261 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8263 infoPtr->bNoItemMetrics = FALSE;
8264 LISTVIEW_UpdateItemSize(infoPtr);
8265 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8266 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8267 LISTVIEW_UpdateScroll(infoPtr);
8269 if (hdc)
8270 LISTVIEW_Refresh(infoPtr, hdc);
8271 else
8273 PAINTSTRUCT ps;
8275 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8276 if (!hdc) return 1;
8277 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8278 LISTVIEW_Refresh(infoPtr, hdc);
8279 EndPaint(infoPtr->hwndSelf, &ps);
8282 return 0;
8285 /***
8286 * DESCRIPTION:
8287 * Processes double click messages (right mouse button).
8289 * PARAMETER(S):
8290 * [I] infoPtr : valid pointer to the listview structure
8291 * [I] wKey : key flag
8292 * [I] pts : mouse coordinate
8294 * RETURN:
8295 * Zero
8297 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8299 LVHITTESTINFO lvHitTestInfo;
8301 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8303 /* send NM_RELEASEDCAPTURE notification */
8304 notify(infoPtr, NM_RELEASEDCAPTURE);
8306 /* send NM_RDBLCLK notification */
8307 lvHitTestInfo.pt.x = pts.x;
8308 lvHitTestInfo.pt.y = pts.y;
8309 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8310 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8312 return 0;
8315 /***
8316 * DESCRIPTION:
8317 * Processes mouse down messages (right mouse button).
8319 * PARAMETER(S):
8320 * [I] infoPtr : valid pointer to the listview structure
8321 * [I] wKey : key flag
8322 * [I] pts : mouse coordinate
8324 * RETURN:
8325 * Zero
8327 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8329 LVHITTESTINFO lvHitTestInfo;
8330 INT nItem;
8332 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8334 /* send NM_RELEASEDCAPTURE notification */
8335 notify(infoPtr, NM_RELEASEDCAPTURE);
8337 /* make sure the listview control window has the focus */
8338 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8340 /* set right button down flag */
8341 infoPtr->bRButtonDown = TRUE;
8343 /* determine the index of the selected item */
8344 lvHitTestInfo.pt.x = pts.x;
8345 lvHitTestInfo.pt.y = pts.y;
8346 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8348 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8350 LISTVIEW_SetItemFocus(infoPtr, nItem);
8351 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8352 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8353 LISTVIEW_SetSelection(infoPtr, nItem);
8355 else
8357 LISTVIEW_DeselectAll(infoPtr);
8360 return 0;
8363 /***
8364 * DESCRIPTION:
8365 * Processes mouse up messages (right mouse button).
8367 * PARAMETER(S):
8368 * [I] infoPtr : valid pointer to the listview structure
8369 * [I] wKey : key flag
8370 * [I] pts : mouse coordinate
8372 * RETURN:
8373 * Zero
8375 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8377 LVHITTESTINFO lvHitTestInfo;
8378 POINT pt;
8380 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8382 if (!infoPtr->bRButtonDown) return 0;
8384 /* set button flag */
8385 infoPtr->bRButtonDown = FALSE;
8387 /* Send NM_RClICK notification */
8388 lvHitTestInfo.pt.x = pts.x;
8389 lvHitTestInfo.pt.y = pts.y;
8390 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8391 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8393 /* Change to screen coordinate for WM_CONTEXTMENU */
8394 pt = lvHitTestInfo.pt;
8395 ClientToScreen(infoPtr->hwndSelf, &pt);
8397 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8398 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8399 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8401 return 0;
8405 /***
8406 * DESCRIPTION:
8407 * Sets the cursor.
8409 * PARAMETER(S):
8410 * [I] infoPtr : valid pointer to the listview structure
8411 * [I] hwnd : window handle of window containing the cursor
8412 * [I] nHittest : hit-test code
8413 * [I] wMouseMsg : ideintifier of the mouse message
8415 * RETURN:
8416 * TRUE if cursor is set
8417 * FALSE otherwise
8419 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8421 LVHITTESTINFO lvHitTestInfo;
8423 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8425 if(!infoPtr->hHotCursor) return FALSE;
8427 GetCursorPos(&lvHitTestInfo.pt);
8428 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8430 SetCursor(infoPtr->hHotCursor);
8432 return TRUE;
8435 /***
8436 * DESCRIPTION:
8437 * Sets the focus.
8439 * PARAMETER(S):
8440 * [I] infoPtr : valid pointer to the listview structure
8441 * [I] hwndLoseFocus : handle of previously focused window
8443 * RETURN:
8444 * Zero
8446 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8448 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8450 /* if we have the focus already, there's nothing to do */
8451 if (infoPtr->bFocus) return 0;
8453 /* send NM_SETFOCUS notification */
8454 notify(infoPtr, NM_SETFOCUS);
8456 /* set window focus flag */
8457 infoPtr->bFocus = TRUE;
8459 /* put the focus rect back on */
8460 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8462 /* redraw all visible selected items */
8463 LISTVIEW_InvalidateSelectedItems(infoPtr);
8465 return 0;
8468 /***
8469 * DESCRIPTION:
8470 * Sets the font.
8472 * PARAMETER(S):
8473 * [I] infoPtr : valid pointer to the listview structure
8474 * [I] fRedraw : font handle
8475 * [I] fRedraw : redraw flag
8477 * RETURN:
8478 * Zero
8480 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8482 HFONT oldFont = infoPtr->hFont;
8484 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8486 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8487 if (infoPtr->hFont == oldFont) return 0;
8489 LISTVIEW_SaveTextMetrics(infoPtr);
8491 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8492 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8494 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8496 return 0;
8499 /***
8500 * DESCRIPTION:
8501 * Message handling for WM_SETREDRAW.
8502 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8504 * PARAMETER(S):
8505 * [I] infoPtr : valid pointer to the listview structure
8506 * [I] bRedraw: state of redraw flag
8508 * RETURN:
8509 * DefWinProc return value
8511 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8513 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8515 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8516 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8518 infoPtr->bRedraw = bRedraw;
8520 if(!bRedraw) return 0;
8522 if (is_autoarrange(infoPtr))
8523 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8524 LISTVIEW_UpdateScroll(infoPtr);
8526 /* despite what the WM_SETREDRAW docs says, apps expect us
8527 * to invalidate the listview here... stupid! */
8528 LISTVIEW_InvalidateList(infoPtr);
8530 return 0;
8533 /***
8534 * DESCRIPTION:
8535 * Resizes the listview control. This function processes WM_SIZE
8536 * messages. At this time, the width and height are not used.
8538 * PARAMETER(S):
8539 * [I] infoPtr : valid pointer to the listview structure
8540 * [I] Width : new width
8541 * [I] Height : new height
8543 * RETURN:
8544 * Zero
8546 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8548 RECT rcOld = infoPtr->rcList;
8550 TRACE("(width=%d, height=%d)\n", Width, Height);
8552 LISTVIEW_UpdateSize(infoPtr);
8553 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8555 /* do not bother with display related stuff if we're not redrawing */
8556 if (!is_redrawing(infoPtr)) return 0;
8558 if (is_autoarrange(infoPtr))
8559 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8561 LISTVIEW_UpdateScroll(infoPtr);
8563 /* refresh all only for lists whose height changed significantly */
8564 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8565 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8566 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8567 LISTVIEW_InvalidateList(infoPtr);
8569 return 0;
8572 /***
8573 * DESCRIPTION:
8574 * Sets the size information.
8576 * PARAMETER(S):
8577 * [I] infoPtr : valid pointer to the listview structure
8579 * RETURN:
8580 * None
8582 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8584 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8586 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8588 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8590 if (uView == LVS_LIST)
8592 /* Apparently the "LIST" style is supposed to have the same
8593 * number of items in a column even if there is no scroll bar.
8594 * Since if a scroll bar already exists then the bottom is already
8595 * reduced, only reduce if the scroll bar does not currently exist.
8596 * The "2" is there to mimic the native control. I think it may be
8597 * related to either padding or edges. (GLA 7/2002)
8599 if (!(infoPtr->dwStyle & WS_HSCROLL))
8600 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8601 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8603 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8605 HDLAYOUT hl;
8606 WINDOWPOS wp;
8608 hl.prc = &infoPtr->rcList;
8609 hl.pwpos = &wp;
8610 Header_Layout(infoPtr->hwndHeader, &hl);
8612 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8614 infoPtr->rcList.top = max(wp.cy, 0);
8617 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8620 /***
8621 * DESCRIPTION:
8622 * Processes WM_STYLECHANGED messages.
8624 * PARAMETER(S):
8625 * [I] infoPtr : valid pointer to the listview structure
8626 * [I] wStyleType : window style type (normal or extended)
8627 * [I] lpss : window style information
8629 * RETURN:
8630 * Zero
8632 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8633 const STYLESTRUCT *lpss)
8635 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8636 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8638 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8639 wStyleType, lpss->styleOld, lpss->styleNew);
8641 if (wStyleType != GWL_STYLE) return 0;
8643 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8644 /* what if LVS_OWNERDATA changed? */
8645 /* or LVS_SINGLESEL */
8646 /* or LVS_SORT{AS,DES}CENDING */
8648 infoPtr->dwStyle = lpss->styleNew;
8650 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8651 ((lpss->styleNew & WS_HSCROLL) == 0))
8652 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8654 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8655 ((lpss->styleNew & WS_VSCROLL) == 0))
8656 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8658 if (uNewView != uOldView)
8660 SIZE oldIconSize = infoPtr->iconSize;
8661 HIMAGELIST himl;
8663 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8664 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8666 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8667 SetRectEmpty(&infoPtr->rcFocus);
8669 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8670 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8672 if (uNewView == LVS_ICON)
8674 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8676 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8677 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8678 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8681 else if (uNewView == LVS_REPORT)
8683 HDLAYOUT hl;
8684 WINDOWPOS wp;
8686 hl.prc = &infoPtr->rcList;
8687 hl.pwpos = &wp;
8688 Header_Layout(infoPtr->hwndHeader, &hl);
8689 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8692 LISTVIEW_UpdateItemSize(infoPtr);
8695 if (uNewView == LVS_REPORT)
8696 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8698 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8699 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8700 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8702 /* update the size of the client area */
8703 LISTVIEW_UpdateSize(infoPtr);
8705 /* add scrollbars if needed */
8706 LISTVIEW_UpdateScroll(infoPtr);
8708 /* invalidate client area + erase background */
8709 LISTVIEW_InvalidateList(infoPtr);
8711 return 0;
8714 /***
8715 * DESCRIPTION:
8716 * Window procedure of the listview control.
8719 static LRESULT WINAPI
8720 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8722 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8724 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8726 if (!infoPtr && (uMsg != WM_CREATE))
8727 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8729 if (infoPtr)
8731 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8734 switch (uMsg)
8736 case LVM_APPROXIMATEVIEWRECT:
8737 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8738 LOWORD(lParam), HIWORD(lParam));
8739 case LVM_ARRANGE:
8740 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8742 /* case LVM_CANCELEDITLABEL: */
8744 case LVM_CREATEDRAGIMAGE:
8745 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8747 case LVM_DELETEALLITEMS:
8748 return LISTVIEW_DeleteAllItems(infoPtr);
8750 case LVM_DELETECOLUMN:
8751 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8753 case LVM_DELETEITEM:
8754 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8756 case LVM_EDITLABELW:
8757 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8759 case LVM_EDITLABELA:
8760 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8762 /* case LVM_ENABLEGROUPVIEW: */
8764 case LVM_ENSUREVISIBLE:
8765 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8767 case LVM_FINDITEMW:
8768 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8770 case LVM_FINDITEMA:
8771 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8773 case LVM_GETBKCOLOR:
8774 return infoPtr->clrBk;
8776 /* case LVM_GETBKIMAGE: */
8778 case LVM_GETCALLBACKMASK:
8779 return infoPtr->uCallbackMask;
8781 case LVM_GETCOLUMNA:
8782 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8784 case LVM_GETCOLUMNW:
8785 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8787 case LVM_GETCOLUMNORDERARRAY:
8788 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8790 case LVM_GETCOLUMNWIDTH:
8791 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8793 case LVM_GETCOUNTPERPAGE:
8794 return LISTVIEW_GetCountPerPage(infoPtr);
8796 case LVM_GETEDITCONTROL:
8797 return (LRESULT)infoPtr->hwndEdit;
8799 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8800 return infoPtr->dwLvExStyle;
8802 /* case LVM_GETGROUPINFO: */
8804 /* case LVM_GETGROUPMETRICS: */
8806 case LVM_GETHEADER:
8807 return (LRESULT)infoPtr->hwndHeader;
8809 case LVM_GETHOTCURSOR:
8810 return (LRESULT)infoPtr->hHotCursor;
8812 case LVM_GETHOTITEM:
8813 return infoPtr->nHotItem;
8815 case LVM_GETHOVERTIME:
8816 return infoPtr->dwHoverTime;
8818 case LVM_GETIMAGELIST:
8819 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8821 /* case LVM_GETINSERTMARK: */
8823 /* case LVM_GETINSERTMARKCOLOR: */
8825 /* case LVM_GETINSERTMARKRECT: */
8827 case LVM_GETISEARCHSTRINGA:
8828 case LVM_GETISEARCHSTRINGW:
8829 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8830 return FALSE;
8832 case LVM_GETITEMA:
8833 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8835 case LVM_GETITEMW:
8836 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8838 case LVM_GETITEMCOUNT:
8839 return infoPtr->nItemCount;
8841 case LVM_GETITEMPOSITION:
8842 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8844 case LVM_GETITEMRECT:
8845 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8847 case LVM_GETITEMSPACING:
8848 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8850 case LVM_GETITEMSTATE:
8851 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8853 case LVM_GETITEMTEXTA:
8854 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8856 case LVM_GETITEMTEXTW:
8857 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8859 case LVM_GETNEXTITEM:
8860 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8862 case LVM_GETNUMBEROFWORKAREAS:
8863 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8864 return 1;
8866 case LVM_GETORIGIN:
8867 if (!lParam) return FALSE;
8868 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8869 return TRUE;
8871 /* case LVM_GETOUTLINECOLOR: */
8873 /* case LVM_GETSELECTEDCOLUMN: */
8875 case LVM_GETSELECTEDCOUNT:
8876 return LISTVIEW_GetSelectedCount(infoPtr);
8878 case LVM_GETSELECTIONMARK:
8879 return infoPtr->nSelectionMark;
8881 case LVM_GETSTRINGWIDTHA:
8882 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8884 case LVM_GETSTRINGWIDTHW:
8885 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8887 case LVM_GETSUBITEMRECT:
8888 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8890 case LVM_GETTEXTBKCOLOR:
8891 return infoPtr->clrTextBk;
8893 case LVM_GETTEXTCOLOR:
8894 return infoPtr->clrText;
8896 /* case LVM_GETTILEINFO: */
8898 /* case LVM_GETTILEVIEWINFO: */
8900 case LVM_GETTOOLTIPS:
8901 return (LRESULT)infoPtr->hwndToolTip;
8903 case LVM_GETTOPINDEX:
8904 return LISTVIEW_GetTopIndex(infoPtr);
8906 /*case LVM_GETUNICODEFORMAT:
8907 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8908 return FALSE;*/
8910 /* case LVM_GETVIEW: */
8912 case LVM_GETVIEWRECT:
8913 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8915 case LVM_GETWORKAREAS:
8916 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8917 return FALSE;
8919 /* case LVM_HASGROUP: */
8921 case LVM_HITTEST:
8922 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8924 case LVM_INSERTCOLUMNA:
8925 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8927 case LVM_INSERTCOLUMNW:
8928 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8930 /* case LVM_INSERTGROUP: */
8932 /* case LVM_INSERTGROUPSORTED: */
8934 case LVM_INSERTITEMA:
8935 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8937 case LVM_INSERTITEMW:
8938 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8940 /* case LVM_INSERTMARKHITTEST: */
8942 /* case LVM_ISGROUPVIEWENABLED: */
8944 /* case LVM_MAPIDTOINDEX: */
8946 /* case LVM_MAPINDEXTOID: */
8948 /* case LVM_MOVEGROUP: */
8950 /* case LVM_MOVEITEMTOGROUP: */
8952 case LVM_REDRAWITEMS:
8953 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
8955 /* case LVM_REMOVEALLGROUPS: */
8957 /* case LVM_REMOVEGROUP: */
8959 case LVM_SCROLL:
8960 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
8962 case LVM_SETBKCOLOR:
8963 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
8965 /* case LVM_SETBKIMAGE: */
8967 case LVM_SETCALLBACKMASK:
8968 infoPtr->uCallbackMask = (UINT)wParam;
8969 return TRUE;
8971 case LVM_SETCOLUMNA:
8972 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8974 case LVM_SETCOLUMNW:
8975 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8977 case LVM_SETCOLUMNORDERARRAY:
8978 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8980 case LVM_SETCOLUMNWIDTH:
8981 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
8983 case LVM_SETEXTENDEDLISTVIEWSTYLE:
8984 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
8986 /* case LVM_SETGROUPINFO: */
8988 /* case LVM_SETGROUPMETRICS: */
8990 case LVM_SETHOTCURSOR:
8991 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
8993 case LVM_SETHOTITEM:
8994 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
8996 case LVM_SETHOVERTIME:
8997 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
8999 case LVM_SETICONSPACING:
9000 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9002 case LVM_SETIMAGELIST:
9003 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9005 /* case LVM_SETINFOTIP: */
9007 /* case LVM_SETINSERTMARK: */
9009 /* case LVM_SETINSERTMARKCOLOR: */
9011 case LVM_SETITEMA:
9012 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9014 case LVM_SETITEMW:
9015 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9017 case LVM_SETITEMCOUNT:
9018 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9020 case LVM_SETITEMPOSITION:
9022 POINT pt;
9023 pt.x = (short)LOWORD(lParam);
9024 pt.y = (short)HIWORD(lParam);
9025 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9028 case LVM_SETITEMPOSITION32:
9029 if (lParam == 0) return FALSE;
9030 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9032 case LVM_SETITEMSTATE:
9033 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9035 case LVM_SETITEMTEXTA:
9036 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9038 case LVM_SETITEMTEXTW:
9039 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9041 /* case LVM_SETOUTLINECOLOR: */
9043 /* case LVM_SETSELECTEDCOLUMN: */
9045 case LVM_SETSELECTIONMARK:
9046 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9048 case LVM_SETTEXTBKCOLOR:
9049 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9051 case LVM_SETTEXTCOLOR:
9052 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9054 /* case LVM_SETTILEINFO: */
9056 /* case LVM_SETTILEVIEWINFO: */
9058 /* case LVM_SETTILEWIDTH: */
9060 case LVM_SETTOOLTIPS:
9061 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9063 /* case LVM_SETUNICODEFORMAT: */
9065 /* case LVM_SETVIEW: */
9067 /* case LVM_SETWORKAREAS: */
9069 /* case LVM_SORTGROUPS: */
9071 case LVM_SORTITEMS:
9072 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9074 /* LVM_SORTITEMSEX: */
9076 case LVM_SUBITEMHITTEST:
9077 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9079 case LVM_UPDATE:
9080 return LISTVIEW_Update(infoPtr, (INT)wParam);
9082 case WM_CHAR:
9083 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9085 case WM_COMMAND:
9086 return LISTVIEW_Command(infoPtr, wParam, lParam);
9088 case WM_CREATE:
9089 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9091 case WM_ERASEBKGND:
9092 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9094 case WM_GETDLGCODE:
9095 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9097 case WM_GETFONT:
9098 return (LRESULT)infoPtr->hFont;
9100 case WM_HSCROLL:
9101 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9103 case WM_KEYDOWN:
9104 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9106 case WM_KILLFOCUS:
9107 return LISTVIEW_KillFocus(infoPtr);
9109 case WM_LBUTTONDBLCLK:
9110 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9112 case WM_LBUTTONDOWN:
9113 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9115 case WM_LBUTTONUP:
9116 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9118 case WM_MOUSEMOVE:
9119 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9121 case WM_MOUSEHOVER:
9122 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9124 case WM_NCDESTROY:
9125 return LISTVIEW_NCDestroy(infoPtr);
9127 case WM_NOTIFY:
9128 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9129 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9130 else return 0;
9132 case WM_NOTIFYFORMAT:
9133 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9135 case WM_PAINT:
9136 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9138 case WM_RBUTTONDBLCLK:
9139 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9141 case WM_RBUTTONDOWN:
9142 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9144 case WM_RBUTTONUP:
9145 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9147 case WM_SETCURSOR:
9148 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9149 return TRUE;
9150 goto fwd_msg;
9152 case WM_SETFOCUS:
9153 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9155 case WM_SETFONT:
9156 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9158 case WM_SETREDRAW:
9159 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9161 case WM_SIZE:
9162 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9164 case WM_STYLECHANGED:
9165 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9167 case WM_SYSCOLORCHANGE:
9168 COMCTL32_RefreshSysColors();
9169 return 0;
9171 /* case WM_TIMER: */
9173 case WM_VSCROLL:
9174 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9176 case WM_MOUSEWHEEL:
9177 if (wParam & (MK_SHIFT | MK_CONTROL))
9178 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9179 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9181 case WM_WINDOWPOSCHANGED:
9182 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9184 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9185 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9186 LISTVIEW_UpdateSize(infoPtr);
9187 LISTVIEW_UpdateScroll(infoPtr);
9189 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9191 /* case WM_WININICHANGE: */
9193 default:
9194 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9195 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9197 fwd_msg:
9198 /* call default window procedure */
9199 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9202 return 0;
9205 /***
9206 * DESCRIPTION:
9207 * Registers the window class.
9209 * PARAMETER(S):
9210 * None
9212 * RETURN:
9213 * None
9215 void LISTVIEW_Register(void)
9217 WNDCLASSW wndClass;
9219 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9220 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9221 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
9222 wndClass.cbClsExtra = 0;
9223 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9224 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9225 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9226 wndClass.lpszClassName = WC_LISTVIEWW;
9227 RegisterClassW(&wndClass);
9230 /***
9231 * DESCRIPTION:
9232 * Unregisters the window class.
9234 * PARAMETER(S):
9235 * None
9237 * RETURN:
9238 * None
9240 void LISTVIEW_Unregister(void)
9242 UnregisterClassW(WC_LISTVIEWW, NULL);
9245 /***
9246 * DESCRIPTION:
9247 * Handle any WM_COMMAND messages
9249 * PARAMETER(S):
9250 * [I] infoPtr : valid pointer to the listview structure
9251 * [I] wParam : the first message parameter
9252 * [I] lParam : the second message parameter
9254 * RETURN:
9255 * Zero.
9257 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9259 switch (HIWORD(wParam))
9261 case EN_UPDATE:
9264 * Adjust the edit window size
9266 WCHAR buffer[1024];
9267 HDC hdc = GetDC(infoPtr->hwndEdit);
9268 HFONT hFont, hOldFont = 0;
9269 RECT rect;
9270 SIZE sz;
9271 int len;
9273 if (!infoPtr->hwndEdit || !hdc) return 0;
9274 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9275 GetWindowRect(infoPtr->hwndEdit, &rect);
9277 /* Select font to get the right dimension of the string */
9278 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9279 if(hFont != 0)
9281 hOldFont = SelectObject(hdc, hFont);
9284 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9286 TEXTMETRICW textMetric;
9288 /* Add Extra spacing for the next character */
9289 GetTextMetricsW(hdc, &textMetric);
9290 sz.cx += (textMetric.tmMaxCharWidth * 2);
9292 SetWindowPos (
9293 infoPtr->hwndEdit,
9294 HWND_TOP,
9297 sz.cx,
9298 rect.bottom - rect.top,
9299 SWP_DRAWFRAME|SWP_NOMOVE);
9301 if(hFont != 0)
9302 SelectObject(hdc, hOldFont);
9304 ReleaseDC(infoPtr->hwndSelf, hdc);
9306 break;
9309 default:
9310 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9313 return 0;
9317 /***
9318 * DESCRIPTION:
9319 * Subclassed edit control windproc function
9321 * PARAMETER(S):
9322 * [I] hwnd : the edit window handle
9323 * [I] uMsg : the message that is to be processed
9324 * [I] wParam : first message parameter
9325 * [I] lParam : second message parameter
9326 * [I] isW : TRUE if input is Unicode
9328 * RETURN:
9329 * Zero.
9331 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9333 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
9334 BOOL cancel = FALSE;
9336 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9337 hwnd, uMsg, wParam, lParam, isW);
9339 switch (uMsg)
9341 case WM_GETDLGCODE:
9342 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9344 case WM_KILLFOCUS:
9345 break;
9347 case WM_DESTROY:
9349 WNDPROC editProc = infoPtr->EditWndProc;
9350 infoPtr->EditWndProc = 0;
9351 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
9352 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9355 case WM_KEYDOWN:
9356 if (VK_ESCAPE == (INT)wParam)
9358 cancel = TRUE;
9359 break;
9361 else if (VK_RETURN == (INT)wParam)
9362 break;
9364 default:
9365 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9368 /* kill the edit */
9369 if (infoPtr->hwndEdit)
9371 LPWSTR buffer = NULL;
9373 infoPtr->hwndEdit = 0;
9374 if (!cancel)
9376 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9378 if (len)
9380 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9382 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9383 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9387 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9389 if (buffer) Free(buffer);
9393 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9394 return 0;
9397 /***
9398 * DESCRIPTION:
9399 * Subclassed edit control Unicode windproc function
9401 * PARAMETER(S):
9402 * [I] hwnd : the edit window handle
9403 * [I] uMsg : the message that is to be processed
9404 * [I] wParam : first message parameter
9405 * [I] lParam : second message parameter
9407 * RETURN:
9409 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9411 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9414 /***
9415 * DESCRIPTION:
9416 * Subclassed edit control ANSI windproc function
9418 * PARAMETER(S):
9419 * [I] hwnd : the edit window handle
9420 * [I] uMsg : the message that is to be processed
9421 * [I] wParam : first message parameter
9422 * [I] lParam : second message parameter
9424 * RETURN:
9426 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9428 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9431 /***
9432 * DESCRIPTION:
9433 * Creates a subclassed edit cotrol
9435 * PARAMETER(S):
9436 * [I] infoPtr : valid pointer to the listview structure
9437 * [I] text : initial text for the edit
9438 * [I] style : the window style
9439 * [I] isW : TRUE if input is Unicode
9441 * RETURN:
9443 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9444 INT x, INT y, INT width, INT height, BOOL isW)
9446 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9447 HWND hedit;
9448 SIZE sz;
9449 HDC hdc;
9450 HDC hOldFont=0;
9451 TEXTMETRICW textMetric;
9452 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9454 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9456 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9457 hdc = GetDC(infoPtr->hwndSelf);
9459 /* Select the font to get appropriate metric dimensions */
9460 if(infoPtr->hFont != 0)
9461 hOldFont = SelectObject(hdc, infoPtr->hFont);
9463 /*Get String Length in pixels */
9464 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9466 /*Add Extra spacing for the next character */
9467 GetTextMetricsW(hdc, &textMetric);
9468 sz.cx += (textMetric.tmMaxCharWidth * 2);
9470 if(infoPtr->hFont != 0)
9471 SelectObject(hdc, hOldFont);
9473 ReleaseDC(infoPtr->hwndSelf, hdc);
9474 if (isW)
9475 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9476 else
9477 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9479 if (!hedit) return 0;
9481 infoPtr->EditWndProc = (WNDPROC)
9482 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9483 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9485 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);
9487 return hedit;