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
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 believe this code to be complete, as per
30 * the specification mentioned above.
31 * If you discover missing features, or bugs, please note them below.
36 * -- Hot item handling, mouse hovering
37 * -- Workareas support
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
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
69 * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76 * -- LVS_NOSCROLL (see Q137520)
77 * -- LVS_SORTASCENDING, LVS_SORTDESCENDING
80 * -- LVS_EX_BORDERSELECT
83 * -- LVS_EX_HEADERDRAGDROP
86 * -- LVS_EX_MULTIWORKAREAS
87 * -- LVS_EX_ONECLICKACTIVATE
89 * -- LVS_EX_SIMPLESELECT
90 * -- LVS_EX_TRACKSELECT
91 * -- LVS_EX_TWOCLICKACTIVATE
92 * -- LVS_EX_UNDERLINECOLD
93 * -- LVS_EX_UNDERLINEHOT
96 * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
101 * -- LVN_ODSTATECHANGED
106 * -- LVM_CANCELEDITLABEL
107 * -- LVM_ENABLEGROUPVIEW
108 * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
109 * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
110 * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
111 * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
112 * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
113 * -- LVM_GETINSERTMARKRECT
114 * -- LVM_GETNUMBEROFWORKAREAS
115 * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
116 * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
117 * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
118 * -- LVM_GETTILEINFO, LVM_SETTILEINFO
119 * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
120 * -- LVM_GETTOOLTIPS, LVM_SETTOOLTIPS
121 * -- LVM_GETUNICODEFORMAT, LVM_SETUNICODEFORMAT
122 * -- LVM_GETVIEW, LVM_SETVIEW
123 * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
124 * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
125 * -- LVM_INSERTGROUPSORTED
126 * -- LVM_INSERTMARKHITTEST
127 * -- LVM_ISGROUPVIEWENABLED
128 * -- LVM_MAPIDTOINDEX, LVM_MAPINDEXTOID
130 * -- LVM_MOVEITEMTOGROUP
132 * -- LVM_SETTILEWIDTH
136 * Known differences in message stream from native control (not known if
137 * these differences cause problems):
138 * LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
139 * LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
140 * WM_CREATE does not issue WM_QUERYUISTATE and associated registry
141 * processing for "USEDOUBLECLICKTIME".
145 #include "wine/port.h"
160 #include "commctrl.h"
161 #include "comctl32.h"
163 #include "wine/debug.h"
164 #include "wine/unicode.h"
166 WINE_DEFAULT_DEBUG_CHANNEL(listview);
168 /* make sure you set this to 0 for production use! */
169 #define DEBUG_RANGES 1
171 typedef struct tagCOLUMN_INFO
173 RECT rcHeader; /* tracks the header's rectangle */
174 int fmt; /* same as LVCOLUMN.fmt */
177 typedef struct tagITEMHDR
181 } ITEMHDR, *LPITEMHDR;
183 typedef struct tagSUBITEM_INFO
189 typedef struct tagITEM_INFO
197 typedef struct tagRANGE
203 typedef struct tagRANGES
208 typedef struct tagITERATOR
217 typedef struct tagLISTVIEW_INFO
224 COLORREF clrTextBkDefault;
225 HIMAGELIST himlNormal;
226 HIMAGELIST himlSmall;
227 HIMAGELIST himlState;
230 BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
233 RANGES selectionRanges;
238 RECT rcList; /* This rectangle is really the window
239 * client rectangle possibly reduced by the
240 * horizontal scroll bar and/or header - see
241 * LISTVIEW_UpdateSize. This rectangle offset
242 * by the LISTVIEW_GetOrigin value is in
243 * client coordinates */
252 INT ntmHeight; /* Some cached metrics of the font used */
253 INT ntmAveCharWidth; /* by the listview to draw items */
254 BOOL bRedraw; /* Turns on/off repaints & invalidations */
255 BOOL bAutoarrange; /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
257 BOOL bDoChangeNotify; /* send change notification messages? */
260 DWORD dwStyle; /* the cached window GWL_STYLE */
261 DWORD dwLvExStyle; /* extended listview style */
262 INT nItemCount; /* the number of items in the list */
263 HDPA hdpaItems; /* array ITEM_INFO pointers */
264 HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
265 HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
266 HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
267 POINT currIconPos; /* this is the position next icon will be placed */
268 PFNLVCOMPARE pfnCompare;
276 DWORD cditemmode; /* Keep the custom draw flags for an item/row */
278 DWORD lastKeyPressTimestamp;
280 INT nSearchParamLength;
281 WCHAR szSearchParam[ MAX_PATH ];
288 /* How many we debug buffer to allocate */
289 #define DEBUG_BUFFERS 20
290 /* The size of a single debug bbuffer */
291 #define DEBUG_BUFFER_SIZE 256
293 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
294 #define SB_INTERNAL -1
296 /* maximum size of a label */
297 #define DISP_TEXT_SIZE 512
299 /* padding for items in list and small icon display modes */
300 #define WIDTH_PADDING 12
302 /* padding for items in list, report and small icon display modes */
303 #define HEIGHT_PADDING 1
305 /* offset of items in report display mode */
306 #define REPORT_MARGINX 2
308 /* padding for icon in large icon display mode
309 * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
310 * that HITTEST will see.
311 * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
312 * ICON_TOP_PADDING - sum of the two above.
313 * ICON_BOTTOM_PADDING - between bottom of icon and top of text
314 * LABEL_HOR_PADDING - between text and sides of box
315 * LABEL_VERT_PADDING - between bottom of text and end of box
317 * ICON_LR_PADDING - additional width above icon size.
318 * ICON_LR_HALF - half of the above value
320 #define ICON_TOP_PADDING_NOTHITABLE 2
321 #define ICON_TOP_PADDING_HITABLE 2
322 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
323 #define ICON_BOTTOM_PADDING 4
324 #define LABEL_HOR_PADDING 5
325 #define LABEL_VERT_PADDING 7
326 #define ICON_LR_PADDING 16
327 #define ICON_LR_HALF (ICON_LR_PADDING/2)
329 /* default label width for items in list and small icon display modes */
330 #define DEFAULT_LABEL_WIDTH 40
332 /* default column width for items in list display mode */
333 #define DEFAULT_COLUMN_WIDTH 128
335 /* Size of "line" scroll for V & H scrolls */
336 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
338 /* Padding betwen image and label */
339 #define IMAGE_PADDING 2
341 /* Padding behind the label */
342 #define TRAILING_LABEL_PADDING 12
343 #define TRAILING_HEADER_PADDING 11
345 /* Border for the icon caption */
346 #define CAPTION_BORDER 2
348 /* Standard DrawText flags */
349 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
350 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
351 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
353 /* The time in milliseconds to reset the search in the list */
354 #define KEY_DELAY 450
356 /* Dump the LISTVIEW_INFO structure to the debug channel */
357 #define LISTVIEW_DUMP(iP) do { \
358 TRACE("hwndSelf=%p, clrBk=0x%06lx, clrText=0x%06lx, clrTextBk=0x%06lx, ItemHeight=%d, ItemWidth=%d, Style=0x%08lx\n", \
359 iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
360 iP->nItemHeight, iP->nItemWidth, infoPtr->dwStyle); \
361 TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08lx, Focus=%d\n", \
362 iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
363 iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
364 TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%ld, icSz.cy=%ld, icSp.cx=%ld, icSp.cy=%ld, notifyFmt=%d\n", \
365 iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
366 iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
367 TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, debugrect(&iP->rcList)); \
371 * forward declarations
373 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *, LPLVITEMW, BOOL);
374 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *, INT, LPRECT);
375 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *, INT, LPPOINT);
376 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *, INT, LPPOINT);
377 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *, INT, LPRECT);
378 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *, INT);
379 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *, LPPOINT);
380 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *, LPRECT);
381 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *, INT);
382 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *, const LVITEMW *, BOOL);
383 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *);
384 static void LISTVIEW_SetSelection(LISTVIEW_INFO *, INT);
385 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
386 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *, INT, BOOL);
387 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
388 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *, PFNLVCOMPARE, LPARAM);
389 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *, LPCWSTR, BOOL);
390 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT);
391 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *, INT, UINT);
392 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
393 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
394 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
395 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *);
396 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
397 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, INT, INT, INT, INT, BOOL);
398 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
400 /******** Text handling functions *************************************/
402 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
403 * text string. The string may be ANSI or Unicode, in which case
404 * the boolean isW tells us the type of the string.
406 * The name of the function tell what type of strings it expects:
407 * W: Unicode, T: ANSI/Unicode - function of isW
410 static inline BOOL is_textW(LPCWSTR text)
412 return text != NULL && text != LPSTR_TEXTCALLBACKW;
415 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
417 /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
418 return is_textW(text);
421 static inline int textlenT(LPCWSTR text, BOOL isW)
423 return !is_textT(text, isW) ? 0 :
424 isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
427 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
430 if (isSrcW) lstrcpynW(dest, src, max);
431 else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
433 if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
434 else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
437 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
439 LPWSTR wstr = (LPWSTR)text;
441 if (!isW && is_textT(text, isW))
443 INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
444 wstr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
445 if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
447 TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
451 static inline void textfreeT(LPWSTR wstr, BOOL isW)
453 if (!isW && is_textT(wstr, isW)) HeapFree(GetProcessHeap(), 0, wstr);
457 * dest is a pointer to a Unicode string
458 * src is a pointer to a string (Unicode if isW, ANSI if !isW)
460 static BOOL textsetptrT(LPWSTR *dest, LPWSTR src, BOOL isW)
464 if (src == LPSTR_TEXTCALLBACKW)
466 if (is_textW(*dest)) Free(*dest);
467 *dest = LPSTR_TEXTCALLBACKW;
471 LPWSTR pszText = textdupTtoW(src, isW);
472 if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
473 bResult = Str_SetPtrW(dest, pszText);
474 textfreeT(pszText, isW);
480 * compares a Unicode to a Unicode/ANSI text string
482 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
484 if (!aw) return bt ? -1 : 0;
485 if (!bt) return aw ? 1 : 0;
486 if (aw == LPSTR_TEXTCALLBACKW)
487 return bt == LPSTR_TEXTCALLBACKW ? 0 : -1;
488 if (bt != LPSTR_TEXTCALLBACKW)
490 LPWSTR bw = textdupTtoW(bt, isW);
491 int r = bw ? lstrcmpW(aw, bw) : 1;
499 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
503 n = min(min(n, strlenW(s1)), strlenW(s2));
504 res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
505 return res ? res - sizeof(WCHAR) : res;
508 /******** Debugging functions *****************************************/
510 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
512 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
513 return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
516 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
518 if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
519 n = min(textlenT(text, isW), n);
520 return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
523 static char* debug_getbuf()
525 static int index = 0;
526 static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
527 return buffers[index++ % DEBUG_BUFFERS];
530 static inline const char* debugrange(const RANGE *lprng)
534 char* buf = debug_getbuf();
535 snprintf(buf, DEBUG_BUFFER_SIZE, "[%d, %d)", lprng->lower, lprng->upper);
537 } else return "(null)";
540 static inline const char* debugpoint(const POINT *lppt)
544 char* buf = debug_getbuf();
545 snprintf(buf, DEBUG_BUFFER_SIZE, "(%ld, %ld)", lppt->x, lppt->y);
547 } else return "(null)";
550 static inline const char* debugrect(const RECT *rect)
554 char* buf = debug_getbuf();
555 snprintf(buf, DEBUG_BUFFER_SIZE, "[(%ld, %ld);(%ld, %ld)]",
556 rect->left, rect->top, rect->right, rect->bottom);
558 } else return "(null)";
561 static const char * debugscrollinfo(const SCROLLINFO *pScrollInfo)
563 char* buf = debug_getbuf(), *text = buf;
564 int len, size = DEBUG_BUFFER_SIZE;
566 if (pScrollInfo == NULL) return "(null)";
567 len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
568 if (len == -1) goto end; buf += len; size -= len;
569 if (pScrollInfo->fMask & SIF_RANGE)
570 len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
572 if (len == -1) goto end; buf += len; size -= len;
573 if (pScrollInfo->fMask & SIF_PAGE)
574 len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
576 if (len == -1) goto end; buf += len; size -= len;
577 if (pScrollInfo->fMask & SIF_POS)
578 len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
580 if (len == -1) goto end; buf += len; size -= len;
581 if (pScrollInfo->fMask & SIF_TRACKPOS)
582 len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
584 if (len == -1) goto end; buf += len; size -= len;
587 buf = text + strlen(text);
589 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
593 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
597 char* buf = debug_getbuf();
598 snprintf(buf, DEBUG_BUFFER_SIZE, "iItem=%d, iSubItem=%d, uNewState=0x%x,"
599 " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld\n",
600 plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
601 plvnm->uChanged, debugpoint(&plvnm->ptAction), plvnm->lParam);
603 } else return "(null)";
606 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
608 char* buf = debug_getbuf(), *text = buf;
609 int len, size = DEBUG_BUFFER_SIZE;
611 if (lpLVItem == NULL) return "(null)";
612 len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
613 if (len == -1) goto end; buf += len; size -= len;
614 if (lpLVItem->mask & LVIF_STATE)
615 len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
617 if (len == -1) goto end; buf += len; size -= len;
618 if (lpLVItem->mask & LVIF_TEXT)
619 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
621 if (len == -1) goto end; buf += len; size -= len;
622 if (lpLVItem->mask & LVIF_IMAGE)
623 len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
625 if (len == -1) goto end; buf += len; size -= len;
626 if (lpLVItem->mask & LVIF_PARAM)
627 len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
629 if (len == -1) goto end; buf += len; size -= len;
630 if (lpLVItem->mask & LVIF_INDENT)
631 len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
633 if (len == -1) goto end; buf += len; size -= len;
636 buf = text + strlen(text);
638 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
642 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
644 char* buf = debug_getbuf(), *text = buf;
645 int len, size = DEBUG_BUFFER_SIZE;
647 if (lpColumn == NULL) return "(null)";
648 len = snprintf(buf, size, "{");
649 if (len == -1) goto end; buf += len; size -= len;
650 if (lpColumn->mask & LVCF_SUBITEM)
651 len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
653 if (len == -1) goto end; buf += len; size -= len;
654 if (lpColumn->mask & LVCF_FMT)
655 len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
657 if (len == -1) goto end; buf += len; size -= len;
658 if (lpColumn->mask & LVCF_WIDTH)
659 len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
661 if (len == -1) goto end; buf += len; size -= len;
662 if (lpColumn->mask & LVCF_TEXT)
663 len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
665 if (len == -1) goto end; buf += len; size -= len;
666 if (lpColumn->mask & LVCF_IMAGE)
667 len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
669 if (len == -1) goto end; buf += len; size -= len;
670 if (lpColumn->mask & LVCF_ORDER)
671 len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
673 if (len == -1) goto end; buf += len; size -= len;
676 buf = text + strlen(text);
678 if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
682 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
686 char* buf = debug_getbuf();
687 snprintf(buf, DEBUG_BUFFER_SIZE, "{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
688 debugpoint(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
690 } else return "(null)";
693 /* Return the corresponding text for a given scroll value */
694 static inline LPCSTR debugscrollcode(int nScrollCode)
698 case SB_LINELEFT: return "SB_LINELEFT";
699 case SB_LINERIGHT: return "SB_LINERIGHT";
700 case SB_PAGELEFT: return "SB_PAGELEFT";
701 case SB_PAGERIGHT: return "SB_PAGERIGHT";
702 case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
703 case SB_THUMBTRACK: return "SB_THUMBTRACK";
704 case SB_ENDSCROLL: return "SB_ENDSCROLL";
705 case SB_INTERNAL: return "SB_INTERNAL";
706 default: return "unknown";
711 /******** Notification functions i************************************/
713 static LRESULT notify_hdr(LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
717 TRACE("(code=%d)\n", code);
719 pnmh->hwndFrom = infoPtr->hwndSelf;
720 pnmh->idFrom = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
722 result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
723 (WPARAM)pnmh->idFrom, (LPARAM)pnmh);
725 TRACE(" <= %ld\n", result);
730 static inline LRESULT notify(LISTVIEW_INFO *infoPtr, INT code)
733 return notify_hdr(infoPtr, code, &nmh);
736 static inline void notify_itemactivate(LISTVIEW_INFO *infoPtr, LVHITTESTINFO *htInfo)
747 item.mask = LVIF_PARAM|LVIF_STATE;
748 item.iItem = htInfo->iItem;
750 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
751 nmia.lParam = item.lParam;
752 nmia.uOldState = item.state;
753 nmia.uNewState = item.state | LVIS_ACTIVATING;
754 nmia.uChanged = LVIF_STATE;
757 nmia.iItem = htInfo->iItem;
758 nmia.iSubItem = htInfo->iSubItem;
759 nmia.ptAction = htInfo->pt;
761 if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
762 if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
763 if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
765 notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
768 static inline LRESULT notify_listview(LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
770 TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
771 return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
774 static LRESULT notify_click(LISTVIEW_INFO *infoPtr, INT code, LVHITTESTINFO *lvht)
779 TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
780 ZeroMemory(&nmlv, sizeof(nmlv));
781 nmlv.iItem = lvht->iItem;
782 nmlv.iSubItem = lvht->iSubItem;
783 nmlv.ptAction = lvht->pt;
784 item.mask = LVIF_PARAM;
785 item.iItem = lvht->iItem;
787 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
788 return notify_listview(infoPtr, code, &nmlv);
791 static void notify_deleteitem(LISTVIEW_INFO *infoPtr, INT nItem)
796 ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
798 item.mask = LVIF_PARAM;
801 if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
802 notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
805 static int get_ansi_notification(INT unicodeNotificationCode)
807 switch (unicodeNotificationCode)
809 case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
810 case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
811 case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
812 case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
813 case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
814 case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
816 ERR("unknown notification %x\n", unicodeNotificationCode);
822 With testing on Windows 2000 it looks like the notify format
823 has nothing to do with this message. It ALWAYS seems to be
826 infoPtr : listview struct
827 notificationCode : *Unicode* notification code
828 pdi : dispinfo structure (can be unicode or ansi)
829 isW : TRUE if dispinfo is Unicode
831 static BOOL notify_dispinfoT(LISTVIEW_INFO *infoPtr, INT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
833 BOOL bResult = FALSE;
834 BOOL convertToAnsi = FALSE;
835 INT cchTempBufMax = 0, savCchTextMax = 0;
836 LPWSTR pszTempBuf = NULL, savPszText = NULL;
838 if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
843 if (notificationCode != LVN_GETDISPINFOW)
845 cchTempBufMax = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText,
846 -1, NULL, 0, NULL, NULL);
850 cchTempBufMax = pdi->item.cchTextMax;
851 *pdi->item.pszText = 0; /* make sure we don't process garbage */
854 pszTempBuf = HeapAlloc(GetProcessHeap(), 0, sizeof(CHAR) *
856 if (!pszTempBuf) return FALSE;
858 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR)
859 pszTempBuf, cchTempBufMax, NULL, NULL);
861 savCchTextMax = pdi->item.cchTextMax;
862 savPszText = pdi->item.pszText;
863 pdi->item.pszText = pszTempBuf;
864 pdi->item.cchTextMax = cchTempBufMax;
867 TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat !=
870 bResult = notify_hdr(infoPtr, get_ansi_notification(notificationCode),
875 MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
876 savPszText, savCchTextMax);
877 pdi->item.pszText = savPszText; /* restores our buffer */
878 pdi->item.cchTextMax = savCchTextMax;
879 HeapFree(GetProcessHeap(), 0, pszTempBuf);
884 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, LISTVIEW_INFO *infoPtr, HDC hdc,
885 const RECT *rcBounds, const LVITEMW *lplvItem)
887 ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
888 lpnmlvcd->nmcd.hdc = hdc;
889 lpnmlvcd->nmcd.rc = *rcBounds;
890 lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
891 lpnmlvcd->clrText = infoPtr->clrText;
892 if (!lplvItem) return;
893 lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
894 lpnmlvcd->iSubItem = lplvItem->iSubItem;
895 if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
896 if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
897 if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
898 lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
901 static inline DWORD notify_customdraw (LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
903 BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
906 lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
907 if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
908 if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
909 if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
910 result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
911 if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
915 static void prepaint_setup (LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd)
917 /* apprently, for selected items, we have to override the returned values */
918 if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
922 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
923 lpnmlvcd->clrText = comctl32_color.clrHighlightText;
925 else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
927 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
928 lpnmlvcd->clrText = comctl32_color.clrBtnText;
932 /* Set the text attributes */
933 if (lpnmlvcd->clrTextBk != CLR_NONE)
935 SetBkMode(hdc, OPAQUE);
936 if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
937 SetBkColor(hdc, infoPtr->clrTextBkDefault);
939 SetBkColor(hdc,lpnmlvcd->clrTextBk);
942 SetBkMode(hdc, TRANSPARENT);
943 SetTextColor(hdc, lpnmlvcd->clrText);
946 static inline DWORD notify_postpaint (LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
948 return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
951 /******** Item iterator functions **********************************/
953 static RANGES ranges_create(int count);
954 static void ranges_destroy(RANGES ranges);
955 static BOOL ranges_add(RANGES ranges, RANGE range);
956 static BOOL ranges_del(RANGES ranges, RANGE range);
957 static void ranges_dump(RANGES ranges);
959 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
961 RANGE range = { nItem, nItem + 1 };
963 return ranges_add(ranges, range);
966 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
968 RANGE range = { nItem, nItem + 1 };
970 return ranges_del(ranges, range);
974 * ITERATOR DOCUMENTATION
976 * The iterator functions allow for easy, and convenient iteration
977 * over items of iterest in the list. Typically, you create a
978 * iterator, use it, and destroy it, as such:
981 * iterator_xxxitems(&i, ...);
982 * while (iterator_{prev,next}(&i)
984 * //code which uses i.nItem
986 * iterator_destroy(&i);
988 * where xxx is either: framed, or visible.
989 * Note that it is important that the code destroys the iterator
990 * after it's done with it, as the creation of the iterator may
991 * allocate memory, which thus needs to be freed.
993 * You can iterate both forwards, and backwards through the list,
994 * by using iterator_next or iterator_prev respectively.
996 * Lower numbered items are draw on top of higher number items in
997 * LVS_ICON, and LVS_SMALLICON (which are the only modes where
998 * items may overlap). So, to test items, you should use
1000 * which lists the items top to bottom (in Z-order).
1001 * For drawing items, you should use
1003 * which lists the items bottom to top (in Z-order).
1004 * If you keep iterating over the items after the end-of-items
1005 * marker (-1) is returned, the iterator will start from the
1006 * beginning. Typically, you don't need to test for -1,
1007 * because iterator_{next,prev} will return TRUE if more items
1008 * are to be iterated over, or FALSE otherwise.
1010 * Note: the iterator is defined to be bidirectional. That is,
1011 * any number of prev followed by any number of next, or
1012 * five versa, should leave the iterator at the same item:
1013 * prev * n, next * n = next * n, prev * n
1015 * The iterator has a notion of a out-of-order, special item,
1016 * which sits at the start of the list. This is used in
1017 * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1018 * which needs to be first, as it may overlap other items.
1020 * The code is a bit messy because we have:
1021 * - a special item to deal with
1022 * - simple range, or composite range
1024 * If you find bugs, or want to add features, please make sure you
1025 * always check/modify *both* iterator_prev, and iterator_next.
1029 * This function iterates through the items in increasing order,
1030 * but prefixed by the special item, then -1. That is:
1031 * special, 1, 2, 3, ..., n, -1.
1032 * Each item is listed only once.
1034 static inline BOOL iterator_next(ITERATOR* i)
1038 i->nItem = i->nSpecial;
1039 if (i->nItem != -1) return TRUE;
1041 if (i->nItem == i->nSpecial)
1043 if (i->ranges) i->index = 0;
1049 if (i->nItem == i->nSpecial) i->nItem++;
1050 if (i->nItem < i->range.upper) return TRUE;
1055 if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1056 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1059 else if (i->nItem >= i->range.upper) goto end;
1061 i->nItem = i->range.lower;
1062 if (i->nItem >= 0) goto testitem;
1069 * This function iterates through the items in decreasing order,
1070 * followed by the special item, then -1. That is:
1071 * n, n-1, ..., 3, 2, 1, special, -1.
1072 * Each item is listed only once.
1074 static inline BOOL iterator_prev(ITERATOR* i)
1081 if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1084 if (i->nItem == i->nSpecial)
1092 if (i->nItem == i->nSpecial) i->nItem--;
1093 if (i->nItem >= i->range.lower) return TRUE;
1099 i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1102 else if (!start && i->nItem < i->range.lower) goto end;
1104 i->nItem = i->range.upper;
1105 if (i->nItem > 0) goto testitem;
1107 return (i->nItem = i->nSpecial) != -1;
1110 static RANGE iterator_range(ITERATOR* i)
1114 if (!i->ranges) return i->range;
1116 if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1118 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1119 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1121 else range.lower = range.upper = 0;
1127 * Releases resources associated with this ierator.
1129 static inline void iterator_destroy(ITERATOR* i)
1131 ranges_destroy(i->ranges);
1135 * Create an empty iterator.
1137 static inline BOOL iterator_empty(ITERATOR* i)
1139 ZeroMemory(i, sizeof(*i));
1140 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1145 * Create an iterator over a range.
1147 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1155 * Create an iterator over a bunch of ranges.
1156 * Please note that the iterator will take ownership of the ranges,
1157 * and will free them upon destruction.
1159 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1167 * Creates an iterator over the items which intersect lprc.
1169 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1171 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1172 RECT frame = *lprc, rcItem, rcTemp;
1175 /* in case we fail, we want to return an empty iterator */
1176 if (!iterator_empty(i)) return FALSE;
1178 LISTVIEW_GetOrigin(infoPtr, &Origin);
1180 TRACE("(lprc=%s)\n", debugrect(lprc));
1181 OffsetRect(&frame, -Origin.x, -Origin.y);
1183 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1187 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1189 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1190 if (IntersectRect(&rcTemp, &rcItem, lprc))
1191 i->nSpecial = infoPtr->nFocusedItem;
1193 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1194 /* to do better here, we need to have PosX, and PosY sorted */
1195 TRACE("building icon ranges:\n");
1196 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1198 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1199 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1200 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1201 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1202 if (IntersectRect(&rcTemp, &rcItem, &frame))
1203 ranges_additem(i->ranges, nItem);
1207 else if (uView == LVS_REPORT)
1211 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1212 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1214 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1215 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1216 if (range.upper <= range.lower) return TRUE;
1217 if (!iterator_rangeitems(i, range)) return FALSE;
1218 TRACE(" report=%s\n", debugrange(&i->range));
1222 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1223 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1224 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1225 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1226 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1227 INT lower = nFirstCol * nPerCol + nFirstRow;
1231 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1232 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1234 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1236 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1237 TRACE("building list ranges:\n");
1238 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1240 item_range.lower = nCol * nPerCol + nFirstRow;
1241 if(item_range.lower >= infoPtr->nItemCount) break;
1242 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1243 TRACE(" list=%s\n", debugrange(&item_range));
1244 ranges_add(i->ranges, item_range);
1252 * Creates an iterator over the items which intersect the visible region of hdc.
1254 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1256 POINT Origin, Position;
1257 RECT rcItem, rcClip;
1260 rgntype = GetClipBox(hdc, &rcClip);
1261 if (rgntype == NULLREGION) return iterator_empty(i);
1262 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1263 if (rgntype == SIMPLEREGION) return TRUE;
1265 /* first deal with the special item */
1266 if (i->nSpecial != -1)
1268 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1269 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1272 /* if we can't deal with the region, we'll just go with the simple range */
1273 LISTVIEW_GetOrigin(infoPtr, &Origin);
1274 TRACE("building visible range:\n");
1275 if (!i->ranges && i->range.lower < i->range.upper)
1277 if (!(i->ranges = ranges_create(50))) return TRUE;
1278 if (!ranges_add(i->ranges, i->range))
1280 ranges_destroy(i->ranges);
1286 /* now delete the invisible items from the list */
1287 while(iterator_next(i))
1289 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1290 rcItem.left = Position.x + Origin.x;
1291 rcItem.top = Position.y + Origin.y;
1292 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1293 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1294 if (!RectVisible(hdc, &rcItem))
1295 ranges_delitem(i->ranges, i->nItem);
1297 /* the iterator should restart on the next iterator_next */
1303 /******** Misc helper functions ************************************/
1305 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1306 WPARAM wParam, LPARAM lParam, BOOL isW)
1308 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1309 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1312 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1314 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1316 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1317 (uView == LVS_ICON || uView == LVS_SMALLICON);
1320 /******** Internal API functions ************************************/
1322 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1324 static COLUMN_INFO mainItem;
1326 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1327 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1328 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1331 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1333 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1336 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1338 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1341 /* Listview invalidation functions: use _only_ these functions to invalidate */
1343 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1345 return infoPtr->bRedraw;
1348 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1350 if(!is_redrawing(infoPtr)) return;
1351 TRACE(" invalidating rect=%s\n", debugrect(rect));
1352 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1355 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1359 if(!is_redrawing(infoPtr)) return;
1360 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1361 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1364 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1366 POINT Origin, Position;
1369 if(!is_redrawing(infoPtr)) return;
1370 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1371 LISTVIEW_GetOrigin(infoPtr, &Origin);
1372 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1373 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1375 rcBox.bottom = infoPtr->nItemHeight;
1376 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1377 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1380 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1382 LISTVIEW_InvalidateRect(infoPtr, NULL);
1385 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1389 if(!is_redrawing(infoPtr)) return;
1390 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1391 rcCol.top = infoPtr->rcList.top;
1392 rcCol.bottom = infoPtr->rcList.bottom;
1393 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1398 * Retrieves the number of items that can fit vertically in the client area.
1401 * [I] infoPtr : valid pointer to the listview structure
1404 * Number of items per row.
1406 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1408 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1410 return max(nListWidth/infoPtr->nItemWidth, 1);
1415 * Retrieves the number of items that can fit horizontally in the client
1419 * [I] infoPtr : valid pointer to the listview structure
1422 * Number of items per column.
1424 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1426 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1428 return max(nListHeight / infoPtr->nItemHeight, 1);
1432 /*************************************************************************
1433 * LISTVIEW_ProcessLetterKeys
1435 * Processes keyboard messages generated by pressing the letter keys
1437 * What this does is perform a case insensitive search from the
1438 * current position with the following quirks:
1439 * - If two chars or more are pressed in quick succession we search
1440 * for the corresponding string (e.g. 'abc').
1441 * - If there is a delay we wipe away the current search string and
1442 * restart with just that char.
1443 * - If the user keeps pressing the same character, whether slowly or
1444 * fast, so that the search string is entirely composed of this
1445 * character ('aaaaa' for instance), then we search for first item
1446 * that starting with that character.
1447 * - If the user types the above character in quick succession, then
1448 * we must also search for the corresponding string ('aaaaa'), and
1449 * go to that string if there is a match.
1452 * [I] hwnd : handle to the window
1453 * [I] charCode : the character code, the actual character
1454 * [I] keyData : key data
1462 * - The current implementation has a list of characters it will
1463 * accept and it ignores averything else. In particular it will
1464 * ignore accentuated characters which seems to match what
1465 * Windows does. But I'm not sure it makes sense to follow
1467 * - We don't sound a beep when the search fails.
1471 * TREEVIEW_ProcessLetterKeys
1473 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1478 WCHAR buffer[MAX_PATH];
1479 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1481 /* simple parameter checking */
1482 if (!charCode || !keyData) return 0;
1484 /* only allow the valid WM_CHARs through */
1485 if (!isalnum(charCode) &&
1486 charCode != '.' && charCode != '`' && charCode != '!' &&
1487 charCode != '@' && charCode != '#' && charCode != '$' &&
1488 charCode != '%' && charCode != '^' && charCode != '&' &&
1489 charCode != '*' && charCode != '(' && charCode != ')' &&
1490 charCode != '-' && charCode != '_' && charCode != '+' &&
1491 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1492 charCode != '}' && charCode != '[' && charCode != '{' &&
1493 charCode != '/' && charCode != '?' && charCode != '>' &&
1494 charCode != '<' && charCode != ',' && charCode != '~')
1497 /* if there's one item or less, there is no where to go */
1498 if (infoPtr->nItemCount <= 1) return 0;
1500 /* update the search parameters */
1501 infoPtr->lastKeyPressTimestamp = GetTickCount();
1502 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1503 if (infoPtr->nSearchParamLength < MAX_PATH)
1504 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1505 if (infoPtr->charCode != charCode)
1506 infoPtr->charCode = charCode = 0;
1508 infoPtr->charCode=charCode;
1509 infoPtr->szSearchParam[0]=charCode;
1510 infoPtr->nSearchParamLength=1;
1511 /* Redundant with the 1 char string */
1515 /* and search from the current position */
1517 if (infoPtr->nFocusedItem >= 0) {
1518 endidx=infoPtr->nFocusedItem;
1520 /* if looking for single character match,
1521 * then we must always move forward
1523 if (infoPtr->nSearchParamLength == 1)
1526 endidx=infoPtr->nItemCount;
1530 if (idx == infoPtr->nItemCount) {
1531 if (endidx == infoPtr->nItemCount || endidx == 0)
1537 item.mask = LVIF_TEXT;
1540 item.pszText = buffer;
1541 item.cchTextMax = MAX_PATH;
1542 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1544 /* check for a match */
1545 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1548 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1549 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1550 /* This would work but we must keep looking for a longer match */
1554 } while (idx != endidx);
1557 LISTVIEW_KeySelection(infoPtr, nItem);
1562 /*************************************************************************
1563 * LISTVIEW_UpdateHeaderSize [Internal]
1565 * Function to resize the header control
1568 * [I] hwnd : handle to a window
1569 * [I] nNewScrollPos : scroll pos to set
1574 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1579 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1581 GetWindowRect(infoPtr->hwndHeader, &winRect);
1582 point[0].x = winRect.left;
1583 point[0].y = winRect.top;
1584 point[1].x = winRect.right;
1585 point[1].y = winRect.bottom;
1587 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1588 point[0].x = -nNewScrollPos;
1589 point[1].x += nNewScrollPos;
1591 SetWindowPos(infoPtr->hwndHeader,0,
1592 point[0].x,point[0].y,point[1].x,point[1].y,
1593 SWP_NOZORDER | SWP_NOACTIVATE);
1598 * Update the scrollbars. This functions should be called whenever
1599 * the content, size or view changes.
1602 * [I] infoPtr : valid pointer to the listview structure
1607 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1609 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1610 SCROLLINFO horzInfo, vertInfo;
1612 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1614 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1615 horzInfo.cbSize = sizeof(SCROLLINFO);
1616 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1618 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1619 if (uView == LVS_LIST)
1621 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1622 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1624 /* scroll by at least one column per page */
1625 if(horzInfo.nPage < infoPtr->nItemWidth)
1626 horzInfo.nPage = infoPtr->nItemWidth;
1628 horzInfo.nPage /= infoPtr->nItemWidth;
1630 else if (uView == LVS_REPORT)
1632 horzInfo.nMax = infoPtr->nItemWidth;
1634 else /* LVS_ICON, or LVS_SMALLICON */
1638 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1641 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1642 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1643 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1644 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1646 /* Setting the horizontal scroll can change the listview size
1647 * (and potentially everything else) so we need to recompute
1648 * everything again for the vertical scroll
1651 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1652 vertInfo.cbSize = sizeof(SCROLLINFO);
1653 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1655 if (uView == LVS_REPORT)
1657 vertInfo.nMax = infoPtr->nItemCount;
1659 /* scroll by at least one page */
1660 if(vertInfo.nPage < infoPtr->nItemHeight)
1661 vertInfo.nPage = infoPtr->nItemHeight;
1663 vertInfo.nPage /= infoPtr->nItemHeight;
1665 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1669 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1672 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1673 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1674 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1675 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1677 /* Update the Header Control */
1678 if (uView == LVS_REPORT)
1680 horzInfo.fMask = SIF_POS;
1681 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1682 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1689 * Shows/hides the focus rectangle.
1692 * [I] infoPtr : valid pointer to the listview structure
1693 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1698 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1700 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1703 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1705 if (infoPtr->nFocusedItem < 0) return;
1707 /* we need some gymnastics in ICON mode to handle large items */
1708 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1712 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1713 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1715 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1720 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1722 /* for some reason, owner draw should work only in report mode */
1723 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1728 item.iItem = infoPtr->nFocusedItem;
1730 item.mask = LVIF_PARAM;
1731 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1733 ZeroMemory(&dis, sizeof(dis));
1734 dis.CtlType = ODT_LISTVIEW;
1735 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1736 dis.itemID = item.iItem;
1737 dis.itemAction = ODA_FOCUS;
1738 if (fShow) dis.itemState |= ODS_FOCUS;
1739 dis.hwndItem = infoPtr->hwndSelf;
1741 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1742 dis.itemData = item.lParam;
1744 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1748 DrawFocusRect(hdc, &infoPtr->rcFocus);
1751 ReleaseDC(infoPtr->hwndSelf, hdc);
1755 * Invalidates all visible selected items.
1757 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1761 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1762 while(iterator_next(&i))
1764 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1765 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1767 iterator_destroy(&i);
1772 * DESCRIPTION: [INTERNAL]
1773 * Computes an item's (left,top) corner, relative to rcView.
1774 * That is, the position has NOT been made relative to the Origin.
1775 * This is deliberate, to avoid computing the Origin over, and
1776 * over again, when this function is call in a loop. Instead,
1777 * one ca factor the computation of the Origin before the loop,
1778 * and offset the value retured by this function, on every iteration.
1781 * [I] infoPtr : valid pointer to the listview structure
1782 * [I] nItem : item number
1783 * [O] lpptOrig : item top, left corner
1788 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1790 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1792 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1794 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1796 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1797 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1799 else if (uView == LVS_LIST)
1801 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1802 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1803 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1805 else /* LVS_REPORT */
1807 lpptPosition->x = 0;
1808 lpptPosition->y = nItem * infoPtr->nItemHeight;
1813 * DESCRIPTION: [INTERNAL]
1814 * Compute the rectangles of an item. This is to localize all
1815 * the computations in one place. If you are not interested in some
1816 * of these values, simply pass in a NULL -- the fucntion is smart
1817 * enough to compute only what's necessary. The function computes
1818 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1819 * one, the BOX rectangle. This rectangle is very cheap to compute,
1820 * and is guaranteed to contain all the other rectangles. Computing
1821 * the ICON rect is also cheap, but all the others are potentaily
1822 * expensive. This gives an easy and effective optimization when
1823 * searching (like point inclusion, or rectangle intersection):
1824 * first test against the BOX, and if TRUE, test agains the desired
1826 * If the function does not have all the necessary information
1827 * to computed the requested rectangles, will crash with a
1828 * failed assertion. This is done so we catch all programming
1829 * errors, given that the function is called only from our code.
1831 * We have the following 'special' meanings for a few fields:
1832 * * If LVIS_FOCUSED is set, we assume the item has the focus
1833 * This is important in ICON mode, where it might get a larger
1834 * then usual rectange
1836 * Please note that subitem support works only in REPORT mode.
1839 * [I] infoPtr : valid pointer to the listview structure
1840 * [I] lpLVItem : item to compute the measures for
1841 * [O] lprcBox : ptr to Box rectangle
1842 * The internal LVIR_BOX rectangle
1843 * [0] lprcState : ptr to State icon rectangle
1844 * The internal LVIR_STATE rectangle
1845 * [O] lprcIcon : ptr to Icon rectangle
1846 * Same as LVM_GETITEMRECT with LVIR_ICON
1847 * [O] lprcLabel : ptr to Label rectangle
1848 * Same as LVM_GETITEMRECT with LVIR_LABEL
1853 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1854 LPRECT lprcBox, LPRECT lprcState,
1855 LPRECT lprcIcon, LPRECT lprcLabel)
1857 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1858 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1859 RECT Box, State, Icon, Label;
1860 COLUMN_INFO *lpColumnInfo = NULL;
1862 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1864 /* Be smart and try to figure out the minimum we have to do */
1865 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1866 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1868 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1869 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1871 if (lprcLabel) doLabel = TRUE;
1872 if (doLabel || lprcIcon) doIcon = TRUE;
1873 if (doIcon || lprcState) doState = TRUE;
1875 /************************************************************/
1876 /* compute the box rectangle (it should be cheap to do) */
1877 /************************************************************/
1878 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1879 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1881 if (lpLVItem->iSubItem)
1883 Box = lpColumnInfo->rcHeader;
1888 Box.right = infoPtr->nItemWidth;
1891 Box.bottom = infoPtr->nItemHeight;
1893 /************************************************************/
1894 /* compute STATEICON bounding box */
1895 /************************************************************/
1898 if (uView == LVS_ICON)
1900 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1901 if (infoPtr->himlNormal)
1902 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1903 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1907 /* we need the ident in report mode, if we don't have it, we fail */
1908 State.left = Box.left;
1909 if (uView == LVS_REPORT)
1911 if (lpLVItem->iSubItem == 0)
1913 State.left += REPORT_MARGINX;
1914 assert(lpLVItem->mask & LVIF_INDENT);
1915 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1918 State.top = Box.top;
1920 State.right = State.left;
1921 State.bottom = State.top;
1922 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1924 State.right += infoPtr->iconStateSize.cx;
1925 State.bottom += infoPtr->iconStateSize.cy;
1927 if (lprcState) *lprcState = State;
1928 TRACE(" - state=%s\n", debugrect(&State));
1931 /************************************************************/
1932 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1933 /************************************************************/
1936 if (uView == LVS_ICON)
1938 Icon.left = Box.left;
1939 if (infoPtr->himlNormal)
1940 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1941 Icon.top = Box.top + ICON_TOP_PADDING;
1942 Icon.right = Icon.left;
1943 Icon.bottom = Icon.top;
1944 if (infoPtr->himlNormal)
1946 Icon.right += infoPtr->iconSize.cx;
1947 Icon.bottom += infoPtr->iconSize.cy;
1950 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1952 Icon.left = State.right;
1954 Icon.right = Icon.left;
1955 if (infoPtr->himlSmall &&
1956 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1957 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1958 Icon.right += infoPtr->iconSize.cx;
1959 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1961 if(lprcIcon) *lprcIcon = Icon;
1962 TRACE(" - icon=%s\n", debugrect(&Icon));
1965 /************************************************************/
1966 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1967 /************************************************************/
1970 SIZE labelSize = { 0, 0 };
1972 /* calculate how far to the right can the label strech */
1973 Label.right = Box.right;
1974 if (uView == LVS_REPORT)
1976 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1979 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1981 labelSize.cx = infoPtr->nItemWidth;
1982 labelSize.cy = infoPtr->nItemHeight;
1986 /* we need the text in non owner draw mode */
1987 assert(lpLVItem->mask & LVIF_TEXT);
1988 if (is_textT(lpLVItem->pszText, TRUE))
1990 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1991 HDC hdc = GetDC(infoPtr->hwndSelf);
1992 HFONT hOldFont = SelectObject(hdc, hFont);
1996 /* compute rough rectangle where the label will go */
1997 SetRectEmpty(&rcText);
1998 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
1999 rcText.bottom = infoPtr->nItemHeight;
2000 if (uView == LVS_ICON)
2001 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2003 /* now figure out the flags */
2004 if (uView == LVS_ICON)
2005 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2007 uFormat = LV_SL_DT_FLAGS;
2009 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2011 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2012 labelSize.cy = rcText.bottom - rcText.top;
2014 SelectObject(hdc, hOldFont);
2015 ReleaseDC(infoPtr->hwndSelf, hdc);
2019 if (uView == LVS_ICON)
2021 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2022 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2023 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2024 Label.right = Label.left + labelSize.cx;
2025 Label.bottom = Label.top + infoPtr->nItemHeight;
2026 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2028 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2029 labelSize.cy /= infoPtr->ntmHeight;
2030 labelSize.cy = max(labelSize.cy, 1);
2031 labelSize.cy *= infoPtr->ntmHeight;
2033 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2035 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2037 Label.left = Icon.right;
2038 Label.top = Box.top;
2039 Label.right = min(Label.left + labelSize.cx, Label.right);
2040 Label.bottom = Label.top + infoPtr->nItemHeight;
2043 if (lprcLabel) *lprcLabel = Label;
2044 TRACE(" - label=%s\n", debugrect(&Label));
2047 /* Fix the Box if necessary */
2050 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2051 else *lprcBox = Box;
2053 TRACE(" - box=%s\n", debugrect(&Box));
2057 * DESCRIPTION: [INTERNAL]
2060 * [I] infoPtr : valid pointer to the listview structure
2061 * [I] nItem : item number
2062 * [O] lprcBox : ptr to Box rectangle
2067 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2069 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2070 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2071 POINT Position, Origin;
2074 LISTVIEW_GetOrigin(infoPtr, &Origin);
2075 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2077 /* Be smart and try to figure out the minimum we have to do */
2079 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2080 lvItem.mask |= LVIF_TEXT;
2081 lvItem.iItem = nItem;
2082 lvItem.iSubItem = 0;
2083 lvItem.pszText = szDispText;
2084 lvItem.cchTextMax = DISP_TEXT_SIZE;
2085 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2086 if (uView == LVS_ICON)
2088 lvItem.mask |= LVIF_STATE;
2089 lvItem.stateMask = LVIS_FOCUSED;
2090 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2092 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2094 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2100 * Returns the current icon position, and advances it along the top.
2101 * The returned position is not offset by Origin.
2104 * [I] infoPtr : valid pointer to the listview structure
2105 * [O] lpPos : will get the current icon position
2110 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2112 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2114 *lpPos = infoPtr->currIconPos;
2116 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2117 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2119 infoPtr->currIconPos.x = 0;
2120 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2126 * Returns the current icon position, and advances it down the left edge.
2127 * The returned position is not offset by Origin.
2130 * [I] infoPtr : valid pointer to the listview structure
2131 * [O] lpPos : will get the current icon position
2136 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2138 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2140 *lpPos = infoPtr->currIconPos;
2142 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2143 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2145 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2146 infoPtr->currIconPos.y = 0;
2152 * Moves an icon to the specified position.
2153 * It takes care of invalidating the item, etc.
2156 * [I] infoPtr : valid pointer to the listview structure
2157 * [I] nItem : the item to move
2158 * [I] lpPos : the new icon position
2159 * [I] isNew : flags the item as being new
2165 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2171 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2172 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2174 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2175 LISTVIEW_InvalidateItem(infoPtr, nItem);
2178 /* Allocating a POINTER for every item is too resource intensive,
2179 * so we'll keep the (x,y) in different arrays */
2180 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2181 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2183 LISTVIEW_InvalidateItem(infoPtr, nItem);
2190 * Arranges listview items in icon display mode.
2193 * [I] infoPtr : valid pointer to the listview structure
2194 * [I] nAlignCode : alignment code
2200 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2202 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2203 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2207 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2209 TRACE("nAlignCode=%d\n", nAlignCode);
2211 if (nAlignCode == LVA_DEFAULT)
2213 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2214 else nAlignCode = LVA_ALIGNTOP;
2219 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2220 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2221 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2222 default: return FALSE;
2225 infoPtr->bAutoarrange = TRUE;
2226 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2227 for (i = 0; i < infoPtr->nItemCount; i++)
2229 next_pos(infoPtr, &pos);
2230 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2238 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2241 * [I] infoPtr : valid pointer to the listview structure
2242 * [O] lprcView : bounding rectangle
2248 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2252 SetRectEmpty(lprcView);
2254 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2258 for (i = 0; i < infoPtr->nItemCount; i++)
2260 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2261 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2262 lprcView->right = max(lprcView->right, x);
2263 lprcView->bottom = max(lprcView->bottom, y);
2265 if (infoPtr->nItemCount > 0)
2267 lprcView->right += infoPtr->nItemWidth;
2268 lprcView->bottom += infoPtr->nItemHeight;
2273 y = LISTVIEW_GetCountPerColumn(infoPtr);
2274 x = infoPtr->nItemCount / y;
2275 if (infoPtr->nItemCount % y) x++;
2276 lprcView->right = x * infoPtr->nItemWidth;
2277 lprcView->bottom = y * infoPtr->nItemHeight;
2281 lprcView->right = infoPtr->nItemWidth;
2282 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2289 * Retrieves the bounding rectangle of all the items.
2292 * [I] infoPtr : valid pointer to the listview structure
2293 * [O] lprcView : bounding rectangle
2299 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2303 TRACE("(lprcView=%p)\n", lprcView);
2305 if (!lprcView) return FALSE;
2307 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2308 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2309 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2311 TRACE("lprcView=%s\n", debugrect(lprcView));
2318 * Retrieves the subitem pointer associated with the subitem index.
2321 * [I] hdpaSubItems : DPA handle for a specific item
2322 * [I] nSubItem : index of subitem
2325 * SUCCESS : subitem pointer
2328 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2330 SUBITEM_INFO *lpSubItem;
2333 /* we should binary search here if need be */
2334 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2336 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2337 if (lpSubItem->iSubItem == nSubItem)
2347 * Caclulates the desired item width.
2350 * [I] infoPtr : valid pointer to the listview structure
2353 * The desired item width.
2355 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2357 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2360 TRACE("uView=%d\n", uView);
2362 if (uView == LVS_ICON)
2363 nItemWidth = infoPtr->iconSpacing.cx;
2364 else if (uView == LVS_REPORT)
2368 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2370 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2371 nItemWidth = rcHeader.right;
2374 else /* LVS_SMALLICON, or LVS_LIST */
2378 for (i = 0; i < infoPtr->nItemCount; i++)
2379 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2381 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2382 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2384 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2387 return max(nItemWidth, 1);
2392 * Caclulates the desired item height.
2395 * [I] infoPtr : valid pointer to the listview structure
2398 * The desired item height.
2400 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2402 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2405 TRACE("uView=%d\n", uView);
2407 if (uView == LVS_ICON)
2408 nItemHeight = infoPtr->iconSpacing.cy;
2411 nItemHeight = infoPtr->ntmHeight;
2412 if (infoPtr->himlState)
2413 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2414 if (infoPtr->himlSmall)
2415 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2416 if (infoPtr->himlState || infoPtr->himlSmall)
2417 nItemHeight += HEIGHT_PADDING;
2420 return max(nItemHeight, 1);
2425 * Updates the width, and height of an item.
2428 * [I] infoPtr : valid pointer to the listview structure
2433 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2435 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2436 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2442 * Retrieves and saves important text metrics info for the current
2446 * [I] infoPtr : valid pointer to the listview structure
2449 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2451 HDC hdc = GetDC(infoPtr->hwndSelf);
2452 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2453 HFONT hOldFont = SelectObject(hdc, hFont);
2456 if (GetTextMetricsW(hdc, &tm))
2458 infoPtr->ntmHeight = tm.tmHeight;
2459 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2461 SelectObject(hdc, hOldFont);
2462 ReleaseDC(infoPtr->hwndSelf, hdc);
2464 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2469 * A compare function for ranges
2472 * [I] range1 : pointer to range 1;
2473 * [I] range2 : pointer to range 2;
2477 * > 0 : if range 1 > range 2
2478 * < 0 : if range 2 > range 1
2479 * = 0 : if range intersects range 2
2481 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2485 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2487 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2492 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2498 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2500 #define ranges_check(ranges, desc) do { } while(0)
2503 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2508 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2510 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2511 ranges_dump(ranges);
2512 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2513 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2514 assert (prev->lower >= 0 && prev->lower < prev->upper);
2515 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2517 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2518 assert (prev->upper <= curr->lower);
2519 assert (curr->lower < curr->upper);
2522 TRACE("--- Done checking---\n");
2525 static RANGES ranges_create(int count)
2527 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2528 if (!ranges) return NULL;
2529 ranges->hdpa = DPA_Create(count);
2530 if (ranges->hdpa) return ranges;
2535 static void ranges_clear(RANGES ranges)
2539 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2540 Free(DPA_GetPtr(ranges->hdpa, i));
2541 DPA_DeleteAllPtrs(ranges->hdpa);
2545 static void ranges_destroy(RANGES ranges)
2547 if (!ranges) return;
2548 ranges_clear(ranges);
2549 DPA_Destroy(ranges->hdpa);
2553 static RANGES ranges_clone(RANGES ranges)
2558 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2560 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2562 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2563 if (!newrng) goto fail;
2564 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2565 DPA_SetPtr(clone->hdpa, i, newrng);
2570 TRACE ("clone failed\n");
2571 ranges_destroy(clone);
2575 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2579 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2580 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2585 static void ranges_dump(RANGES ranges)
2589 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2590 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2593 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2595 RANGE srchrng = { nItem, nItem + 1 };
2597 TRACE("(nItem=%d)\n", nItem);
2598 ranges_check(ranges, "before contain");
2599 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2602 static INT ranges_itemcount(RANGES ranges)
2606 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2608 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2609 count += sel->upper - sel->lower;
2615 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2617 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2620 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2621 if (index == -1) return TRUE;
2623 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2625 chkrng = DPA_GetPtr(ranges->hdpa, index);
2626 if (chkrng->lower >= nItem)
2627 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2628 if (chkrng->upper > nItem)
2629 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2634 static BOOL ranges_add(RANGES ranges, RANGE range)
2639 TRACE("(%s)\n", debugrange(&range));
2640 ranges_check(ranges, "before add");
2642 /* try find overlapping regions first */
2643 srchrgn.lower = range.lower - 1;
2644 srchrgn.upper = range.upper + 1;
2645 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2651 TRACE("Adding new range\n");
2653 /* create the brand new range to insert */
2654 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2655 if(!newrgn) goto fail;
2658 /* figure out where to insert it */
2659 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2660 TRACE("index=%d\n", index);
2661 if (index == -1) index = 0;
2663 /* and get it over with */
2664 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2672 RANGE *chkrgn, *mrgrgn;
2673 INT fromindex, mergeindex;
2675 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2676 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2678 chkrgn->lower = min(range.lower, chkrgn->lower);
2679 chkrgn->upper = max(range.upper, chkrgn->upper);
2681 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2683 /* merge now common anges */
2685 srchrgn.lower = chkrgn->lower - 1;
2686 srchrgn.upper = chkrgn->upper + 1;
2690 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2691 if (mergeindex == -1) break;
2692 if (mergeindex == index)
2694 fromindex = index + 1;
2698 TRACE("Merge with index %i\n", mergeindex);
2700 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2701 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2702 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2704 DPA_DeletePtr(ranges->hdpa, mergeindex);
2705 if (mergeindex < index) index --;
2709 ranges_check(ranges, "after add");
2713 ranges_check(ranges, "failed add");
2717 static BOOL ranges_del(RANGES ranges, RANGE range)
2722 TRACE("(%s)\n", debugrange(&range));
2723 ranges_check(ranges, "before del");
2725 /* we don't use DPAS_SORTED here, since we need *
2726 * to find the first overlapping range */
2727 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2730 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2732 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2734 /* case 1: Same range */
2735 if ( (chkrgn->upper == range.upper) &&
2736 (chkrgn->lower == range.lower) )
2738 DPA_DeletePtr(ranges->hdpa, index);
2741 /* case 2: engulf */
2742 else if ( (chkrgn->upper <= range.upper) &&
2743 (chkrgn->lower >= range.lower) )
2745 DPA_DeletePtr(ranges->hdpa, index);
2747 /* case 3: overlap upper */
2748 else if ( (chkrgn->upper <= range.upper) &&
2749 (chkrgn->lower < range.lower) )
2751 chkrgn->upper = range.lower;
2753 /* case 4: overlap lower */
2754 else if ( (chkrgn->upper > range.upper) &&
2755 (chkrgn->lower >= range.lower) )
2757 chkrgn->lower = range.upper;
2760 /* case 5: fully internal */
2763 RANGE tmprgn = *chkrgn, *newrgn;
2765 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2766 newrgn->lower = chkrgn->lower;
2767 newrgn->upper = range.lower;
2768 chkrgn->lower = range.upper;
2769 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2778 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2781 ranges_check(ranges, "after del");
2785 ranges_check(ranges, "failed del");
2791 * Removes all selection ranges
2794 * [I] infoPtr : valid pointer to the listview structure
2795 * [I] toSkip : item range to skip removing the selection
2801 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2810 lvItem.stateMask = LVIS_SELECTED;
2812 /* need to clone the DPA because callbacks can change it */
2813 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2814 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2815 while(iterator_next(&i))
2816 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2817 /* note that the iterator destructor will free the cloned range */
2818 iterator_destroy(&i);
2823 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2827 if (!(toSkip = ranges_create(1))) return FALSE;
2828 if (nItem != -1) ranges_additem(toSkip, nItem);
2829 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2830 ranges_destroy(toSkip);
2834 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2836 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2841 * Retrieves the number of items that are marked as selected.
2844 * [I] infoPtr : valid pointer to the listview structure
2847 * Number of items selected.
2849 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2851 INT nSelectedCount = 0;
2853 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2856 for (i = 0; i < infoPtr->nItemCount; i++)
2858 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2863 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2865 TRACE("nSelectedCount=%d\n", nSelectedCount);
2866 return nSelectedCount;
2871 * Manages the item focus.
2874 * [I] infoPtr : valid pointer to the listview structure
2875 * [I] nItem : item index
2878 * TRUE : focused item changed
2879 * FALSE : focused item has NOT changed
2881 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2883 INT oldFocus = infoPtr->nFocusedItem;
2886 if (nItem == infoPtr->nFocusedItem) return FALSE;
2888 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2889 lvItem.stateMask = LVIS_FOCUSED;
2890 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2892 return oldFocus != infoPtr->nFocusedItem;
2895 /* Helper function for LISTVIEW_ShiftIndices *only* */
2896 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2898 if (nShiftItem < nItem) return nShiftItem;
2900 if (nShiftItem > nItem) return nShiftItem + direction;
2902 if (direction > 0) return nShiftItem + direction;
2904 return min(nShiftItem, infoPtr->nItemCount - 1);
2909 * Updates the various indices after an item has been inserted or deleted.
2912 * [I] infoPtr : valid pointer to the listview structure
2913 * [I] nItem : item index
2914 * [I] direction : Direction of shift, +1 or -1.
2919 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2924 /* temporarily disable change notification while shifting items */
2925 bOldChange = infoPtr->bDoChangeNotify;
2926 infoPtr->bDoChangeNotify = FALSE;
2928 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2930 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2932 assert(abs(direction) == 1);
2934 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2936 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2937 if (nNewFocus != infoPtr->nFocusedItem)
2938 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2940 /* But we are not supposed to modify nHotItem! */
2942 infoPtr->bDoChangeNotify = bOldChange;
2948 * Adds a block of selections.
2951 * [I] infoPtr : valid pointer to the listview structure
2952 * [I] nItem : item index
2957 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2959 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2960 INT nLast = max(infoPtr->nSelectionMark, nItem);
2964 if (nFirst == -1) nFirst = nItem;
2966 item.state = LVIS_SELECTED;
2967 item.stateMask = LVIS_SELECTED;
2969 /* FIXME: this is not correct LVS_OWNERDATA
2970 * setting the item states individually will generate
2971 * a LVN_ITEMCHANGED notification for each one. Instead,
2972 * we have to send a LVN_ODSTATECHANGED notification.
2973 * See MSDN documentation for LVN_ITEMCHANGED.
2975 for (i = nFirst; i <= nLast; i++)
2976 LISTVIEW_SetItemState(infoPtr,i,&item);
2982 * Sets a single group selection.
2985 * [I] infoPtr : valid pointer to the listview structure
2986 * [I] nItem : item index
2991 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2993 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2998 if (!(selection = ranges_create(100))) return;
3000 item.state = LVIS_SELECTED;
3001 item.stateMask = LVIS_SELECTED;
3003 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3005 if (infoPtr->nSelectionMark == -1)
3007 infoPtr->nSelectionMark = nItem;
3008 ranges_additem(selection, nItem);
3014 sel.lower = min(infoPtr->nSelectionMark, nItem);
3015 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3016 ranges_add(selection, sel);
3021 RECT rcItem, rcSel, rcSelMark;
3024 rcItem.left = LVIR_BOUNDS;
3025 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3026 rcSelMark.left = LVIR_BOUNDS;
3027 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3028 UnionRect(&rcSel, &rcItem, &rcSelMark);
3029 iterator_frameditems(&i, infoPtr, &rcSel);
3030 while(iterator_next(&i))
3032 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3033 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3035 iterator_destroy(&i);
3038 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3039 iterator_rangesitems(&i, selection);
3040 while(iterator_next(&i))
3041 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3042 /* this will also destroy the selection */
3043 iterator_destroy(&i);
3045 LISTVIEW_SetItemFocus(infoPtr, nItem);
3050 * Sets a single selection.
3053 * [I] infoPtr : valid pointer to the listview structure
3054 * [I] nItem : item index
3059 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3063 TRACE("nItem=%d\n", nItem);
3065 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3067 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3068 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3069 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3071 infoPtr->nSelectionMark = nItem;
3076 * Set selection(s) with keyboard.
3079 * [I] infoPtr : valid pointer to the listview structure
3080 * [I] nItem : item index
3083 * SUCCESS : TRUE (needs to be repainted)
3084 * FAILURE : FALSE (nothing has changed)
3086 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3088 /* FIXME: pass in the state */
3089 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3090 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3091 BOOL bResult = FALSE;
3093 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3095 if (infoPtr->dwStyle & LVS_SINGLESEL)
3098 LISTVIEW_SetSelection(infoPtr, nItem);
3105 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3109 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3114 LISTVIEW_SetSelection(infoPtr, nItem);
3117 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3120 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3127 * Called when the mouse is being actively tracked and has hovered for a specified
3131 * [I] infoPtr : valid pointer to the listview structure
3132 * [I] fwKeys : key indicator
3133 * [I] pts : mouse position
3136 * 0 if the message was processed, non-zero if there was an error
3139 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3140 * over the item for a certain period of time.
3143 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3145 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3146 /* FIXME: select the item!!! */
3147 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3154 * Called whenever WM_MOUSEMOVE is received.
3157 * [I] infoPtr : valid pointer to the listview structure
3158 * [I] fwKeys : key indicator
3159 * [I] pts : mouse position
3162 * 0 if the message is processed, non-zero if there was an error
3164 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3166 TRACKMOUSEEVENT trackinfo;
3168 /* see if we are supposed to be tracking mouse hovering */
3169 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3170 /* fill in the trackinfo struct */
3171 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3172 trackinfo.dwFlags = TME_QUERY;
3173 trackinfo.hwndTrack = infoPtr->hwndSelf;
3174 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3176 /* see if we are already tracking this hwnd */
3177 _TrackMouseEvent(&trackinfo);
3179 if(!(trackinfo.dwFlags & TME_HOVER)) {
3180 trackinfo.dwFlags = TME_HOVER;
3182 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3183 _TrackMouseEvent(&trackinfo);
3192 * Tests wheather the item is assignable to a list with style lStyle
3194 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3196 if ( (lpLVItem->mask & LVIF_TEXT) &&
3197 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3198 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3206 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3209 * [I] infoPtr : valid pointer to the listview structure
3210 * [I] lpLVItem : valid pointer to new item atttributes
3211 * [I] isNew : the item being set is being inserted
3212 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3213 * [O] bChanged : will be set to TRUE if the item really changed
3219 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3221 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3229 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3231 if (lpLVItem->mask == 0) return TRUE;
3233 if (infoPtr->dwStyle & LVS_OWNERDATA)
3235 /* a virtual listview we stores only selection and focus */
3236 if (lpLVItem->mask & ~LVIF_STATE)
3242 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3243 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3247 /* we need to get the lParam and state of the item */
3248 item.iItem = lpLVItem->iItem;
3249 item.iSubItem = lpLVItem->iSubItem;
3250 item.mask = LVIF_STATE | LVIF_PARAM;
3251 item.stateMask = ~0;
3254 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3256 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3257 /* determine what fields will change */
3258 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3259 uChanged |= LVIF_STATE;
3261 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3262 uChanged |= LVIF_IMAGE;
3264 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3265 uChanged |= LVIF_PARAM;
3267 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3268 uChanged |= LVIF_INDENT;
3270 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3271 uChanged |= LVIF_TEXT;
3273 TRACE("uChanged=0x%x\n", uChanged);
3274 if (!uChanged) return TRUE;
3277 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3278 nmlv.iItem = lpLVItem->iItem;
3279 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3280 nmlv.uOldState = item.state;
3281 nmlv.uChanged = uChanged;
3282 nmlv.lParam = item.lParam;
3284 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3285 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3287 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3288 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3291 /* copy information */
3292 if (lpLVItem->mask & LVIF_TEXT)
3293 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3295 if (lpLVItem->mask & LVIF_IMAGE)
3296 lpItem->hdr.iImage = lpLVItem->iImage;
3298 if (lpLVItem->mask & LVIF_PARAM)
3299 lpItem->lParam = lpLVItem->lParam;
3301 if (lpLVItem->mask & LVIF_INDENT)
3302 lpItem->iIndent = lpLVItem->iIndent;
3304 if (uChanged & LVIF_STATE)
3306 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3308 lpItem->state &= ~lpLVItem->stateMask;
3309 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3311 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3313 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3314 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3316 else if (lpLVItem->stateMask & LVIS_SELECTED)
3317 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3319 /* if we are asked to change focus, and we manage it, do it */
3320 if (lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3322 if (lpLVItem->state & LVIS_FOCUSED)
3324 LISTVIEW_SetItemFocus(infoPtr, -1);
3325 infoPtr->nFocusedItem = lpLVItem->iItem;
3326 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3328 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3329 infoPtr->nFocusedItem = -1;
3333 /* if we're inserting the item, we're done */
3334 if (isNew) return TRUE;
3336 /* send LVN_ITEMCHANGED notification */
3337 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3338 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3345 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3348 * [I] infoPtr : valid pointer to the listview structure
3349 * [I] lpLVItem : valid pointer to new subitem atttributes
3350 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3351 * [O] bChanged : will be set to TRUE if the item really changed
3357 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3360 SUBITEM_INFO *lpSubItem;
3362 /* we do not support subitems for virtual listviews */
3363 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3365 /* set subitem only if column is present */
3366 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3368 /* First do some sanity checks */
3369 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3370 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3372 /* get the subitem structure, and create it if not there */
3373 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3374 assert (hdpaSubItems);
3376 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3379 SUBITEM_INFO *tmpSubItem;
3382 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3383 if (!lpSubItem) return FALSE;
3384 /* we could binary search here, if need be...*/
3385 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3387 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3388 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3390 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3395 lpSubItem->iSubItem = lpLVItem->iSubItem;
3396 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3400 if (lpLVItem->mask & LVIF_IMAGE)
3401 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3403 lpSubItem->hdr.iImage = lpLVItem->iImage;
3407 if (lpLVItem->mask & LVIF_TEXT)
3408 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3410 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3419 * Sets item attributes.
3422 * [I] infoPtr : valid pointer to the listview structure
3423 * [I] lpLVItem : new item atttributes
3424 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3430 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3432 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3433 LPWSTR pszText = NULL;
3434 BOOL bResult, bChanged = FALSE;
3436 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3438 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3441 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3442 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3444 pszText = lpLVItem->pszText;
3445 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3448 /* actually set the fields */
3449 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3451 if (lpLVItem->iSubItem)
3452 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3454 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3456 /* redraw item, if necessary */
3457 if (bChanged && !infoPtr->bIsDrawing)
3459 /* this little optimization eliminates some nasty flicker */
3460 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3461 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3462 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3464 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3469 textfreeT(lpLVItem->pszText, isW);
3470 ((LVITEMW *)lpLVItem)->pszText = pszText;
3478 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3481 * [I] infoPtr : valid pointer to the listview structure
3486 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3488 LONG lStyle = infoPtr->dwStyle;
3489 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3491 SCROLLINFO scrollInfo;
3493 scrollInfo.cbSize = sizeof(SCROLLINFO);
3494 scrollInfo.fMask = SIF_POS;
3496 if (uView == LVS_LIST)
3498 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3499 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3501 else if (uView == LVS_REPORT)
3503 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3504 nItem = scrollInfo.nPos;
3508 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3509 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3512 TRACE("nItem=%d\n", nItem);
3520 * Erases the background of the given rectangle
3523 * [I] infoPtr : valid pointer to the listview structure
3524 * [I] hdc : device context handle
3525 * [I] lprcBox : clipping rectangle
3531 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3533 if (!infoPtr->hBkBrush) return FALSE;
3535 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3537 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3545 * [I] infoPtr : valid pointer to the listview structure
3546 * [I] hdc : device context handle
3547 * [I] nItem : item index
3548 * [I] nSubItem : subitem index
3549 * [I] pos : item position in client coordinates
3550 * [I] cdmode : custom draw mode
3556 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3558 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3559 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3560 static const WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3561 DWORD cdsubitemmode = CDRF_DODEFAULT;
3562 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3563 NMLVCUSTOMDRAW nmlvcd;
3567 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3569 /* get information needed for drawing the item */
3570 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3571 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3572 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3573 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3574 lvItem.iItem = nItem;
3575 lvItem.iSubItem = nSubItem;
3578 lvItem.cchTextMax = DISP_TEXT_SIZE;
3579 lvItem.pszText = szDispText;
3580 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3581 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3582 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3583 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = (LPWSTR)szCallback;
3584 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3586 /* now check if we need to update the focus rectangle */
3587 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3589 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3590 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3591 OffsetRect(&rcBox, pos.x, pos.y);
3592 OffsetRect(&rcState, pos.x, pos.y);
3593 OffsetRect(&rcIcon, pos.x, pos.y);
3594 OffsetRect(&rcLabel, pos.x, pos.y);
3595 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3596 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3598 /* fill in the custom draw structure */
3599 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3601 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3602 if (cdmode & CDRF_NOTIFYITEMDRAW)
3603 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3604 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3605 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3606 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3607 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3609 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3610 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3612 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3613 prepaint_setup(infoPtr, hdc, &nmlvcd);
3615 /* in full row select, subitems, will just use main item's colors */
3616 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3617 nmlvcd.clrTextBk = CLR_NONE;
3620 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3622 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3625 TRACE("uStateImage=%d\n", uStateImage);
3626 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3631 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3632 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3634 TRACE("iImage=%d\n", lvItem.iImage);
3635 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3636 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3639 /* Don't bother painting item being edited */
3640 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3642 /* draw the selection background, if we're drawing the main item */
3646 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3647 rcSelect.right = rcBox.right;
3649 if (nmlvcd.clrTextBk != CLR_NONE)
3650 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3651 if(lprcFocus) *lprcFocus = rcSelect;
3654 /* figure out the text drawing flags */
3655 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3656 if (uView == LVS_ICON)
3657 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3660 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3662 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3663 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3664 default: uFormat |= DT_LEFT;
3667 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3669 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3670 else rcLabel.left += LABEL_HOR_PADDING;
3672 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3673 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3676 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3677 notify_postpaint(infoPtr, &nmlvcd);
3683 * Draws listview items when in owner draw mode.
3686 * [I] infoPtr : valid pointer to the listview structure
3687 * [I] hdc : device context handle
3692 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3694 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3695 DWORD cditemmode = CDRF_DODEFAULT;
3696 NMLVCUSTOMDRAW nmlvcd;
3697 POINT Origin, Position;
3703 ZeroMemory(&dis, sizeof(dis));
3705 /* Get scroll info once before loop */
3706 LISTVIEW_GetOrigin(infoPtr, &Origin);
3708 /* iterate through the invalidated rows */
3709 while(iterator_next(i))
3711 item.iItem = i->nItem;
3713 item.mask = LVIF_PARAM | LVIF_STATE;
3714 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3715 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3717 dis.CtlType = ODT_LISTVIEW;
3719 dis.itemID = item.iItem;
3720 dis.itemAction = ODA_DRAWENTIRE;
3722 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3723 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3724 dis.hwndItem = infoPtr->hwndSelf;
3726 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3727 dis.rcItem.left = Position.x + Origin.x;
3728 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3729 dis.rcItem.top = Position.y + Origin.y;
3730 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3731 dis.itemData = item.lParam;
3733 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3736 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3737 * structure for the rest. of the paint cycle
3739 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3740 if (cdmode & CDRF_NOTIFYITEMDRAW)
3741 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3743 if (!(cditemmode & CDRF_SKIPDEFAULT))
3745 prepaint_setup (infoPtr, hdc, &nmlvcd);
3746 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3749 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3750 notify_postpaint(infoPtr, &nmlvcd);
3756 * Draws listview items when in report display mode.
3759 * [I] infoPtr : valid pointer to the listview structure
3760 * [I] hdc : device context handle
3761 * [I] cdmode : custom draw mode
3766 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3769 RECT rcClip, rcItem;
3770 POINT Origin, Position;
3776 /* figure out what to draw */
3777 rgntype = GetClipBox(hdc, &rcClip);
3778 if (rgntype == NULLREGION) return;
3780 /* Get scroll info once before loop */
3781 LISTVIEW_GetOrigin(infoPtr, &Origin);
3783 /* narrow down the columns we need to paint */
3784 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3786 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3787 if (rcItem.right + Origin.x >= rcClip.left) break;
3789 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3791 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3792 if (rcItem.left + Origin.x < rcClip.right) break;
3794 iterator_rangeitems(&j, colRange);
3796 /* in full row select, we _have_ to draw the main item */
3797 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3800 /* iterate through the invalidated rows */
3801 while(iterator_next(i))
3803 /* iterate through the invalidated columns */
3804 while(iterator_next(&j))
3806 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3807 Position.x += Origin.x;
3808 Position.y += Origin.y;
3810 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3812 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3814 rcItem.bottom = infoPtr->nItemHeight;
3815 OffsetRect(&rcItem, Position.x, Position.y);
3816 if (!RectVisible(hdc, &rcItem)) continue;
3819 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3822 iterator_destroy(&j);
3827 * Draws listview items when in list display mode.
3830 * [I] infoPtr : valid pointer to the listview structure
3831 * [I] hdc : device context handle
3832 * [I] cdmode : custom draw mode
3837 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3839 POINT Origin, Position;
3841 /* Get scroll info once before loop */
3842 LISTVIEW_GetOrigin(infoPtr, &Origin);
3844 while(iterator_prev(i))
3846 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3847 Position.x += Origin.x;
3848 Position.y += Origin.y;
3850 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3857 * Draws listview items.
3860 * [I] infoPtr : valid pointer to the listview structure
3861 * [I] hdc : device context handle
3866 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3868 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3869 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3870 NMLVCUSTOMDRAW nmlvcd;
3877 LISTVIEW_DUMP(infoPtr);
3879 infoPtr->bIsDrawing = TRUE;
3881 /* save dc values we're gonna trash while drawing */
3882 hOldFont = SelectObject(hdc, infoPtr->hFont);
3883 oldBkMode = GetBkMode(hdc);
3884 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3885 oldTextColor = GetTextColor(hdc);
3887 oldClrTextBk = infoPtr->clrTextBk;
3888 oldClrText = infoPtr->clrText;
3890 infoPtr->cditemmode = CDRF_DODEFAULT;
3892 GetClientRect(infoPtr->hwndSelf, &rcClient);
3893 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3894 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3895 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3896 prepaint_setup(infoPtr, hdc, &nmlvcd);
3898 /* Use these colors to draw the items */
3899 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3900 infoPtr->clrText = nmlvcd.clrText;
3902 /* nothing to draw */
3903 if(infoPtr->nItemCount == 0) goto enddraw;
3905 /* figure out what we need to draw */
3906 iterator_visibleitems(&i, infoPtr, hdc);
3908 /* send cache hint notification */
3909 if (infoPtr->dwStyle & LVS_OWNERDATA)
3911 RANGE range = iterator_range(&i);
3914 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3915 nmlv.iFrom = range.lower;
3916 nmlv.iTo = range.upper - 1;
3917 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3920 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3921 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
3924 if (uView == LVS_REPORT)
3925 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
3926 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3927 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
3929 /* if we have a focus rect, draw it */
3930 if (infoPtr->bFocus)
3931 DrawFocusRect(hdc, &infoPtr->rcFocus);
3933 iterator_destroy(&i);
3936 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3937 notify_postpaint(infoPtr, &nmlvcd);
3939 infoPtr->clrTextBk = oldClrTextBk;
3940 infoPtr->clrText = oldClrText;
3942 SelectObject(hdc, hOldFont);
3943 SetBkMode(hdc, oldBkMode);
3944 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3945 SetTextColor(hdc, oldTextColor);
3946 infoPtr->bIsDrawing = FALSE;
3952 * Calculates the approximate width and height of a given number of items.
3955 * [I] infoPtr : valid pointer to the listview structure
3956 * [I] nItemCount : number of items
3957 * [I] wWidth : width
3958 * [I] wHeight : height
3961 * Returns a DWORD. The width in the low word and the height in high word.
3963 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3964 WORD wWidth, WORD wHeight)
3966 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3967 INT nItemCountPerColumn = 1;
3968 INT nColumnCount = 0;
3969 DWORD dwViewRect = 0;
3971 if (nItemCount == -1)
3972 nItemCount = infoPtr->nItemCount;
3974 if (uView == LVS_LIST)
3976 if (wHeight == 0xFFFF)
3978 /* use current height */
3979 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3982 if (wHeight < infoPtr->nItemHeight)
3983 wHeight = infoPtr->nItemHeight;
3987 if (infoPtr->nItemHeight > 0)
3989 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3990 if (nItemCountPerColumn == 0)
3991 nItemCountPerColumn = 1;
3993 if (nItemCount % nItemCountPerColumn != 0)
3994 nColumnCount = nItemCount / nItemCountPerColumn;
3996 nColumnCount = nItemCount / nItemCountPerColumn + 1;
4000 /* Microsoft padding magic */
4001 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
4002 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
4004 dwViewRect = MAKELONG(wWidth, wHeight);
4006 else if (uView == LVS_REPORT)
4010 if (infoPtr->nItemCount > 0)
4012 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4013 wWidth = rcBox.right - rcBox.left;
4014 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4018 /* use current height and width */
4019 if (wHeight == 0xffff)
4020 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4021 if (wWidth == 0xffff)
4022 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4025 dwViewRect = MAKELONG(wWidth, wHeight);
4027 else if (uView == LVS_SMALLICON)
4028 FIXME("uView == LVS_SMALLICON: not implemented\n");
4029 else if (uView == LVS_ICON)
4030 FIXME("uView == LVS_ICON: not implemented\n");
4038 * Create a drag image list for the specified item.
4041 * [I] infoPtr : valid pointer to the listview structure
4042 * [I] iItem : index of item
4043 * [O] lppt : Upperr-left corner of the image
4046 * Returns a handle to the image list if successful, NULL otherwise.
4048 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4054 HBITMAP hbmp, hOldbmp;
4055 HIMAGELIST dragList = 0;
4056 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4058 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4061 rcItem.left = LVIR_BOUNDS;
4062 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4065 lppt->x = rcItem.left;
4066 lppt->y = rcItem.top;
4068 size.cx = rcItem.right - rcItem.left;
4069 size.cy = rcItem.bottom - rcItem.top;
4071 hdcOrig = GetDC(infoPtr->hwndSelf);
4072 hdc = CreateCompatibleDC(hdcOrig);
4073 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4074 hOldbmp = SelectObject(hdc, hbmp);
4076 rcItem.left = rcItem.top = 0;
4077 rcItem.right = size.cx;
4078 rcItem.bottom = size.cy;
4079 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4082 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4084 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4085 SelectObject(hdc, hOldbmp);
4086 ImageList_Add(dragList, hbmp, 0);
4089 SelectObject(hdc, hOldbmp);
4093 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4095 TRACE("ret=%p\n", dragList);
4103 * Removes all listview items and subitems.
4106 * [I] infoPtr : valid pointer to the listview structure
4112 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4115 HDPA hdpaSubItems = NULL;
4122 /* we do it directly, to avoid notifications */
4123 ranges_clear(infoPtr->selectionRanges);
4124 infoPtr->nSelectionMark = -1;
4125 infoPtr->nFocusedItem = -1;
4126 SetRectEmpty(&infoPtr->rcFocus);
4127 /* But we are supposed to leave nHotItem as is! */
4130 /* send LVN_DELETEALLITEMS notification */
4131 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4133 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4135 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4137 /* send LVN_DELETEITEM notification, if not supressed */
4138 if (!bSuppress) notify_deleteitem(infoPtr, i);
4139 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4141 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4142 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4144 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4145 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4148 DPA_Destroy(hdpaSubItems);
4149 DPA_DeletePtr(infoPtr->hdpaItems, i);
4151 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4152 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4153 infoPtr->nItemCount --;
4156 LISTVIEW_UpdateScroll(infoPtr);
4158 LISTVIEW_InvalidateList(infoPtr);
4165 * Scrolls, and updates the columns, when a column is changing width.
4168 * [I] infoPtr : valid pointer to the listview structure
4169 * [I] nColumn : column to scroll
4170 * [I] dx : amount of scroll, in pixels
4175 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4177 COLUMN_INFO *lpColumnInfo;
4181 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4182 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4183 rcCol = lpColumnInfo->rcHeader;
4184 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4185 rcCol.left = rcCol.right;
4187 /* ajust the other columns */
4188 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4190 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4191 lpColumnInfo->rcHeader.left += dx;
4192 lpColumnInfo->rcHeader.right += dx;
4195 /* do not update screen if not in report mode */
4196 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4198 /* if we have a focus, must first erase the focus rect */
4199 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4201 /* Need to reset the item width when inserting a new column */
4202 infoPtr->nItemWidth += dx;
4204 LISTVIEW_UpdateScroll(infoPtr);
4206 /* scroll to cover the deleted column, and invalidate for redraw */
4207 rcOld = infoPtr->rcList;
4208 rcOld.left = rcCol.left;
4209 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4211 /* we can restore focus now */
4212 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4217 * Removes a column from the listview control.
4220 * [I] infoPtr : valid pointer to the listview structure
4221 * [I] nColumn : column index
4227 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4231 TRACE("nColumn=%d\n", nColumn);
4233 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4234 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4236 /* While the MSDN specifically says that column zero should not be deleted,
4237 what actually happens is that the column itself is deleted but no items or subitems
4241 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4243 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4246 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4247 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4249 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4251 SUBITEM_INFO *lpSubItem, *lpDelItem;
4253 INT nItem, nSubItem, i;
4255 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4257 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4260 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4262 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4263 if (lpSubItem->iSubItem == nColumn)
4266 lpDelItem = lpSubItem;
4268 else if (lpSubItem->iSubItem > nColumn)
4270 lpSubItem->iSubItem--;
4274 /* if we found our subitem, zapp it */
4278 if (is_textW(lpDelItem->hdr.pszText))
4279 Free(lpDelItem->hdr.pszText);
4284 /* free dpa memory */
4285 DPA_DeletePtr(hdpaSubItems, nSubItem);
4290 /* update the other column info */
4291 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4292 LISTVIEW_InvalidateList(infoPtr);
4294 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4301 * Invalidates the listview after an item's insertion or deletion.
4304 * [I] infoPtr : valid pointer to the listview structure
4305 * [I] nItem : item index
4306 * [I] dir : -1 if deleting, 1 if inserting
4311 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4313 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4314 INT nPerCol, nItemCol, nItemRow;
4318 /* if we don't refresh, what's the point of scrolling? */
4319 if (!is_redrawing(infoPtr)) return;
4321 assert (abs(dir) == 1);
4323 /* arrange icons if autoarrange is on */
4324 if (is_autoarrange(infoPtr))
4326 BOOL arrange = TRUE;
4327 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4328 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4329 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4332 /* scrollbars need updating */
4333 LISTVIEW_UpdateScroll(infoPtr);
4335 /* figure out the item's position */
4336 if (uView == LVS_REPORT)
4337 nPerCol = infoPtr->nItemCount + 1;
4338 else if (uView == LVS_LIST)
4339 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4340 else /* LVS_ICON, or LVS_SMALLICON */
4343 nItemCol = nItem / nPerCol;
4344 nItemRow = nItem % nPerCol;
4345 LISTVIEW_GetOrigin(infoPtr, &Origin);
4347 /* move the items below up a slot */
4348 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4349 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4350 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4351 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4352 OffsetRect(&rcScroll, Origin.x, Origin.y);
4353 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4354 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4356 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4357 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4358 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4361 /* report has only that column, so we're done */
4362 if (uView == LVS_REPORT) return;
4364 /* now for LISTs, we have to deal with the columns to the right */
4365 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4367 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4368 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4369 OffsetRect(&rcScroll, Origin.x, Origin.y);
4370 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4371 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4372 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4377 * Removes an item from the listview control.
4380 * [I] infoPtr : valid pointer to the listview structure
4381 * [I] nItem : item index
4387 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4389 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4392 TRACE("(nItem=%d)\n", nItem);
4394 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4396 /* remove selection, and focus */
4398 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4399 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4401 /* send LVN_DELETEITEM notification. */
4402 notify_deleteitem(infoPtr, nItem);
4404 /* we need to do this here, because we'll be deleting stuff */
4405 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4406 LISTVIEW_InvalidateItem(infoPtr, nItem);
4408 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4414 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4415 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4417 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4418 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4421 DPA_Destroy(hdpaSubItems);
4424 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4426 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4427 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4430 infoPtr->nItemCount--;
4431 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4433 /* now is the invalidation fun */
4434 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4441 * Callback implementation for editlabel control
4444 * [I] infoPtr : valid pointer to the listview structure
4445 * [I] pszText : modified text
4446 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4452 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4454 NMLVDISPINFOW dispInfo;
4456 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4458 ZeroMemory(&dispInfo, sizeof(dispInfo));
4459 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4460 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4461 dispInfo.item.iSubItem = 0;
4462 dispInfo.item.stateMask = ~0;
4463 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4464 /* add the text from the edit in */
4465 dispInfo.item.mask |= LVIF_TEXT;
4466 dispInfo.item.pszText = pszText;
4467 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4469 /* Do we need to update the Item Text */
4470 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4471 if (!pszText) return TRUE;
4473 ZeroMemory(&dispInfo, sizeof(dispInfo));
4474 dispInfo.item.mask = LVIF_TEXT;
4475 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4476 dispInfo.item.iSubItem = 0;
4477 dispInfo.item.pszText = pszText;
4478 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4479 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4484 * Begin in place editing of specified list view item
4487 * [I] infoPtr : valid pointer to the listview structure
4488 * [I] nItem : item index
4489 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4495 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4497 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4498 NMLVDISPINFOW dispInfo;
4501 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4503 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4504 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4506 infoPtr->nEditLabelItem = nItem;
4508 /* Is the EditBox still there, if so remove it */
4509 if(infoPtr->hwndEdit != 0)
4511 SetFocus(infoPtr->hwndSelf);
4512 infoPtr->hwndEdit = 0;
4515 LISTVIEW_SetSelection(infoPtr, nItem);
4516 LISTVIEW_SetItemFocus(infoPtr, nItem);
4517 LISTVIEW_InvalidateItem(infoPtr, nItem);
4519 rect.left = LVIR_LABEL;
4520 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4522 ZeroMemory(&dispInfo, sizeof(dispInfo));
4523 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4524 dispInfo.item.iItem = nItem;
4525 dispInfo.item.iSubItem = 0;
4526 dispInfo.item.stateMask = ~0;
4527 dispInfo.item.pszText = szDispText;
4528 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4529 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4531 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4532 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4533 if (!infoPtr->hwndEdit) return 0;
4535 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4537 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4538 infoPtr->hwndEdit = 0;
4542 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4543 SetFocus(infoPtr->hwndEdit);
4544 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4545 return infoPtr->hwndEdit;
4551 * Ensures the specified item is visible, scrolling into view if necessary.
4554 * [I] infoPtr : valid pointer to the listview structure
4555 * [I] nItem : item index
4556 * [I] bPartial : partially or entirely visible
4562 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4564 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4565 INT nScrollPosHeight = 0;
4566 INT nScrollPosWidth = 0;
4567 INT nHorzAdjust = 0;
4568 INT nVertAdjust = 0;
4571 RECT rcItem, rcTemp;
4573 rcItem.left = LVIR_BOUNDS;
4574 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4576 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4578 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4580 /* scroll left/right, but in LVS_REPORT mode */
4581 if (uView == LVS_LIST)
4582 nScrollPosWidth = infoPtr->nItemWidth;
4583 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4584 nScrollPosWidth = 1;
4586 if (rcItem.left < infoPtr->rcList.left)
4589 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4594 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4598 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4600 /* scroll up/down, but not in LVS_LIST mode */
4601 if (uView == LVS_REPORT)
4602 nScrollPosHeight = infoPtr->nItemHeight;
4603 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4604 nScrollPosHeight = 1;
4606 if (rcItem.top < infoPtr->rcList.top)
4609 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4614 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4618 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4620 if (nScrollPosWidth)
4622 INT diff = nHorzDiff / nScrollPosWidth;
4623 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4624 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4627 if (nScrollPosHeight)
4629 INT diff = nVertDiff / nScrollPosHeight;
4630 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4631 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4639 * Searches for an item with specific characteristics.
4642 * [I] hwnd : window handle
4643 * [I] nStart : base item index
4644 * [I] lpFindInfo : item information to look for
4647 * SUCCESS : index of item
4650 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4651 const LVFINDINFOW *lpFindInfo)
4653 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4654 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4655 BOOL bWrap = FALSE, bNearest = FALSE;
4656 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4657 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4658 POINT Position, Destination;
4661 if (!lpFindInfo || nItem < 0) return -1;
4664 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4666 lvItem.mask |= LVIF_TEXT;
4667 lvItem.pszText = szDispText;
4668 lvItem.cchTextMax = DISP_TEXT_SIZE;
4671 if (lpFindInfo->flags & LVFI_WRAP)
4674 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4675 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4680 LISTVIEW_GetOrigin(infoPtr, &Origin);
4681 Destination.x = lpFindInfo->pt.x - Origin.x;
4682 Destination.y = lpFindInfo->pt.y - Origin.y;
4683 switch(lpFindInfo->vkDirection)
4685 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4686 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4687 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4688 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4689 case VK_HOME: Destination.x = Destination.y = 0; break;
4690 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4691 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4693 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4694 Destination.x = rcArea.right;
4695 Destination.y = rcArea.bottom;
4697 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4702 /* if LVFI_PARAM is specified, all other flags are ignored */
4703 if (lpFindInfo->flags & LVFI_PARAM)
4705 lvItem.mask |= LVIF_PARAM;
4707 lvItem.mask &= ~LVIF_TEXT;
4711 for (; nItem < nLast; nItem++)
4713 lvItem.iItem = nItem;
4714 lvItem.iSubItem = 0;
4715 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4717 if (lvItem.mask & LVIF_PARAM)
4719 if (lpFindInfo->lParam == lvItem.lParam)
4725 if (lvItem.mask & LVIF_TEXT)
4727 if (lpFindInfo->flags & LVFI_PARTIAL)
4729 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4733 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4737 if (!bNearest) return nItem;
4739 /* This is very inefficient. To do a good job here,
4740 * we need a sorted array of (x,y) item positions */
4741 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4743 /* compute the distance^2 to the destination */
4744 xdist = Destination.x - Position.x;
4745 ydist = Destination.y - Position.y;
4746 dist = xdist * xdist + ydist * ydist;
4748 /* remember the distance, and item if it's closer */
4752 nNearestItem = nItem;
4759 nLast = min(nStart + 1, infoPtr->nItemCount);
4764 return nNearestItem;
4769 * Searches for an item with specific characteristics.
4772 * [I] hwnd : window handle
4773 * [I] nStart : base item index
4774 * [I] lpFindInfo : item information to look for
4777 * SUCCESS : index of item
4780 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4781 const LVFINDINFOA *lpFindInfo)
4783 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4787 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4788 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4789 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4790 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4796 * Retrieves the background image of the listview control.
4799 * [I] infoPtr : valid pointer to the listview structure
4800 * [O] lpBkImage : background image attributes
4806 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4808 /* FIXME (listview, "empty stub!\n"); */
4814 * Retrieves column attributes.
4817 * [I] infoPtr : valid pointer to the listview structure
4818 * [I] nColumn : column index
4819 * [IO] lpColumn : column information
4820 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4821 * otherwise it is in fact a LPLVCOLUMNA
4827 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4829 COLUMN_INFO *lpColumnInfo;
4832 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4833 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4835 /* initialize memory */
4836 ZeroMemory(&hdi, sizeof(hdi));
4838 if (lpColumn->mask & LVCF_TEXT)
4840 hdi.mask |= HDI_TEXT;
4841 hdi.pszText = lpColumn->pszText;
4842 hdi.cchTextMax = lpColumn->cchTextMax;
4845 if (lpColumn->mask & LVCF_IMAGE)
4846 hdi.mask |= HDI_IMAGE;
4848 if (lpColumn->mask & LVCF_ORDER)
4849 hdi.mask |= HDI_ORDER;
4851 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4853 if (lpColumn->mask & LVCF_FMT)
4854 lpColumn->fmt = lpColumnInfo->fmt;
4856 if (lpColumn->mask & LVCF_WIDTH)
4857 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4859 if (lpColumn->mask & LVCF_IMAGE)
4860 lpColumn->iImage = hdi.iImage;
4862 if (lpColumn->mask & LVCF_ORDER)
4863 lpColumn->iOrder = hdi.iOrder;
4869 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4876 /* FIXME: little hack */
4877 for (i = 0; i < iCount; i++)
4885 * Retrieves the column width.
4888 * [I] infoPtr : valid pointer to the listview structure
4889 * [I] int : column index
4892 * SUCCESS : column width
4895 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4897 INT nColumnWidth = 0;
4900 TRACE("nColumn=%d\n", nColumn);
4902 /* we have a 'column' in LIST and REPORT mode only */
4903 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4906 nColumnWidth = infoPtr->nItemWidth;
4909 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
4910 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4911 nColumnWidth = rcHeader.right - rcHeader.left;
4915 TRACE("nColumnWidth=%d\n", nColumnWidth);
4916 return nColumnWidth;
4921 * In list or report display mode, retrieves the number of items that can fit
4922 * vertically in the visible area. In icon or small icon display mode,
4923 * retrieves the total number of visible items.
4926 * [I] infoPtr : valid pointer to the listview structure
4929 * Number of fully visible items.
4931 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4933 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4937 return infoPtr->nItemCount;
4939 return LISTVIEW_GetCountPerColumn(infoPtr);
4941 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4949 * Retrieves an image list handle.
4952 * [I] infoPtr : valid pointer to the listview structure
4953 * [I] nImageList : image list identifier
4956 * SUCCESS : image list handle
4959 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4963 case LVSIL_NORMAL: return infoPtr->himlNormal;
4964 case LVSIL_SMALL: return infoPtr->himlSmall;
4965 case LVSIL_STATE: return infoPtr->himlState;
4970 /* LISTVIEW_GetISearchString */
4974 * Retrieves item attributes.
4977 * [I] hwnd : window handle
4978 * [IO] lpLVItem : item info
4979 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4980 * if FALSE, the lpLVItem is a LPLVITEMA.
4983 * This is the internal 'GetItem' interface -- it tries to
4984 * be smart, and avoids text copies, if possible, by modifing
4985 * lpLVItem->pszText to point to the text string. Please note
4986 * that this is not always possible (e.g. OWNERDATA), so on
4987 * entry you *must* supply valid values for pszText, and cchTextMax.
4988 * The only difference to the documented interface is that upon
4989 * return, you should use *only* the lpLVItem->pszText, rather than
4990 * the buffer pointer you provided on input. Most code already does
4991 * that, so it's not a problem.
4992 * For the two cases when the text must be copied (that is,
4993 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
4999 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5001 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
5002 NMLVDISPINFOW dispInfo;
5008 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5010 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5013 if (lpLVItem->mask == 0) return TRUE;
5015 /* make a local copy */
5016 isubitem = lpLVItem->iSubItem;
5018 /* a quick optimization if all we're asked is the focus state
5019 * these queries are worth optimising since they are common,
5020 * and can be answered in constant time, without the heavy accesses */
5021 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5022 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5024 lpLVItem->state = 0;
5025 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5026 lpLVItem->state |= LVIS_FOCUSED;
5030 ZeroMemory(&dispInfo, sizeof(dispInfo));
5032 /* if the app stores all the data, handle it separately */
5033 if (infoPtr->dwStyle & LVS_OWNERDATA)
5035 dispInfo.item.state = 0;
5037 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5038 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5040 /* NOTE: copy only fields which we _know_ are initialized, some apps
5041 * depend on the uninitialized fields being 0 */
5042 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5043 dispInfo.item.iItem = lpLVItem->iItem;
5044 dispInfo.item.iSubItem = isubitem;
5045 if (lpLVItem->mask & LVIF_TEXT)
5047 dispInfo.item.pszText = lpLVItem->pszText;
5048 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5050 if (lpLVItem->mask & LVIF_STATE)
5051 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5052 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5053 dispInfo.item.stateMask = lpLVItem->stateMask;
5054 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5056 /* full size structure expected - _WIN32IE >= 0x560 */
5057 *lpLVItem = dispInfo.item;
5059 else if (lpLVItem->mask & LVIF_INDENT)
5061 /* indent member expected - _WIN32IE >= 0x300 */
5062 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5066 /* minimal structure expected */
5067 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5069 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5072 /* make sure lParam is zeroed out */
5073 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5075 /* we store only a little state, so if we're not asked, we're done */
5076 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5078 /* if focus is handled by us, report it */
5079 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5081 lpLVItem->state &= ~LVIS_FOCUSED;
5082 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5083 lpLVItem->state |= LVIS_FOCUSED;
5086 /* and do the same for selection, if we handle it */
5087 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5089 lpLVItem->state &= ~LVIS_SELECTED;
5090 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5091 lpLVItem->state |= LVIS_SELECTED;
5097 /* find the item and subitem structures before we proceed */
5098 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5099 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5104 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5105 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5108 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5113 pItemHdr = &lpItem->hdr;
5115 /* Do we need to query the state from the app? */
5116 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5118 dispInfo.item.mask |= LVIF_STATE;
5119 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5122 /* Do we need to enquire about the image? */
5123 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5124 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5126 dispInfo.item.mask |= LVIF_IMAGE;
5127 dispInfo.item.iImage = I_IMAGECALLBACK;
5130 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5131 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5133 dispInfo.item.mask |= LVIF_TEXT;
5134 dispInfo.item.pszText = lpLVItem->pszText;
5135 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5136 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5137 *dispInfo.item.pszText = '\0';
5140 /* If we don't have all the requested info, query the application */
5141 if (dispInfo.item.mask != 0)
5143 dispInfo.item.iItem = lpLVItem->iItem;
5144 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5145 dispInfo.item.lParam = lpItem->lParam;
5146 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5147 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5150 /* we should not store values for subitems */
5151 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5153 /* Now, handle the iImage field */
5154 if (dispInfo.item.mask & LVIF_IMAGE)
5156 lpLVItem->iImage = dispInfo.item.iImage;
5157 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5158 pItemHdr->iImage = dispInfo.item.iImage;
5160 else if (lpLVItem->mask & LVIF_IMAGE)
5162 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5163 lpLVItem->iImage = pItemHdr->iImage;
5165 lpLVItem->iImage = 0;
5168 /* The pszText field */
5169 if (dispInfo.item.mask & LVIF_TEXT)
5171 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5172 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5174 lpLVItem->pszText = dispInfo.item.pszText;
5176 else if (lpLVItem->mask & LVIF_TEXT)
5178 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5179 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5182 /* if this is a subitem, we're done */
5183 if (isubitem) return TRUE;
5185 /* Next is the lParam field */
5186 if (dispInfo.item.mask & LVIF_PARAM)
5188 lpLVItem->lParam = dispInfo.item.lParam;
5189 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5190 lpItem->lParam = dispInfo.item.lParam;
5192 else if (lpLVItem->mask & LVIF_PARAM)
5193 lpLVItem->lParam = lpItem->lParam;
5195 /* ... the state field (this one is different due to uCallbackmask) */
5196 if (lpLVItem->mask & LVIF_STATE)
5198 lpLVItem->state = lpItem->state;
5199 if (dispInfo.item.mask & LVIF_STATE)
5201 lpLVItem->state &= ~dispInfo.item.stateMask;
5202 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5204 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5206 lpLVItem->state &= ~LVIS_FOCUSED;
5207 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5208 lpLVItem->state |= LVIS_FOCUSED;
5210 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5212 lpLVItem->state &= ~LVIS_SELECTED;
5213 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5214 lpLVItem->state |= LVIS_SELECTED;
5218 /* and last, but not least, the indent field */
5219 if (lpLVItem->mask & LVIF_INDENT)
5220 lpLVItem->iIndent = lpItem->iIndent;
5227 * Retrieves item attributes.
5230 * [I] hwnd : window handle
5231 * [IO] lpLVItem : item info
5232 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5233 * if FALSE, the lpLVItem is a LPLVITEMA.
5236 * This is the external 'GetItem' interface -- it properly copies
5237 * the text in the provided buffer.
5243 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5248 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5251 pszText = lpLVItem->pszText;
5252 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5253 if (bResult && lpLVItem->pszText != pszText)
5254 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5255 lpLVItem->pszText = pszText;
5263 * Retrieves the position (upper-left) of the listview control item.
5264 * Note that for LVS_ICON style, the upper-left is that of the icon
5265 * and not the bounding box.
5268 * [I] infoPtr : valid pointer to the listview structure
5269 * [I] nItem : item index
5270 * [O] lpptPosition : coordinate information
5276 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5278 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5281 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5283 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5285 LISTVIEW_GetOrigin(infoPtr, &Origin);
5286 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5288 if (uView == LVS_ICON)
5290 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5291 lpptPosition->y += ICON_TOP_PADDING;
5293 lpptPosition->x += Origin.x;
5294 lpptPosition->y += Origin.y;
5296 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5303 * Retrieves the bounding rectangle for a listview control item.
5306 * [I] infoPtr : valid pointer to the listview structure
5307 * [I] nItem : item index
5308 * [IO] lprc : bounding rectangle coordinates
5309 * lprc->left specifies the portion of the item for which the bounding
5310 * rectangle will be retrieved.
5312 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5313 * including the icon and label.
5316 * * Experiment shows that native control returns:
5317 * * width = min (48, length of text line)
5318 * * .left = position.x - (width - iconsize.cx)/2
5319 * * .right = .left + width
5320 * * height = #lines of text * ntmHeight + icon height + 8
5321 * * .top = position.y - 2
5322 * * .bottom = .top + height
5323 * * separation between items .y = itemSpacing.cy - height
5324 * * .x = itemSpacing.cx - width
5325 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5328 * * Experiment shows that native control returns:
5329 * * width = iconSize.cx + 16
5330 * * .left = position.x - (width - iconsize.cx)/2
5331 * * .right = .left + width
5332 * * height = iconSize.cy + 4
5333 * * .top = position.y - 2
5334 * * .bottom = .top + height
5335 * * separation between items .y = itemSpacing.cy - height
5336 * * .x = itemSpacing.cx - width
5337 * LVIR_LABEL Returns the bounding rectangle of the item text.
5340 * * Experiment shows that native control returns:
5341 * * width = text length
5342 * * .left = position.x - width/2
5343 * * .right = .left + width
5344 * * height = ntmH * linecount + 2
5345 * * .top = position.y + iconSize.cy + 6
5346 * * .bottom = .top + height
5347 * * separation between items .y = itemSpacing.cy - height
5348 * * .x = itemSpacing.cx - width
5349 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5350 * rectangles, but excludes columns in report view.
5357 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5358 * upon whether the window has the focus currently and on whether the item
5359 * is the one with the focus. Ensure that the control's record of which
5360 * item has the focus agrees with the items' records.
5362 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5364 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5365 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5366 BOOL doLabel = TRUE, oversizedBox = FALSE;
5367 POINT Position, Origin;
5371 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5373 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5375 LISTVIEW_GetOrigin(infoPtr, &Origin);
5376 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5378 /* Be smart and try to figure out the minimum we have to do */
5379 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5380 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5381 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5382 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5383 oversizedBox = TRUE;
5385 /* get what we need from the item before hand, so we make
5386 * only one request. This can speed up things, if data
5387 * is stored on the app side */
5389 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5390 if (doLabel) lvItem.mask |= LVIF_TEXT;
5391 lvItem.iItem = nItem;
5392 lvItem.iSubItem = 0;
5393 lvItem.pszText = szDispText;
5394 lvItem.cchTextMax = DISP_TEXT_SIZE;
5395 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5396 /* we got the state already up, simulate it here, to avoid a reget */
5397 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5399 lvItem.mask |= LVIF_STATE;
5400 lvItem.stateMask = LVIS_FOCUSED;
5401 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5404 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5405 lprc->left = LVIR_BOUNDS;
5409 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5413 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5417 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5420 case LVIR_SELECTBOUNDS:
5421 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5422 UnionRect(lprc, lprc, &label_rect);
5426 WARN("Unknown value: %ld\n", lprc->left);
5430 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5432 TRACE(" rect=%s\n", debugrect(lprc));
5439 * Retrieves the spacing between listview control items.
5442 * [I] infoPtr : valid pointer to the listview structure
5443 * [IO] lprc : rectangle to receive the output
5444 * on input, lprc->top = nSubItem
5445 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5447 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5448 * not only those of the first column.
5449 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5455 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5460 if (!lprc) return FALSE;
5462 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5463 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5465 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5467 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5469 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5472 lvItem.iItem = nItem;
5473 lvItem.iSubItem = lprc->top;
5475 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5479 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5484 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5488 ERR("Unknown bounds=%ld\n", lprc->left);
5492 OffsetRect(lprc, Position.x, Position.y);
5499 * Retrieves the width of a label.
5502 * [I] infoPtr : valid pointer to the listview structure
5505 * SUCCESS : string width (in pixels)
5508 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5510 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5513 TRACE("(nItem=%d)\n", nItem);
5515 lvItem.mask = LVIF_TEXT;
5516 lvItem.iItem = nItem;
5517 lvItem.iSubItem = 0;
5518 lvItem.pszText = szDispText;
5519 lvItem.cchTextMax = DISP_TEXT_SIZE;
5520 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5522 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5527 * Retrieves the spacing between listview control items.
5530 * [I] infoPtr : valid pointer to the listview structure
5531 * [I] bSmall : flag for small or large icon
5534 * Horizontal + vertical spacing
5536 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5542 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5546 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5547 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5549 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5556 * Retrieves the state of a listview control item.
5559 * [I] infoPtr : valid pointer to the listview structure
5560 * [I] nItem : item index
5561 * [I] uMask : state mask
5564 * State specified by the mask.
5566 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5570 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5572 lvItem.iItem = nItem;
5573 lvItem.iSubItem = 0;
5574 lvItem.mask = LVIF_STATE;
5575 lvItem.stateMask = uMask;
5576 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5578 return lvItem.state & uMask;
5583 * Retrieves the text of a listview control item or subitem.
5586 * [I] hwnd : window handle
5587 * [I] nItem : item index
5588 * [IO] lpLVItem : item information
5589 * [I] isW : TRUE if lpLVItem is Unicode
5592 * SUCCESS : string length
5595 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5597 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5599 lpLVItem->mask = LVIF_TEXT;
5600 lpLVItem->iItem = nItem;
5601 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5603 return textlenT(lpLVItem->pszText, isW);
5608 * Searches for an item based on properties + relationships.
5611 * [I] infoPtr : valid pointer to the listview structure
5612 * [I] nItem : item index
5613 * [I] uFlags : relationship flag
5616 * SUCCESS : item index
5619 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5621 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5623 LVFINDINFOW lvFindInfo;
5624 INT nCountPerColumn;
5628 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5629 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5631 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5633 if (uFlags & LVNI_CUT)
5636 if (uFlags & LVNI_DROPHILITED)
5637 uMask |= LVIS_DROPHILITED;
5639 if (uFlags & LVNI_FOCUSED)
5640 uMask |= LVIS_FOCUSED;
5642 if (uFlags & LVNI_SELECTED)
5643 uMask |= LVIS_SELECTED;
5645 /* if we're asked for the focused item, that's only one,
5646 * so it's worth optimizing */
5647 if (uFlags & LVNI_FOCUSED)
5649 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5650 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5653 if (uFlags & LVNI_ABOVE)
5655 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5660 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5666 /* Special case for autoarrange - move 'til the top of a list */
5667 if (is_autoarrange(infoPtr))
5669 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5670 while (nItem - nCountPerRow >= 0)
5672 nItem -= nCountPerRow;
5673 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5678 lvFindInfo.flags = LVFI_NEARESTXY;
5679 lvFindInfo.vkDirection = VK_UP;
5680 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5681 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5683 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5688 else if (uFlags & LVNI_BELOW)
5690 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5692 while (nItem < infoPtr->nItemCount)
5695 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5701 /* Special case for autoarrange - move 'til the bottom of a list */
5702 if (is_autoarrange(infoPtr))
5704 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5705 while (nItem + nCountPerRow < infoPtr->nItemCount )
5707 nItem += nCountPerRow;
5708 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5713 lvFindInfo.flags = LVFI_NEARESTXY;
5714 lvFindInfo.vkDirection = VK_DOWN;
5715 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5716 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5718 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5723 else if (uFlags & LVNI_TOLEFT)
5725 if (uView == LVS_LIST)
5727 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5728 while (nItem - nCountPerColumn >= 0)
5730 nItem -= nCountPerColumn;
5731 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5735 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5737 /* Special case for autoarrange - move 'ti the beginning of a row */
5738 if (is_autoarrange(infoPtr))
5740 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5741 while (nItem % nCountPerRow > 0)
5744 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5749 lvFindInfo.flags = LVFI_NEARESTXY;
5750 lvFindInfo.vkDirection = VK_LEFT;
5751 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5752 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5754 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5759 else if (uFlags & LVNI_TORIGHT)
5761 if (uView == LVS_LIST)
5763 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5764 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5766 nItem += nCountPerColumn;
5767 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5771 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5773 /* Special case for autoarrange - move 'til the end of a row */
5774 if (is_autoarrange(infoPtr))
5776 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5777 while (nItem % nCountPerRow < nCountPerRow - 1 )
5780 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5785 lvFindInfo.flags = LVFI_NEARESTXY;
5786 lvFindInfo.vkDirection = VK_RIGHT;
5787 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5788 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5790 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5799 /* search by index */
5800 for (i = nItem; i < infoPtr->nItemCount; i++)
5802 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5810 /* LISTVIEW_GetNumberOfWorkAreas */
5814 * Retrieves the origin coordinates when in icon or small icon display mode.
5817 * [I] infoPtr : valid pointer to the listview structure
5818 * [O] lpptOrigin : coordinate information
5823 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5825 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5826 INT nHorzPos = 0, nVertPos = 0;
5827 SCROLLINFO scrollInfo;
5829 scrollInfo.cbSize = sizeof(SCROLLINFO);
5830 scrollInfo.fMask = SIF_POS;
5832 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5833 nHorzPos = scrollInfo.nPos;
5834 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5835 nVertPos = scrollInfo.nPos;
5837 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5839 lpptOrigin->x = infoPtr->rcList.left;
5840 lpptOrigin->y = infoPtr->rcList.top;
5841 if (uView == LVS_LIST)
5842 nHorzPos *= infoPtr->nItemWidth;
5843 else if (uView == LVS_REPORT)
5844 nVertPos *= infoPtr->nItemHeight;
5846 lpptOrigin->x -= nHorzPos;
5847 lpptOrigin->y -= nVertPos;
5849 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5854 * Retrieves the width of a string.
5857 * [I] hwnd : window handle
5858 * [I] lpszText : text string to process
5859 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5862 * SUCCESS : string width (in pixels)
5865 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5870 if (is_textT(lpszText, isW))
5872 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5873 HDC hdc = GetDC(infoPtr->hwndSelf);
5874 HFONT hOldFont = SelectObject(hdc, hFont);
5877 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5879 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5880 SelectObject(hdc, hOldFont);
5881 ReleaseDC(infoPtr->hwndSelf, hdc);
5883 return stringSize.cx;
5888 * Determines which listview item is located at the specified position.
5891 * [I] infoPtr : valid pointer to the listview structure
5892 * [IO] lpht : hit test information
5893 * [I] subitem : fill out iSubItem.
5894 * [I] select : return the index only if the hit selects the item
5897 * (mm 20001022): We must not allow iSubItem to be touched, for
5898 * an app might pass only a structure with space up to iItem!
5899 * (MS Office 97 does that for instance in the file open dialog)
5902 * SUCCESS : item index
5905 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5907 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5908 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5909 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5910 POINT Origin, Position, opt;
5915 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5919 if (subitem) lpht->iSubItem = 0;
5921 if (infoPtr->rcList.left > lpht->pt.x)
5922 lpht->flags |= LVHT_TOLEFT;
5923 else if (infoPtr->rcList.right < lpht->pt.x)
5924 lpht->flags |= LVHT_TORIGHT;
5926 if (infoPtr->rcList.top > lpht->pt.y)
5927 lpht->flags |= LVHT_ABOVE;
5928 else if (infoPtr->rcList.bottom < lpht->pt.y)
5929 lpht->flags |= LVHT_BELOW;
5931 TRACE("lpht->flags=0x%x\n", lpht->flags);
5932 if (lpht->flags) return -1;
5934 lpht->flags |= LVHT_NOWHERE;
5936 LISTVIEW_GetOrigin(infoPtr, &Origin);
5938 /* first deal with the large items */
5939 rcSearch.left = lpht->pt.x;
5940 rcSearch.top = lpht->pt.y;
5941 rcSearch.right = rcSearch.left + 1;
5942 rcSearch.bottom = rcSearch.top + 1;
5944 iterator_frameditems(&i, infoPtr, &rcSearch);
5945 iterator_next(&i); /* go to first item in the sequence */
5947 iterator_destroy(&i);
5949 TRACE("lpht->iItem=%d\n", iItem);
5950 if (iItem == -1) return -1;
5952 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5953 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5954 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5955 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5956 lvItem.iItem = iItem;
5957 lvItem.iSubItem = 0;
5958 lvItem.pszText = szDispText;
5959 lvItem.cchTextMax = DISP_TEXT_SIZE;
5960 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5961 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5963 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5964 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
5965 opt.x = lpht->pt.x - Position.x - Origin.x;
5966 opt.y = lpht->pt.y - Position.y - Origin.y;
5968 if (uView == LVS_REPORT)
5971 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5972 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5973 if (!PtInRect(&rcBounds, opt)) return -1;
5975 if (PtInRect(&rcIcon, opt))
5976 lpht->flags |= LVHT_ONITEMICON;
5977 else if (PtInRect(&rcLabel, opt))
5978 lpht->flags |= LVHT_ONITEMLABEL;
5979 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5980 lpht->flags |= LVHT_ONITEMSTATEICON;
5981 if (lpht->flags & LVHT_ONITEM)
5982 lpht->flags &= ~LVHT_NOWHERE;
5984 TRACE("lpht->flags=0x%x\n", lpht->flags);
5985 if (uView == LVS_REPORT && subitem)
5989 rcBounds.right = rcBounds.left;
5990 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
5992 rcBounds.left = rcBounds.right;
5993 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5994 if (PtInRect(&rcBounds, opt))
6002 if (select && !(uView == LVS_REPORT &&
6003 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
6004 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6006 if (uView == LVS_REPORT)
6008 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6009 UnionRect(&rcBounds, &rcBounds, &rcState);
6011 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6013 return lpht->iItem = iItem;
6017 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6018 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6019 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6020 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6021 their own sort proc. when sending LVM_SORTITEMS.
6024 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6026 LVS_SORTXXX must be specified,
6027 LVS_OWNERDRAW is not set,
6028 <item>.pszText is not LPSTR_TEXTCALLBACK.
6030 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6031 are sorted based on item text..."
6033 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6035 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6036 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6037 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6039 /* if we're sorting descending, negate the return value */
6040 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6045 * Inserts a new item in the listview control.
6048 * [I] infoPtr : valid pointer to the listview structure
6049 * [I] lpLVItem : item information
6050 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6053 * SUCCESS : new item index
6056 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6058 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6063 BOOL is_sorted, has_changed;
6066 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6068 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6070 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6071 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6073 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6075 if (!(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO)))) return -1;
6077 /* insert item in listview control data structure */
6078 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6079 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6081 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6082 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6084 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6085 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6086 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6087 if (nItem == -1) goto fail;
6088 infoPtr->nItemCount++;
6090 /* shift indices first so they don't get tangled */
6091 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6093 /* set the item attributes */
6094 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6096 /* full size structure expected - _WIN32IE >= 0x560 */
6099 else if (lpLVItem->mask & LVIF_INDENT)
6101 /* indent member expected - _WIN32IE >= 0x300 */
6102 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6106 /* minimal structure expected */
6107 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6110 if (infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) item.state &= ~LVIS_STATEIMAGEMASK;
6111 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6113 /* if we're sorted, sort the list, and update the index */
6116 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6117 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6118 assert(nItem != -1);
6121 /* make room for the position, if we are in the right mode */
6122 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6124 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6126 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6128 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6133 /* send LVN_INSERTITEM notification */
6134 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6136 nmlv.lParam = lpItem->lParam;
6137 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6139 /* align items (set position of each item) */
6140 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6144 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6145 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6147 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6149 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6152 /* now is the invalidation fun */
6153 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6157 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6158 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6159 infoPtr->nItemCount--;
6161 DPA_DeletePtr(hdpaSubItems, 0);
6162 DPA_Destroy (hdpaSubItems);
6169 * Redraws a range of items.
6172 * [I] infoPtr : valid pointer to the listview structure
6173 * [I] nFirst : first item
6174 * [I] nLast : last item
6180 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6184 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6185 max(nFirst, nLast) >= infoPtr->nItemCount)
6188 for (i = nFirst; i <= nLast; i++)
6189 LISTVIEW_InvalidateItem(infoPtr, i);
6196 * Scroll the content of a listview.
6199 * [I] infoPtr : valid pointer to the listview structure
6200 * [I] dx : horizontal scroll amount in pixels
6201 * [I] dy : vertical scroll amount in pixels
6208 * If the control is in report mode (LVS_REPORT) the control can
6209 * be scrolled only in line increments. "dy" will be rounded to the
6210 * nearest number of pixels that are a whole line. Ex: if line height
6211 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6212 * is passed the the scroll will be 0. (per MSDN 7/2002)
6214 * For: (per experimentaion with native control and CSpy ListView)
6215 * LVS_ICON dy=1 = 1 pixel (vertical only)
6217 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6219 * LVS_LIST dx=1 = 1 column (horizontal only)
6220 * but will only scroll 1 column per message
6221 * no matter what the value.
6222 * dy must be 0 or FALSE returned.
6223 * LVS_REPORT dx=1 = 1 pixel
6227 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6229 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6231 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6232 dy /= infoPtr->nItemHeight;
6235 if (dy != 0) return FALSE;
6242 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6243 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6250 * Sets the background color.
6253 * [I] infoPtr : valid pointer to the listview structure
6254 * [I] clrBk : background color
6260 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6262 TRACE("(clrBk=%lx)\n", clrBk);
6264 if(infoPtr->clrBk != clrBk) {
6265 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6266 infoPtr->clrBk = clrBk;
6267 if (clrBk == CLR_NONE)
6268 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
6270 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6271 LISTVIEW_InvalidateList(infoPtr);
6277 /* LISTVIEW_SetBkImage */
6279 /*** Helper for {Insert,Set}ColumnT *only* */
6280 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6282 if (lpColumn->mask & LVCF_FMT)
6284 /* format member is valid */
6285 lphdi->mask |= HDI_FORMAT;
6287 /* set text alignment (leftmost column must be left-aligned) */
6288 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
6289 lphdi->fmt |= HDF_LEFT;
6290 else if (lpColumn->fmt & LVCFMT_RIGHT)
6291 lphdi->fmt |= HDF_RIGHT;
6292 else if (lpColumn->fmt & LVCFMT_CENTER)
6293 lphdi->fmt |= HDF_CENTER;
6295 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6296 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6298 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6300 lphdi->fmt |= HDF_IMAGE;
6301 lphdi->iImage = I_IMAGECALLBACK;
6305 if (lpColumn->mask & LVCF_WIDTH)
6307 lphdi->mask |= HDI_WIDTH;
6308 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6310 /* make it fill the remainder of the controls width */
6314 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6316 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6317 lphdi->cxy += rcHeader.right - rcHeader.left;
6320 /* retrieve the layout of the header */
6321 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6322 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6324 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6327 lphdi->cxy = lpColumn->cx;
6330 if (lpColumn->mask & LVCF_TEXT)
6332 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6333 lphdi->fmt |= HDF_STRING;
6334 lphdi->pszText = lpColumn->pszText;
6335 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6338 if (lpColumn->mask & LVCF_IMAGE)
6340 lphdi->mask |= HDI_IMAGE;
6341 lphdi->iImage = lpColumn->iImage;
6344 if (lpColumn->mask & LVCF_ORDER)
6346 lphdi->mask |= HDI_ORDER;
6347 lphdi->iOrder = lpColumn->iOrder;
6354 * Inserts a new column.
6357 * [I] infoPtr : valid pointer to the listview structure
6358 * [I] nColumn : column index
6359 * [I] lpColumn : column information
6360 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6363 * SUCCESS : new column index
6366 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6367 const LVCOLUMNW *lpColumn, BOOL isW)
6369 COLUMN_INFO *lpColumnInfo;
6373 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6375 if (!lpColumn || nColumn < 0) return -1;
6376 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6378 ZeroMemory(&hdi, sizeof(HDITEMW));
6379 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6381 /* insert item in header control */
6382 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6383 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6384 (WPARAM)nColumn, (LPARAM)&hdi);
6385 if (nNewColumn == -1) return -1;
6386 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6388 /* create our own column info */
6389 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6390 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6392 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6393 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6395 /* now we have to actually adjust the data */
6396 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6398 SUBITEM_INFO *lpSubItem;
6402 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6404 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6405 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6407 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6408 if (lpSubItem->iSubItem >= nNewColumn)
6409 lpSubItem->iSubItem++;
6414 /* make space for the new column */
6415 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6420 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6423 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6431 * Sets the attributes of a header item.
6434 * [I] infoPtr : valid pointer to the listview structure
6435 * [I] nColumn : column index
6436 * [I] lpColumn : column attributes
6437 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6443 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6444 const LVCOLUMNW *lpColumn, BOOL isW)
6446 HDITEMW hdi, hdiget;
6449 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6451 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6453 ZeroMemory(&hdi, sizeof(HDITEMW));
6454 if (lpColumn->mask & LVCF_FMT)
6456 hdi.mask |= HDI_FORMAT;
6457 hdiget.mask = HDI_FORMAT;
6458 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6459 hdi.fmt = hdiget.fmt & HDF_STRING;
6461 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6463 /* set header item attributes */
6464 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6465 if (!bResult) return FALSE;
6467 if (lpColumn->mask & LVCF_FMT)
6469 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6470 int oldFmt = lpColumnInfo->fmt;
6472 lpColumnInfo->fmt = lpColumn->fmt;
6473 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6475 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6476 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6485 * Sets the column order array
6488 * [I] infoPtr : valid pointer to the listview structure
6489 * [I] iCount : number of elements in column order array
6490 * [I] lpiArray : pointer to column order array
6496 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6498 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6509 * Sets the width of a column
6512 * [I] infoPtr : valid pointer to the listview structure
6513 * [I] nColumn : column index
6514 * [I] cx : column width
6520 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6522 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6523 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6527 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6529 /* set column width only if in report or list mode */
6530 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6532 /* take care of invalid cx values */
6533 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6534 else if (uView == LVS_LIST && cx < 1) return FALSE;
6536 /* resize all columns if in LVS_LIST mode */
6537 if(uView == LVS_LIST)
6539 infoPtr->nItemWidth = cx;
6540 LISTVIEW_InvalidateList(infoPtr);
6544 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6546 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6551 lvItem.mask = LVIF_TEXT;
6553 lvItem.iSubItem = nColumn;
6554 lvItem.pszText = szDispText;
6555 lvItem.cchTextMax = DISP_TEXT_SIZE;
6556 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6558 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6559 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6560 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6562 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6563 max_cx += infoPtr->iconSize.cx;
6564 max_cx += TRAILING_LABEL_PADDING;
6567 /* autosize based on listview items width */
6568 if(cx == LVSCW_AUTOSIZE)
6570 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6572 /* if iCol is the last column make it fill the remainder of the controls width */
6573 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6578 LISTVIEW_GetOrigin(infoPtr, &Origin);
6579 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6581 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6585 /* Despite what the MS docs say, if this is not the last
6586 column, then MS resizes the column to the width of the
6587 largest text string in the column, including headers
6588 and items. This is different from LVSCW_AUTOSIZE in that
6589 LVSCW_AUTOSIZE ignores the header string length. */
6592 /* retrieve header text */
6593 hdi.mask = HDI_TEXT;
6594 hdi.cchTextMax = DISP_TEXT_SIZE;
6595 hdi.pszText = szDispText;
6596 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6598 HDC hdc = GetDC(infoPtr->hwndSelf);
6599 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6602 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6603 cx = size.cx + TRAILING_HEADER_PADDING;
6604 /* FIXME: Take into account the header image, if one is present */
6605 SelectObject(hdc, old_font);
6606 ReleaseDC(infoPtr->hwndSelf, hdc);
6608 cx = max (cx, max_cx);
6612 if (cx < 0) return FALSE;
6614 /* call header to update the column change */
6615 hdi.mask = HDI_WIDTH;
6617 TRACE("hdi.cxy=%d\n", hdi.cxy);
6618 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6622 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6625 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6628 HBITMAP hbm_im, hbm_mask, hbm_orig;
6630 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6631 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6634 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6635 ILC_COLOR | ILC_MASK, 2, 2);
6636 hdc_wnd = GetDC(infoPtr->hwndSelf);
6637 hdc = CreateCompatibleDC(hdc_wnd);
6638 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6639 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6640 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6642 rc.left = rc.top = 0;
6643 rc.right = GetSystemMetrics(SM_CXSMICON);
6644 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6646 hbm_orig = SelectObject(hdc, hbm_mask);
6647 FillRect(hdc, &rc, hbr_white);
6648 InflateRect(&rc, -3, -3);
6649 FillRect(hdc, &rc, hbr_black);
6651 SelectObject(hdc, hbm_im);
6652 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6653 SelectObject(hdc, hbm_orig);
6654 ImageList_Add(himl, hbm_im, hbm_mask);
6656 SelectObject(hdc, hbm_im);
6657 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6658 SelectObject(hdc, hbm_orig);
6659 ImageList_Add(himl, hbm_im, hbm_mask);
6661 DeleteObject(hbm_mask);
6662 DeleteObject(hbm_im);
6670 * Sets the extended listview style.
6673 * [I] infoPtr : valid pointer to the listview structure
6675 * [I] dwStyle : style
6678 * SUCCESS : previous style
6681 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6683 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6687 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6689 infoPtr->dwLvExStyle = dwStyle;
6691 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6693 HIMAGELIST himl = 0;
6694 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6695 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6696 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6704 * Sets the new hot cursor used during hot tracking and hover selection.
6707 * [I] infoPtr : valid pointer to the listview structure
6708 * [I} hCurosr : the new hot cursor handle
6711 * Returns the previous hot cursor
6713 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6715 HCURSOR oldCursor = infoPtr->hHotCursor;
6717 infoPtr->hHotCursor = hCursor;
6725 * Sets the hot item index.
6728 * [I] infoPtr : valid pointer to the listview structure
6729 * [I] iIndex : index
6732 * SUCCESS : previous hot item index
6733 * FAILURE : -1 (no hot item)
6735 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6737 INT iOldIndex = infoPtr->nHotItem;
6739 infoPtr->nHotItem = iIndex;
6747 * Sets the amount of time the cursor must hover over an item before it is selected.
6750 * [I] infoPtr : valid pointer to the listview structure
6751 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6754 * Returns the previous hover time
6756 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6758 DWORD oldHoverTime = infoPtr->dwHoverTime;
6760 infoPtr->dwHoverTime = dwHoverTime;
6762 return oldHoverTime;
6767 * Sets spacing for icons of LVS_ICON style.
6770 * [I] infoPtr : valid pointer to the listview structure
6771 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6772 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6775 * MAKELONG(oldcx, oldcy)
6777 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6779 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6780 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6782 TRACE("requested=(%d,%d)\n", cx, cy);
6784 /* this is supported only for LVS_ICON style */
6785 if (uView != LVS_ICON) return oldspacing;
6787 /* set to defaults, if instructed to */
6788 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6789 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6791 /* if 0 then compute width
6792 * FIXME: Should scan each item and determine max width of
6793 * icon or label, then make that the width */
6795 cx = infoPtr->iconSpacing.cx;
6797 /* if 0 then compute height */
6799 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6800 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6803 infoPtr->iconSpacing.cx = cx;
6804 infoPtr->iconSpacing.cy = cy;
6806 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6807 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6808 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6809 infoPtr->ntmHeight);
6811 /* these depend on the iconSpacing */
6812 LISTVIEW_UpdateItemSize(infoPtr);
6817 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6821 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6828 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6829 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6838 * [I] infoPtr : valid pointer to the listview structure
6839 * [I] nType : image list type
6840 * [I] himl : image list handle
6843 * SUCCESS : old image list
6846 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6848 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6849 INT oldHeight = infoPtr->nItemHeight;
6850 HIMAGELIST himlOld = 0;
6852 TRACE("(nType=%d, himl=%p\n", nType, himl);
6857 himlOld = infoPtr->himlNormal;
6858 infoPtr->himlNormal = himl;
6859 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6860 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6864 himlOld = infoPtr->himlSmall;
6865 infoPtr->himlSmall = himl;
6866 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6870 himlOld = infoPtr->himlState;
6871 infoPtr->himlState = himl;
6872 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6873 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6877 ERR("Unknown icon type=%d\n", nType);
6881 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6882 if (infoPtr->nItemHeight != oldHeight)
6883 LISTVIEW_UpdateScroll(infoPtr);
6890 * Preallocates memory (does *not* set the actual count of items !)
6893 * [I] infoPtr : valid pointer to the listview structure
6894 * [I] nItems : item count (projected number of items to allocate)
6895 * [I] dwFlags : update flags
6901 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6903 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6905 if (infoPtr->dwStyle & LVS_OWNERDATA)
6907 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6908 INT nOldCount = infoPtr->nItemCount;
6910 if (nItems < nOldCount)
6912 RANGE range = { nItems, nOldCount };
6913 ranges_del(infoPtr->selectionRanges, range);
6914 if (infoPtr->nFocusedItem >= nItems)
6916 infoPtr->nFocusedItem = -1;
6917 SetRectEmpty(&infoPtr->rcFocus);
6921 infoPtr->nItemCount = nItems;
6922 LISTVIEW_UpdateScroll(infoPtr);
6924 /* the flags are valid only in ownerdata report and list modes */
6925 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6927 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6928 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6930 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6931 LISTVIEW_InvalidateList(infoPtr);
6938 LISTVIEW_GetOrigin(infoPtr, &Origin);
6939 nFrom = min(nOldCount, nItems);
6940 nTo = max(nOldCount, nItems);
6942 if (uView == LVS_REPORT)
6945 rcErase.top = nFrom * infoPtr->nItemHeight;
6946 rcErase.right = infoPtr->nItemWidth;
6947 rcErase.bottom = nTo * infoPtr->nItemHeight;
6948 OffsetRect(&rcErase, Origin.x, Origin.y);
6949 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6950 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6954 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6956 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6957 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6958 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6959 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6960 OffsetRect(&rcErase, Origin.x, Origin.y);
6961 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6962 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6964 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6966 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6967 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6968 OffsetRect(&rcErase, Origin.x, Origin.y);
6969 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6970 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6976 /* According to MSDN for non-LVS_OWNERDATA this is just
6977 * a performance issue. The control allocates its internal
6978 * data structures for the number of items specified. It
6979 * cuts down on the number of memory allocations. Therefore
6980 * we will just issue a WARN here
6982 WARN("for non-ownerdata performance option not implemented.\n");
6990 * Sets the position of an item.
6993 * [I] infoPtr : valid pointer to the listview structure
6994 * [I] nItem : item index
6995 * [I] pt : coordinate
7001 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
7003 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7006 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
7008 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7009 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7011 LISTVIEW_GetOrigin(infoPtr, &Origin);
7013 /* This point value seems to be an undocumented feature.
7014 * The best guess is that it means either at the origin,
7015 * or at true beginning of the list. I will assume the origin. */
7016 if ((pt.x == -1) && (pt.y == -1))
7019 if (uView == LVS_ICON)
7021 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7022 pt.y -= ICON_TOP_PADDING;
7027 infoPtr->bAutoarrange = FALSE;
7029 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7034 * Sets the state of one or many items.
7037 * [I] infoPtr : valid pointer to the listview structure
7038 * [I] nItem : item index
7039 * [I] lpLVItem : item or subitem info
7045 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7047 BOOL bResult = TRUE;
7050 lvItem.iItem = nItem;
7051 lvItem.iSubItem = 0;
7052 lvItem.mask = LVIF_STATE;
7053 lvItem.state = lpLVItem->state;
7054 lvItem.stateMask = lpLVItem->stateMask;
7055 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7059 /* apply to all items */
7060 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7061 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7064 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7067 *update selection mark
7069 * Investigation on windows 2k showed that selection mark was updated
7070 * whenever a new selection was made, but if the selected item was
7071 * unselected it was not updated.
7073 * we are probably still not 100% accurate, but this at least sets the
7074 * proper selection mark when it is needed
7077 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7078 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7081 infoPtr->nSelectionMark = -1;
7082 for (i = 0; i < infoPtr->nItemCount; i++)
7084 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7086 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7088 infoPtr->nSelectionMark = i;
7092 else if (ranges_contain(infoPtr->selectionRanges, i))
7094 infoPtr->nSelectionMark = i;
7105 * Sets the text of an item or subitem.
7108 * [I] hwnd : window handle
7109 * [I] nItem : item index
7110 * [I] lpLVItem : item or subitem info
7111 * [I] isW : TRUE if input is Unicode
7117 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7121 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7123 lvItem.iItem = nItem;
7124 lvItem.iSubItem = lpLVItem->iSubItem;
7125 lvItem.mask = LVIF_TEXT;
7126 lvItem.pszText = lpLVItem->pszText;
7127 lvItem.cchTextMax = lpLVItem->cchTextMax;
7129 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7131 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7136 * Set item index that marks the start of a multiple selection.
7139 * [I] infoPtr : valid pointer to the listview structure
7140 * [I] nIndex : index
7143 * Index number or -1 if there is no selection mark.
7145 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7147 INT nOldIndex = infoPtr->nSelectionMark;
7149 TRACE("(nIndex=%d)\n", nIndex);
7151 infoPtr->nSelectionMark = nIndex;
7158 * Sets the text background color.
7161 * [I] infoPtr : valid pointer to the listview structure
7162 * [I] clrTextBk : text background color
7168 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7170 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7172 if (infoPtr->clrTextBk != clrTextBk)
7174 infoPtr->clrTextBk = clrTextBk;
7175 LISTVIEW_InvalidateList(infoPtr);
7183 * Sets the text foreground color.
7186 * [I] infoPtr : valid pointer to the listview structure
7187 * [I] clrText : text color
7193 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7195 TRACE("(clrText=%lx)\n", clrText);
7197 if (infoPtr->clrText != clrText)
7199 infoPtr->clrText = clrText;
7200 LISTVIEW_InvalidateList(infoPtr);
7208 * Determines which listview item is located at the specified position.
7211 * [I] infoPtr : valid pointer to the listview structure
7212 * [I] hwndNewToolTip : handle to new ToolTip
7217 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7219 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7220 infoPtr->hwndToolTip = hwndNewToolTip;
7221 return hwndOldToolTip;
7224 /* LISTVIEW_SetUnicodeFormat */
7225 /* LISTVIEW_SetWorkAreas */
7229 * Callback internally used by LISTVIEW_SortItems()
7232 * [I] first : pointer to first ITEM_INFO to compare
7233 * [I] second : pointer to second ITEM_INFO to compare
7234 * [I] lParam : HWND of control
7237 * if first comes before second : negative
7238 * if first comes after second : positive
7239 * if first and second are equivalent : zero
7241 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7243 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7244 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7245 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7247 /* Forward the call to the client defined callback */
7248 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7253 * Sorts the listview items.
7256 * [I] infoPtr : valid pointer to the listview structure
7257 * [I] pfnCompare : application-defined value
7258 * [I] lParamSort : pointer to comparision callback
7264 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7266 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7269 LPVOID selectionMarkItem;
7273 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7275 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7277 if (!infoPtr->hdpaItems) return FALSE;
7279 /* if there are 0 or 1 items, there is no need to sort */
7280 if (infoPtr->nItemCount < 2) return TRUE;
7282 if (infoPtr->nFocusedItem >= 0)
7284 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7285 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7286 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7288 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7289 /* clear the lpItem->state for non-selected ones */
7290 /* remove the selection ranges */
7292 infoPtr->pfnCompare = pfnCompare;
7293 infoPtr->lParamSort = lParamSort;
7294 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7296 /* Adjust selections and indices so that they are the way they should
7297 * be after the sort (otherwise, the list items move around, but
7298 * whatever is at the item's previous original position will be
7301 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7302 for (i=0; i < infoPtr->nItemCount; i++)
7304 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7305 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7307 if (lpItem->state & LVIS_SELECTED)
7309 item.state = LVIS_SELECTED;
7310 item.stateMask = LVIS_SELECTED;
7311 LISTVIEW_SetItemState(infoPtr, i, &item);
7313 if (lpItem->state & LVIS_FOCUSED)
7315 infoPtr->nFocusedItem = i;
7316 lpItem->state &= ~LVIS_FOCUSED;
7319 if (selectionMarkItem != NULL)
7320 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7321 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7323 /* refresh the display */
7324 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7325 LISTVIEW_InvalidateList(infoPtr);
7332 * Updates an items or rearranges the listview control.
7335 * [I] infoPtr : valid pointer to the listview structure
7336 * [I] nItem : item index
7342 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7344 TRACE("(nItem=%d)\n", nItem);
7346 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7348 /* rearrange with default alignment style */
7349 if (is_autoarrange(infoPtr))
7350 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7352 LISTVIEW_InvalidateItem(infoPtr, nItem);
7360 * Creates the listview control.
7363 * [I] hwnd : window handle
7364 * [I] lpcs : the create parameters
7370 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7372 LISTVIEW_INFO *infoPtr;
7373 UINT uView = lpcs->style & LVS_TYPEMASK;
7376 TRACE("(lpcs=%p)\n", lpcs);
7378 /* initialize info pointer */
7379 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7380 if (!infoPtr) return -1;
7382 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
7384 infoPtr->hwndSelf = hwnd;
7385 infoPtr->dwStyle = lpcs->style;
7386 /* determine the type of structures to use */
7387 infoPtr->hwndNotify = lpcs->hwndParent;
7388 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7389 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7391 /* initialize color information */
7392 infoPtr->clrBk = CLR_NONE;
7393 infoPtr->clrText = comctl32_color.clrWindowText;
7394 infoPtr->clrTextBk = CLR_DEFAULT;
7395 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7397 /* set default values */
7398 infoPtr->nFocusedItem = -1;
7399 infoPtr->nSelectionMark = -1;
7400 infoPtr->nHotItem = -1;
7401 infoPtr->bRedraw = TRUE;
7402 infoPtr->bNoItemMetrics = TRUE;
7403 infoPtr->bDoChangeNotify = TRUE;
7404 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7405 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7406 infoPtr->nEditLabelItem = -1;
7407 infoPtr->dwHoverTime = -1; /* default system hover time */
7409 /* get default font (icon title) */
7410 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7411 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7412 infoPtr->hFont = infoPtr->hDefaultFont;
7413 LISTVIEW_SaveTextMetrics(infoPtr);
7416 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7417 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7418 0, 0, 0, 0, hwnd, NULL,
7419 lpcs->hInstance, NULL);
7420 if (!infoPtr->hwndHeader) goto fail;
7422 /* set header unicode format */
7423 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7425 /* set header font */
7426 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7428 /* allocate memory for the data structure */
7429 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7430 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7431 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7432 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7433 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7435 /* initialize the icon sizes */
7436 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7437 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7439 /* init item size to avoid division by 0 */
7440 LISTVIEW_UpdateItemSize (infoPtr);
7442 if (uView == LVS_REPORT)
7444 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7446 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7450 /* set HDS_HIDDEN flag to hide the header bar */
7451 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7452 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7459 DestroyWindow(infoPtr->hwndHeader);
7460 ranges_destroy(infoPtr->selectionRanges);
7461 DPA_Destroy(infoPtr->hdpaItems);
7462 DPA_Destroy(infoPtr->hdpaPosX);
7463 DPA_Destroy(infoPtr->hdpaPosY);
7464 DPA_Destroy(infoPtr->hdpaColumns);
7471 * Erases the background of the listview control.
7474 * [I] infoPtr : valid pointer to the listview structure
7475 * [I] hdc : device context handle
7481 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7485 TRACE("(hdc=%p)\n", hdc);
7487 if (!GetClipBox(hdc, &rc)) return FALSE;
7489 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7495 * Helper function for LISTVIEW_[HV]Scroll *only*.
7496 * Performs vertical/horizontal scrolling by a give amount.
7499 * [I] infoPtr : valid pointer to the listview structure
7500 * [I] dx : amount of horizontal scroll
7501 * [I] dy : amount of vertical scroll
7503 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7505 /* now we can scroll the list */
7506 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7507 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7508 /* if we have focus, adjust rect */
7509 OffsetRect(&infoPtr->rcFocus, dx, dy);
7510 UpdateWindow(infoPtr->hwndSelf);
7515 * Performs vertical scrolling.
7518 * [I] infoPtr : valid pointer to the listview structure
7519 * [I] nScrollCode : scroll code
7520 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7521 * [I] hScrollWnd : scrollbar control window handle
7527 * SB_LINEUP/SB_LINEDOWN:
7528 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7529 * for LVS_REPORT is 1 line
7530 * for LVS_LIST cannot occur
7533 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7534 INT nScrollDiff, HWND hScrollWnd)
7536 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7537 INT nOldScrollPos, nNewScrollPos;
7538 SCROLLINFO scrollInfo;
7541 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7542 debugscrollcode(nScrollCode), nScrollDiff);
7544 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7546 scrollInfo.cbSize = sizeof(SCROLLINFO);
7547 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7549 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7551 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7553 nOldScrollPos = scrollInfo.nPos;
7554 switch (nScrollCode)
7560 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7564 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7568 nScrollDiff = -scrollInfo.nPage;
7572 nScrollDiff = scrollInfo.nPage;
7575 case SB_THUMBPOSITION:
7577 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7584 /* quit right away if pos isn't changing */
7585 if (nScrollDiff == 0) return 0;
7587 /* calculate new position, and handle overflows */
7588 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7589 if (nScrollDiff > 0) {
7590 if (nNewScrollPos < nOldScrollPos ||
7591 nNewScrollPos > scrollInfo.nMax)
7592 nNewScrollPos = scrollInfo.nMax;
7594 if (nNewScrollPos > nOldScrollPos ||
7595 nNewScrollPos < scrollInfo.nMin)
7596 nNewScrollPos = scrollInfo.nMin;
7599 /* set the new position, and reread in case it changed */
7600 scrollInfo.fMask = SIF_POS;
7601 scrollInfo.nPos = nNewScrollPos;
7602 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7604 /* carry on only if it really changed */
7605 if (nNewScrollPos == nOldScrollPos) return 0;
7607 /* now adjust to client coordinates */
7608 nScrollDiff = nOldScrollPos - nNewScrollPos;
7609 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7611 /* and scroll the window */
7612 scroll_list(infoPtr, 0, nScrollDiff);
7619 * Performs horizontal scrolling.
7622 * [I] infoPtr : valid pointer to the listview structure
7623 * [I] nScrollCode : scroll code
7624 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7625 * [I] hScrollWnd : scrollbar control window handle
7631 * SB_LINELEFT/SB_LINERIGHT:
7632 * for LVS_ICON, LVS_SMALLICON 1 pixel
7633 * for LVS_REPORT is 1 pixel
7634 * for LVS_LIST is 1 column --> which is a 1 because the
7635 * scroll is based on columns not pixels
7638 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7639 INT nScrollDiff, HWND hScrollWnd)
7641 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7642 INT nOldScrollPos, nNewScrollPos;
7643 SCROLLINFO scrollInfo;
7645 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7646 debugscrollcode(nScrollCode), nScrollDiff);
7648 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7650 scrollInfo.cbSize = sizeof(SCROLLINFO);
7651 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7653 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7655 nOldScrollPos = scrollInfo.nPos;
7657 switch (nScrollCode)
7671 nScrollDiff = -scrollInfo.nPage;
7675 nScrollDiff = scrollInfo.nPage;
7678 case SB_THUMBPOSITION:
7680 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7687 /* quit right away if pos isn't changing */
7688 if (nScrollDiff == 0) return 0;
7690 /* calculate new position, and handle overflows */
7691 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7692 if (nScrollDiff > 0) {
7693 if (nNewScrollPos < nOldScrollPos ||
7694 nNewScrollPos > scrollInfo.nMax)
7695 nNewScrollPos = scrollInfo.nMax;
7697 if (nNewScrollPos > nOldScrollPos ||
7698 nNewScrollPos < scrollInfo.nMin)
7699 nNewScrollPos = scrollInfo.nMin;
7702 /* set the new position, and reread in case it changed */
7703 scrollInfo.fMask = SIF_POS;
7704 scrollInfo.nPos = nNewScrollPos;
7705 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7707 /* carry on only if it really changed */
7708 if (nNewScrollPos == nOldScrollPos) return 0;
7710 if(uView == LVS_REPORT)
7711 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7713 /* now adjust to client coordinates */
7714 nScrollDiff = nOldScrollPos - nNewScrollPos;
7715 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7717 /* and scroll the window */
7718 scroll_list(infoPtr, nScrollDiff, 0);
7723 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7725 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7726 INT gcWheelDelta = 0;
7727 UINT pulScrollLines = 3;
7728 SCROLLINFO scrollInfo;
7730 TRACE("(wheelDelta=%d)\n", wheelDelta);
7732 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7733 gcWheelDelta -= wheelDelta;
7735 scrollInfo.cbSize = sizeof(SCROLLINFO);
7736 scrollInfo.fMask = SIF_POS;
7743 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7744 * should be fixed in the future.
7746 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7747 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7751 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7753 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7754 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7755 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7760 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7771 * [I] infoPtr : valid pointer to the listview structure
7772 * [I] nVirtualKey : virtual key
7773 * [I] lKeyData : key data
7778 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7780 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7782 NMLVKEYDOWN nmKeyDown;
7784 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7786 /* send LVN_KEYDOWN notification */
7787 nmKeyDown.wVKey = nVirtualKey;
7788 nmKeyDown.flags = 0;
7789 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7791 switch (nVirtualKey)
7794 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7796 notify(infoPtr, NM_RETURN);
7797 notify(infoPtr, LVN_ITEMACTIVATE);
7802 if (infoPtr->nItemCount > 0)
7807 if (infoPtr->nItemCount > 0)
7808 nItem = infoPtr->nItemCount - 1;
7812 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7816 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7820 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7824 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7828 if (uView == LVS_REPORT)
7829 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7831 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7832 * LISTVIEW_GetCountPerRow(infoPtr);
7833 if(nItem < 0) nItem = 0;
7837 if (uView == LVS_REPORT)
7838 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7840 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7841 * LISTVIEW_GetCountPerRow(infoPtr);
7842 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7846 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7847 LISTVIEW_KeySelection(infoPtr, nItem);
7857 * [I] infoPtr : valid pointer to the listview structure
7862 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7866 /* if we did not have the focus, there's nothing to do */
7867 if (!infoPtr->bFocus) return 0;
7869 /* send NM_KILLFOCUS notification */
7870 notify(infoPtr, NM_KILLFOCUS);
7872 /* if we have a focus rectagle, get rid of it */
7873 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7875 /* set window focus flag */
7876 infoPtr->bFocus = FALSE;
7878 /* invalidate the selected items before reseting focus flag */
7879 LISTVIEW_InvalidateSelectedItems(infoPtr);
7887 * Track mouse/dragging
7890 * [I] infoPtr : valid pointer to the listview structure
7891 * [I] pt : mouse coordinate
7896 static LRESULT LISTVIEW_TrackMouse(LISTVIEW_INFO *infoPtr, POINT pt)
7898 INT cxDrag = GetSystemMetrics(SM_CXDRAG);
7899 INT cyDrag = GetSystemMetrics(SM_CYDRAG);
7905 r.top = pt.y - cyDrag;
7906 r.left = pt.x - cxDrag;
7907 r.bottom = pt.y + cyDrag;
7908 r.right = pt.x + cxDrag;
7910 SetCapture(infoPtr->hwndSelf);
7914 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
7916 if (msg.message == WM_MOUSEMOVE)
7918 pt.x = (short)LOWORD(msg.lParam);
7919 pt.y = (short)HIWORD(msg.lParam);
7920 if (PtInRect(&r, pt))
7928 else if (msg.message >= WM_LBUTTONDOWN &&
7929 msg.message <= WM_RBUTTONDBLCLK)
7934 DispatchMessageW(&msg);
7937 if (GetCapture() != infoPtr->hwndSelf)
7948 * Processes double click messages (left mouse button).
7951 * [I] infoPtr : valid pointer to the listview structure
7952 * [I] wKey : key flag
7953 * [I] pts : mouse coordinate
7958 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7960 LVHITTESTINFO htInfo;
7962 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7964 /* send NM_RELEASEDCAPTURE notification */
7965 notify(infoPtr, NM_RELEASEDCAPTURE);
7967 htInfo.pt.x = pts.x;
7968 htInfo.pt.y = pts.y;
7970 /* send NM_DBLCLK notification */
7971 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7972 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7974 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7975 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
7982 * Processes mouse down messages (left mouse button).
7985 * [I] infoPtr : valid pointer to the listview structure
7986 * [I] wKey : key flag
7987 * [I] pts : mouse coordinate
7992 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7994 LVHITTESTINFO lvHitTestInfo;
7995 static BOOL bGroupSelect = TRUE;
7996 POINT pt = { pts.x, pts.y };
7999 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
8001 /* send NM_RELEASEDCAPTURE notification */
8002 notify(infoPtr, NM_RELEASEDCAPTURE);
8004 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8006 /* set left button down flag */
8007 infoPtr->bLButtonDown = TRUE;
8009 lvHitTestInfo.pt.x = pts.x;
8010 lvHitTestInfo.pt.y = pts.y;
8012 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8013 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
8014 infoPtr->nEditLabelItem = -1;
8015 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8017 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8019 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
8020 if(state == 1 || state == 2)
8024 lvitem.state = state << 12;
8025 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8026 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8030 if (LISTVIEW_TrackMouse(infoPtr, lvHitTestInfo.pt))
8034 ZeroMemory(&nmlv, sizeof(nmlv));
8036 nmlv.ptAction.x = lvHitTestInfo.pt.x;
8037 nmlv.ptAction.y = lvHitTestInfo.pt.y;
8039 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
8044 if (infoPtr->dwStyle & LVS_SINGLESEL)
8046 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8047 infoPtr->nEditLabelItem = nItem;
8049 LISTVIEW_SetSelection(infoPtr, nItem);
8053 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8057 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8058 LISTVIEW_SetItemFocus(infoPtr, nItem);
8059 infoPtr->nSelectionMark = nItem;
8065 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8066 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8068 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8069 infoPtr->nSelectionMark = nItem;
8072 else if (wKey & MK_CONTROL)
8076 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8078 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8079 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8080 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8081 infoPtr->nSelectionMark = nItem;
8083 else if (wKey & MK_SHIFT)
8085 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8089 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8090 infoPtr->nEditLabelItem = nItem;
8092 /* set selection (clears other pre-existing selections) */
8093 LISTVIEW_SetSelection(infoPtr, nItem);
8099 /* remove all selections */
8100 LISTVIEW_DeselectAll(infoPtr);
8109 * Processes mouse up messages (left mouse button).
8112 * [I] infoPtr : valid pointer to the listview structure
8113 * [I] wKey : key flag
8114 * [I] pts : mouse coordinate
8119 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8121 LVHITTESTINFO lvHitTestInfo;
8123 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
8125 if (!infoPtr->bLButtonDown) return 0;
8127 lvHitTestInfo.pt.x = pts.x;
8128 lvHitTestInfo.pt.y = pts.y;
8130 /* send NM_CLICK notification */
8131 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8132 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8134 /* set left button flag */
8135 infoPtr->bLButtonDown = FALSE;
8137 /* if we clicked on a selected item, edit the label */
8138 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8139 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8146 * Destroys the listview control (called after WM_DESTROY).
8149 * [I] infoPtr : valid pointer to the listview structure
8154 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8158 /* delete all items */
8159 LISTVIEW_DeleteAllItems(infoPtr);
8161 /* destroy data structure */
8162 DPA_Destroy(infoPtr->hdpaItems);
8163 DPA_Destroy(infoPtr->hdpaPosX);
8164 DPA_Destroy(infoPtr->hdpaPosY);
8165 DPA_Destroy(infoPtr->hdpaColumns);
8166 ranges_destroy(infoPtr->selectionRanges);
8168 /* destroy image lists */
8169 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8171 if (infoPtr->himlNormal)
8172 ImageList_Destroy(infoPtr->himlNormal);
8173 if (infoPtr->himlSmall)
8174 ImageList_Destroy(infoPtr->himlSmall);
8175 if (infoPtr->himlState)
8176 ImageList_Destroy(infoPtr->himlState);
8179 /* destroy font, bkgnd brush */
8181 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8182 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8184 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
8186 /* free listview info pointer*/
8194 * Handles notifications from header.
8197 * [I] infoPtr : valid pointer to the listview structure
8198 * [I] nCtrlId : control identifier
8199 * [I] lpnmh : notification information
8204 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8206 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8208 TRACE("(lpnmh=%p)\n", lpnmh);
8210 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8212 switch (lpnmh->hdr.code)
8216 case HDN_ITEMCHANGEDW:
8217 case HDN_ITEMCHANGEDA:
8219 COLUMN_INFO *lpColumnInfo;
8222 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8226 hdi.mask = HDI_WIDTH;
8227 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8231 cxy = lpnmh->pitem->cxy;
8233 /* determine how much we change since the last know position */
8234 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8235 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8238 RECT rcCol = lpColumnInfo->rcHeader;
8240 lpColumnInfo->rcHeader.right += dx;
8241 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8242 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8244 /* this trick works for left aligned columns only */
8245 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8247 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8248 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8250 rcCol.top = infoPtr->rcList.top;
8251 rcCol.bottom = infoPtr->rcList.bottom;
8252 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8258 case HDN_ITEMCLICKW:
8259 case HDN_ITEMCLICKA:
8261 /* Handle sorting by Header Column */
8264 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8266 nmlv.iSubItem = lpnmh->iItem;
8267 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8277 * Determines the type of structure to use.
8280 * [I] infoPtr : valid pointer to the listview structureof the sender
8281 * [I] hwndFrom : listview window handle
8282 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8287 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8289 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8291 if (nCommand != NF_REQUERY) return 0;
8293 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8300 * Paints/Repaints the listview control.
8303 * [I] infoPtr : valid pointer to the listview structure
8304 * [I] hdc : device context handle
8309 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8311 TRACE("(hdc=%p)\n", hdc);
8313 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8315 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8317 infoPtr->bNoItemMetrics = FALSE;
8318 LISTVIEW_UpdateItemSize(infoPtr);
8319 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8320 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8321 LISTVIEW_UpdateScroll(infoPtr);
8324 LISTVIEW_Refresh(infoPtr, hdc);
8329 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8331 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8332 LISTVIEW_Refresh(infoPtr, hdc);
8333 EndPaint(infoPtr->hwndSelf, &ps);
8341 * Processes double click messages (right mouse button).
8344 * [I] infoPtr : valid pointer to the listview structure
8345 * [I] wKey : key flag
8346 * [I] pts : mouse coordinate
8351 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8353 LVHITTESTINFO lvHitTestInfo;
8355 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8357 /* send NM_RELEASEDCAPTURE notification */
8358 notify(infoPtr, NM_RELEASEDCAPTURE);
8360 /* send NM_RDBLCLK notification */
8361 lvHitTestInfo.pt.x = pts.x;
8362 lvHitTestInfo.pt.y = pts.y;
8363 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8364 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8371 * Processes mouse down messages (right mouse button).
8374 * [I] infoPtr : valid pointer to the listview structure
8375 * [I] wKey : key flag
8376 * [I] pts : mouse coordinate
8381 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8383 LVHITTESTINFO lvHitTestInfo;
8386 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8388 /* send NM_RELEASEDCAPTURE notification */
8389 notify(infoPtr, NM_RELEASEDCAPTURE);
8391 /* make sure the listview control window has the focus */
8392 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8394 /* set right button down flag */
8395 infoPtr->bRButtonDown = TRUE;
8397 /* determine the index of the selected item */
8398 lvHitTestInfo.pt.x = pts.x;
8399 lvHitTestInfo.pt.y = pts.y;
8400 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8402 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8404 LISTVIEW_SetItemFocus(infoPtr, nItem);
8405 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8406 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8407 LISTVIEW_SetSelection(infoPtr, nItem);
8411 LISTVIEW_DeselectAll(infoPtr);
8419 * Processes mouse up messages (right mouse button).
8422 * [I] infoPtr : valid pointer to the listview structure
8423 * [I] wKey : key flag
8424 * [I] pts : mouse coordinate
8429 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8431 LVHITTESTINFO lvHitTestInfo;
8434 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8436 if (!infoPtr->bRButtonDown) return 0;
8438 /* set button flag */
8439 infoPtr->bRButtonDown = FALSE;
8441 /* Send NM_RClICK notification */
8442 lvHitTestInfo.pt.x = pts.x;
8443 lvHitTestInfo.pt.y = pts.y;
8444 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8445 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8447 /* Change to screen coordinate for WM_CONTEXTMENU */
8448 pt = lvHitTestInfo.pt;
8449 ClientToScreen(infoPtr->hwndSelf, &pt);
8451 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8452 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8453 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8464 * [I] infoPtr : valid pointer to the listview structure
8465 * [I] hwnd : window handle of window containing the cursor
8466 * [I] nHittest : hit-test code
8467 * [I] wMouseMsg : ideintifier of the mouse message
8470 * TRUE if cursor is set
8473 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8475 LVHITTESTINFO lvHitTestInfo;
8477 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8479 if(!infoPtr->hHotCursor) return FALSE;
8481 GetCursorPos(&lvHitTestInfo.pt);
8482 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8484 SetCursor(infoPtr->hHotCursor);
8494 * [I] infoPtr : valid pointer to the listview structure
8495 * [I] hwndLoseFocus : handle of previously focused window
8500 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8502 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8504 /* if we have the focus already, there's nothing to do */
8505 if (infoPtr->bFocus) return 0;
8507 /* send NM_SETFOCUS notification */
8508 notify(infoPtr, NM_SETFOCUS);
8510 /* set window focus flag */
8511 infoPtr->bFocus = TRUE;
8513 /* put the focus rect back on */
8514 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8516 /* redraw all visible selected items */
8517 LISTVIEW_InvalidateSelectedItems(infoPtr);
8527 * [I] infoPtr : valid pointer to the listview structure
8528 * [I] fRedraw : font handle
8529 * [I] fRedraw : redraw flag
8534 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8536 HFONT oldFont = infoPtr->hFont;
8538 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8540 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8541 if (infoPtr->hFont == oldFont) return 0;
8543 LISTVIEW_SaveTextMetrics(infoPtr);
8545 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8546 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8548 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8555 * Message handling for WM_SETREDRAW.
8556 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8559 * [I] infoPtr : valid pointer to the listview structure
8560 * [I] bRedraw: state of redraw flag
8563 * DefWinProc return value
8565 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8567 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8569 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8570 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8572 infoPtr->bRedraw = bRedraw;
8574 if(!bRedraw) return 0;
8576 if (is_autoarrange(infoPtr))
8577 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8578 LISTVIEW_UpdateScroll(infoPtr);
8580 /* despite what the WM_SETREDRAW docs says, apps expect us
8581 * to invalidate the listview here... stupid! */
8582 LISTVIEW_InvalidateList(infoPtr);
8589 * Resizes the listview control. This function processes WM_SIZE
8590 * messages. At this time, the width and height are not used.
8593 * [I] infoPtr : valid pointer to the listview structure
8594 * [I] Width : new width
8595 * [I] Height : new height
8600 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8602 RECT rcOld = infoPtr->rcList;
8604 TRACE("(width=%d, height=%d)\n", Width, Height);
8606 LISTVIEW_UpdateSize(infoPtr);
8607 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8609 /* do not bother with display related stuff if we're not redrawing */
8610 if (!is_redrawing(infoPtr)) return 0;
8612 if (is_autoarrange(infoPtr))
8613 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8615 LISTVIEW_UpdateScroll(infoPtr);
8617 /* refresh all only for lists whose height changed significantly */
8618 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8619 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8620 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8621 LISTVIEW_InvalidateList(infoPtr);
8628 * Sets the size information.
8631 * [I] infoPtr : valid pointer to the listview structure
8636 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8638 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8640 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8642 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8644 if (uView == LVS_LIST)
8646 /* Apparently the "LIST" style is supposed to have the same
8647 * number of items in a column even if there is no scroll bar.
8648 * Since if a scroll bar already exists then the bottom is already
8649 * reduced, only reduce if the scroll bar does not currently exist.
8650 * The "2" is there to mimic the native control. I think it may be
8651 * related to either padding or edges. (GLA 7/2002)
8653 if (!(infoPtr->dwStyle & WS_HSCROLL))
8654 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8655 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8657 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8662 hl.prc = &infoPtr->rcList;
8664 Header_Layout(infoPtr->hwndHeader, &hl);
8666 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8668 infoPtr->rcList.top = max(wp.cy, 0);
8671 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8676 * Processes WM_STYLECHANGED messages.
8679 * [I] infoPtr : valid pointer to the listview structure
8680 * [I] wStyleType : window style type (normal or extended)
8681 * [I] lpss : window style information
8686 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8687 const STYLESTRUCT *lpss)
8689 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8690 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8692 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8693 wStyleType, lpss->styleOld, lpss->styleNew);
8695 if (wStyleType != GWL_STYLE) return 0;
8697 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8698 /* what if LVS_OWNERDATA changed? */
8699 /* or LVS_SINGLESEL */
8700 /* or LVS_SORT{AS,DES}CENDING */
8702 infoPtr->dwStyle = lpss->styleNew;
8704 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8705 ((lpss->styleNew & WS_HSCROLL) == 0))
8706 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8708 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8709 ((lpss->styleNew & WS_VSCROLL) == 0))
8710 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8712 if (uNewView != uOldView)
8714 SIZE oldIconSize = infoPtr->iconSize;
8717 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8718 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8720 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8721 SetRectEmpty(&infoPtr->rcFocus);
8723 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8724 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8726 if (uNewView == LVS_ICON)
8728 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8730 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8731 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8732 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8735 else if (uNewView == LVS_REPORT)
8740 hl.prc = &infoPtr->rcList;
8742 Header_Layout(infoPtr->hwndHeader, &hl);
8743 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8746 LISTVIEW_UpdateItemSize(infoPtr);
8749 if (uNewView == LVS_REPORT)
8750 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8752 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8753 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8754 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8756 /* update the size of the client area */
8757 LISTVIEW_UpdateSize(infoPtr);
8759 /* add scrollbars if needed */
8760 LISTVIEW_UpdateScroll(infoPtr);
8762 /* invalidate client area + erase background */
8763 LISTVIEW_InvalidateList(infoPtr);
8770 * Window procedure of the listview control.
8773 static LRESULT WINAPI
8774 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8776 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8778 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8780 if (!infoPtr && (uMsg != WM_CREATE))
8781 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8785 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8790 case LVM_APPROXIMATEVIEWRECT:
8791 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8792 LOWORD(lParam), HIWORD(lParam));
8794 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8796 /* case LVM_CANCELEDITLABEL: */
8798 case LVM_CREATEDRAGIMAGE:
8799 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8801 case LVM_DELETEALLITEMS:
8802 return LISTVIEW_DeleteAllItems(infoPtr);
8804 case LVM_DELETECOLUMN:
8805 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8807 case LVM_DELETEITEM:
8808 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8810 case LVM_EDITLABELW:
8811 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8813 case LVM_EDITLABELA:
8814 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8816 /* case LVM_ENABLEGROUPVIEW: */
8818 case LVM_ENSUREVISIBLE:
8819 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8822 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8825 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8827 case LVM_GETBKCOLOR:
8828 return infoPtr->clrBk;
8830 /* case LVM_GETBKIMAGE: */
8832 case LVM_GETCALLBACKMASK:
8833 return infoPtr->uCallbackMask;
8835 case LVM_GETCOLUMNA:
8836 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8838 case LVM_GETCOLUMNW:
8839 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8841 case LVM_GETCOLUMNORDERARRAY:
8842 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8844 case LVM_GETCOLUMNWIDTH:
8845 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8847 case LVM_GETCOUNTPERPAGE:
8848 return LISTVIEW_GetCountPerPage(infoPtr);
8850 case LVM_GETEDITCONTROL:
8851 return (LRESULT)infoPtr->hwndEdit;
8853 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8854 return infoPtr->dwLvExStyle;
8856 /* case LVM_GETGROUPINFO: */
8858 /* case LVM_GETGROUPMETRICS: */
8861 return (LRESULT)infoPtr->hwndHeader;
8863 case LVM_GETHOTCURSOR:
8864 return (LRESULT)infoPtr->hHotCursor;
8866 case LVM_GETHOTITEM:
8867 return infoPtr->nHotItem;
8869 case LVM_GETHOVERTIME:
8870 return infoPtr->dwHoverTime;
8872 case LVM_GETIMAGELIST:
8873 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8875 /* case LVM_GETINSERTMARK: */
8877 /* case LVM_GETINSERTMARKCOLOR: */
8879 /* case LVM_GETINSERTMARKRECT: */
8881 case LVM_GETISEARCHSTRINGA:
8882 case LVM_GETISEARCHSTRINGW:
8883 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8887 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8890 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8892 case LVM_GETITEMCOUNT:
8893 return infoPtr->nItemCount;
8895 case LVM_GETITEMPOSITION:
8896 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8898 case LVM_GETITEMRECT:
8899 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8901 case LVM_GETITEMSPACING:
8902 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8904 case LVM_GETITEMSTATE:
8905 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8907 case LVM_GETITEMTEXTA:
8908 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8910 case LVM_GETITEMTEXTW:
8911 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8913 case LVM_GETNEXTITEM:
8914 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8916 case LVM_GETNUMBEROFWORKAREAS:
8917 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8921 if (!lParam) return FALSE;
8922 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8925 /* case LVM_GETOUTLINECOLOR: */
8927 /* case LVM_GETSELECTEDCOLUMN: */
8929 case LVM_GETSELECTEDCOUNT:
8930 return LISTVIEW_GetSelectedCount(infoPtr);
8932 case LVM_GETSELECTIONMARK:
8933 return infoPtr->nSelectionMark;
8935 case LVM_GETSTRINGWIDTHA:
8936 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8938 case LVM_GETSTRINGWIDTHW:
8939 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8941 case LVM_GETSUBITEMRECT:
8942 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8944 case LVM_GETTEXTBKCOLOR:
8945 return infoPtr->clrTextBk;
8947 case LVM_GETTEXTCOLOR:
8948 return infoPtr->clrText;
8950 /* case LVM_GETTILEINFO: */
8952 /* case LVM_GETTILEVIEWINFO: */
8954 case LVM_GETTOOLTIPS:
8955 if( !infoPtr->hwndToolTip )
8956 infoPtr->hwndToolTip = COMCTL32_CreateToolTip( hwnd );
8957 return (LRESULT)infoPtr->hwndToolTip;
8959 case LVM_GETTOPINDEX:
8960 return LISTVIEW_GetTopIndex(infoPtr);
8962 /*case LVM_GETUNICODEFORMAT:
8963 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8966 /* case LVM_GETVIEW: */
8968 case LVM_GETVIEWRECT:
8969 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8971 case LVM_GETWORKAREAS:
8972 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8975 /* case LVM_HASGROUP: */
8978 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8980 case LVM_INSERTCOLUMNA:
8981 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8983 case LVM_INSERTCOLUMNW:
8984 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8986 /* case LVM_INSERTGROUP: */
8988 /* case LVM_INSERTGROUPSORTED: */
8990 case LVM_INSERTITEMA:
8991 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8993 case LVM_INSERTITEMW:
8994 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8996 /* case LVM_INSERTMARKHITTEST: */
8998 /* case LVM_ISGROUPVIEWENABLED: */
9000 /* case LVM_MAPIDTOINDEX: */
9002 /* case LVM_MAPINDEXTOID: */
9004 /* case LVM_MOVEGROUP: */
9006 /* case LVM_MOVEITEMTOGROUP: */
9008 case LVM_REDRAWITEMS:
9009 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9011 /* case LVM_REMOVEALLGROUPS: */
9013 /* case LVM_REMOVEGROUP: */
9016 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9018 case LVM_SETBKCOLOR:
9019 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9021 /* case LVM_SETBKIMAGE: */
9023 case LVM_SETCALLBACKMASK:
9024 infoPtr->uCallbackMask = (UINT)wParam;
9027 case LVM_SETCOLUMNA:
9028 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9030 case LVM_SETCOLUMNW:
9031 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9033 case LVM_SETCOLUMNORDERARRAY:
9034 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9036 case LVM_SETCOLUMNWIDTH:
9037 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9039 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9040 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9042 /* case LVM_SETGROUPINFO: */
9044 /* case LVM_SETGROUPMETRICS: */
9046 case LVM_SETHOTCURSOR:
9047 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9049 case LVM_SETHOTITEM:
9050 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9052 case LVM_SETHOVERTIME:
9053 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9055 case LVM_SETICONSPACING:
9056 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9058 case LVM_SETIMAGELIST:
9059 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9061 /* case LVM_SETINFOTIP: */
9063 /* case LVM_SETINSERTMARK: */
9065 /* case LVM_SETINSERTMARKCOLOR: */
9068 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9071 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9073 case LVM_SETITEMCOUNT:
9074 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9076 case LVM_SETITEMPOSITION:
9079 pt.x = (short)LOWORD(lParam);
9080 pt.y = (short)HIWORD(lParam);
9081 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9084 case LVM_SETITEMPOSITION32:
9085 if (lParam == 0) return FALSE;
9086 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9088 case LVM_SETITEMSTATE:
9089 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9091 case LVM_SETITEMTEXTA:
9092 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9094 case LVM_SETITEMTEXTW:
9095 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9097 /* case LVM_SETOUTLINECOLOR: */
9099 /* case LVM_SETSELECTEDCOLUMN: */
9101 case LVM_SETSELECTIONMARK:
9102 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9104 case LVM_SETTEXTBKCOLOR:
9105 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9107 case LVM_SETTEXTCOLOR:
9108 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9110 /* case LVM_SETTILEINFO: */
9112 /* case LVM_SETTILEVIEWINFO: */
9114 /* case LVM_SETTILEWIDTH: */
9116 case LVM_SETTOOLTIPS:
9117 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9119 /* case LVM_SETUNICODEFORMAT: */
9121 /* case LVM_SETVIEW: */
9123 /* case LVM_SETWORKAREAS: */
9125 /* case LVM_SORTGROUPS: */
9128 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9130 /* LVM_SORTITEMSEX: */
9132 case LVM_SUBITEMHITTEST:
9133 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9136 return LISTVIEW_Update(infoPtr, (INT)wParam);
9139 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9142 return LISTVIEW_Command(infoPtr, wParam, lParam);
9145 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9148 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9151 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9154 return (LRESULT)infoPtr->hFont;
9157 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9160 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9163 return LISTVIEW_KillFocus(infoPtr);
9165 case WM_LBUTTONDBLCLK:
9166 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9168 case WM_LBUTTONDOWN:
9169 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9172 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9175 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9178 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9181 return LISTVIEW_NCDestroy(infoPtr);
9184 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9185 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9188 case WM_NOTIFYFORMAT:
9189 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9192 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9194 case WM_RBUTTONDBLCLK:
9195 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9197 case WM_RBUTTONDOWN:
9198 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9201 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9204 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9209 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9212 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9215 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9218 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9220 case WM_STYLECHANGED:
9221 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9223 case WM_SYSCOLORCHANGE:
9224 COMCTL32_RefreshSysColors();
9227 /* case WM_TIMER: */
9230 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9233 if (wParam & (MK_SHIFT | MK_CONTROL))
9234 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9235 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9237 case WM_WINDOWPOSCHANGED:
9238 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9240 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9241 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9242 LISTVIEW_UpdateSize(infoPtr);
9243 LISTVIEW_UpdateScroll(infoPtr);
9245 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9247 /* case WM_WININICHANGE: */
9250 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9251 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9254 /* call default window procedure */
9255 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9263 * Registers the window class.
9271 void LISTVIEW_Register(void)
9275 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9276 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9277 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
9278 wndClass.cbClsExtra = 0;
9279 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9280 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9281 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9282 wndClass.lpszClassName = WC_LISTVIEWW;
9283 RegisterClassW(&wndClass);
9288 * Unregisters the window class.
9296 void LISTVIEW_Unregister(void)
9298 UnregisterClassW(WC_LISTVIEWW, NULL);
9303 * Handle any WM_COMMAND messages
9306 * [I] infoPtr : valid pointer to the listview structure
9307 * [I] wParam : the first message parameter
9308 * [I] lParam : the second message parameter
9313 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9315 switch (HIWORD(wParam))
9320 * Adjust the edit window size
9323 HDC hdc = GetDC(infoPtr->hwndEdit);
9324 HFONT hFont, hOldFont = 0;
9329 if (!infoPtr->hwndEdit || !hdc) return 0;
9330 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9331 GetWindowRect(infoPtr->hwndEdit, &rect);
9333 /* Select font to get the right dimension of the string */
9334 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9337 hOldFont = SelectObject(hdc, hFont);
9340 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9342 TEXTMETRICW textMetric;
9344 /* Add Extra spacing for the next character */
9345 GetTextMetricsW(hdc, &textMetric);
9346 sz.cx += (textMetric.tmMaxCharWidth * 2);
9354 rect.bottom - rect.top,
9355 SWP_DRAWFRAME|SWP_NOMOVE);
9358 SelectObject(hdc, hOldFont);
9360 ReleaseDC(infoPtr->hwndSelf, hdc);
9366 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9375 * Subclassed edit control windproc function
9378 * [I] hwnd : the edit window handle
9379 * [I] uMsg : the message that is to be processed
9380 * [I] wParam : first message parameter
9381 * [I] lParam : second message parameter
9382 * [I] isW : TRUE if input is Unicode
9387 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9389 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
9390 BOOL cancel = FALSE;
9392 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9393 hwnd, uMsg, wParam, lParam, isW);
9398 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9405 WNDPROC editProc = infoPtr->EditWndProc;
9406 infoPtr->EditWndProc = 0;
9407 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
9408 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9412 if (VK_ESCAPE == (INT)wParam)
9417 else if (VK_RETURN == (INT)wParam)
9421 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9425 if (infoPtr->hwndEdit)
9427 LPWSTR buffer = NULL;
9429 infoPtr->hwndEdit = 0;
9432 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9436 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9438 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9439 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9443 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9445 if (buffer) Free(buffer);
9449 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9455 * Subclassed edit control Unicode windproc function
9458 * [I] hwnd : the edit window handle
9459 * [I] uMsg : the message that is to be processed
9460 * [I] wParam : first message parameter
9461 * [I] lParam : second message parameter
9465 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9467 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9472 * Subclassed edit control ANSI windproc function
9475 * [I] hwnd : the edit window handle
9476 * [I] uMsg : the message that is to be processed
9477 * [I] wParam : first message parameter
9478 * [I] lParam : second message parameter
9482 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9484 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9489 * Creates a subclassed edit cotrol
9492 * [I] infoPtr : valid pointer to the listview structure
9493 * [I] text : initial text for the edit
9494 * [I] style : the window style
9495 * [I] isW : TRUE if input is Unicode
9499 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9500 INT x, INT y, INT width, INT height, BOOL isW)
9502 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9507 TEXTMETRICW textMetric;
9508 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9510 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9512 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9513 hdc = GetDC(infoPtr->hwndSelf);
9515 /* Select the font to get appropriate metric dimensions */
9516 if(infoPtr->hFont != 0)
9517 hOldFont = SelectObject(hdc, infoPtr->hFont);
9519 /*Get String Length in pixels */
9520 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9522 /*Add Extra spacing for the next character */
9523 GetTextMetricsW(hdc, &textMetric);
9524 sz.cx += (textMetric.tmMaxCharWidth * 2);
9526 if(infoPtr->hFont != 0)
9527 SelectObject(hdc, hOldFont);
9529 ReleaseDC(infoPtr->hwndSelf, hdc);
9531 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9533 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9535 if (!hedit) return 0;
9537 infoPtr->EditWndProc = (WNDPROC)
9538 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9539 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9541 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);