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 range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1117 range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1122 * Releases resources associated with this ierator.
1124 static inline void iterator_destroy(ITERATOR* i)
1126 ranges_destroy(i->ranges);
1130 * Create an empty iterator.
1132 static inline BOOL iterator_empty(ITERATOR* i)
1134 ZeroMemory(i, sizeof(*i));
1135 i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1140 * Create an iterator over a range.
1142 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1150 * Create an iterator over a bunch of ranges.
1151 * Please note that the iterator will take ownership of the ranges,
1152 * and will free them upon destruction.
1154 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1162 * Creates an iterator over the items which intersect lprc.
1164 static BOOL iterator_frameditems(ITERATOR* i, LISTVIEW_INFO* infoPtr, const RECT *lprc)
1166 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1167 RECT frame = *lprc, rcItem, rcTemp;
1170 /* in case we fail, we want to return an empty iterator */
1171 if (!iterator_empty(i)) return FALSE;
1173 LISTVIEW_GetOrigin(infoPtr, &Origin);
1175 TRACE("(lprc=%s)\n", debugrect(lprc));
1176 OffsetRect(&frame, -Origin.x, -Origin.y);
1178 if (uView == LVS_ICON || uView == LVS_SMALLICON)
1182 if (uView == LVS_ICON && infoPtr->nFocusedItem != -1)
1184 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1185 if (IntersectRect(&rcTemp, &rcItem, lprc))
1186 i->nSpecial = infoPtr->nFocusedItem;
1188 if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1189 /* to do better here, we need to have PosX, and PosY sorted */
1190 TRACE("building icon ranges:\n");
1191 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1193 rcItem.left = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1194 rcItem.top = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1195 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1196 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1197 if (IntersectRect(&rcTemp, &rcItem, &frame))
1198 ranges_additem(i->ranges, nItem);
1202 else if (uView == LVS_REPORT)
1206 if (frame.left >= infoPtr->nItemWidth) return TRUE;
1207 if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1209 range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1210 range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1211 if (range.upper <= range.lower) return TRUE;
1212 if (!iterator_rangeitems(i, range)) return FALSE;
1213 TRACE(" report=%s\n", debugrange(&i->range));
1217 INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1218 INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1219 INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1220 INT nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1221 INT nLastCol = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1222 INT lower = nFirstCol * nPerCol + nFirstRow;
1226 TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1227 nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1229 if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1231 if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1232 TRACE("building list ranges:\n");
1233 for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1235 item_range.lower = nCol * nPerCol + nFirstRow;
1236 if(item_range.lower >= infoPtr->nItemCount) break;
1237 item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1238 TRACE(" list=%s\n", debugrange(&item_range));
1239 ranges_add(i->ranges, item_range);
1247 * Creates an iterator over the items which intersect the visible region of hdc.
1249 static BOOL iterator_visibleitems(ITERATOR *i, LISTVIEW_INFO *infoPtr, HDC hdc)
1251 POINT Origin, Position;
1252 RECT rcItem, rcClip;
1255 rgntype = GetClipBox(hdc, &rcClip);
1256 if (rgntype == NULLREGION) return iterator_empty(i);
1257 if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1258 if (rgntype == SIMPLEREGION) return TRUE;
1260 /* first deal with the special item */
1261 if (i->nSpecial != -1)
1263 LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1264 if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1267 /* if we can't deal with the region, we'll just go with the simple range */
1268 LISTVIEW_GetOrigin(infoPtr, &Origin);
1269 TRACE("building visible range:\n");
1270 if (!i->ranges && i->range.lower < i->range.upper)
1272 if (!(i->ranges = ranges_create(50))) return TRUE;
1273 if (!ranges_add(i->ranges, i->range))
1275 ranges_destroy(i->ranges);
1281 /* now delete the invisible items from the list */
1282 while(iterator_next(i))
1284 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1285 rcItem.left = Position.x + Origin.x;
1286 rcItem.top = Position.y + Origin.y;
1287 rcItem.right = rcItem.left + infoPtr->nItemWidth;
1288 rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1289 if (!RectVisible(hdc, &rcItem))
1290 ranges_delitem(i->ranges, i->nItem);
1292 /* the iterator should restart on the next iterator_next */
1298 /******** Misc helper functions ************************************/
1300 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1301 WPARAM wParam, LPARAM lParam, BOOL isW)
1303 if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1304 else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1307 static inline BOOL is_autoarrange(LISTVIEW_INFO *infoPtr)
1309 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1311 return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1312 (uView == LVS_ICON || uView == LVS_SMALLICON);
1315 /******** Internal API functions ************************************/
1317 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(LISTVIEW_INFO *infoPtr, INT nSubItem)
1319 static COLUMN_INFO mainItem;
1321 if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1322 assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1323 return (COLUMN_INFO *)DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1326 static inline void LISTVIEW_GetHeaderRect(LISTVIEW_INFO *infoPtr, INT nSubItem, RECT *lprc)
1328 *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1331 static inline BOOL LISTVIEW_GetItemW(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1333 return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1336 /* Listview invalidation functions: use _only_ these functions to invalidate */
1338 static inline BOOL is_redrawing(LISTVIEW_INFO *infoPtr)
1340 return infoPtr->bRedraw;
1343 static inline void LISTVIEW_InvalidateRect(LISTVIEW_INFO *infoPtr, const RECT* rect)
1345 if(!is_redrawing(infoPtr)) return;
1346 TRACE(" invalidating rect=%s\n", debugrect(rect));
1347 InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1350 static inline void LISTVIEW_InvalidateItem(LISTVIEW_INFO *infoPtr, INT nItem)
1354 if(!is_redrawing(infoPtr)) return;
1355 LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1356 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1359 static inline void LISTVIEW_InvalidateSubItem(LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1361 POINT Origin, Position;
1364 if(!is_redrawing(infoPtr)) return;
1365 assert ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT);
1366 LISTVIEW_GetOrigin(infoPtr, &Origin);
1367 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1368 LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1370 rcBox.bottom = infoPtr->nItemHeight;
1371 OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1372 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1375 static inline void LISTVIEW_InvalidateList(LISTVIEW_INFO *infoPtr)
1377 LISTVIEW_InvalidateRect(infoPtr, NULL);
1380 static inline void LISTVIEW_InvalidateColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
1384 if(!is_redrawing(infoPtr)) return;
1385 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1386 rcCol.top = infoPtr->rcList.top;
1387 rcCol.bottom = infoPtr->rcList.bottom;
1388 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1393 * Retrieves the number of items that can fit vertically in the client area.
1396 * [I] infoPtr : valid pointer to the listview structure
1399 * Number of items per row.
1401 static inline INT LISTVIEW_GetCountPerRow(LISTVIEW_INFO *infoPtr)
1403 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1405 return max(nListWidth/infoPtr->nItemWidth, 1);
1410 * Retrieves the number of items that can fit horizontally in the client
1414 * [I] infoPtr : valid pointer to the listview structure
1417 * Number of items per column.
1419 static inline INT LISTVIEW_GetCountPerColumn(LISTVIEW_INFO *infoPtr)
1421 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1423 return max(nListHeight / infoPtr->nItemHeight, 1);
1427 /*************************************************************************
1428 * LISTVIEW_ProcessLetterKeys
1430 * Processes keyboard messages generated by pressing the letter keys
1432 * What this does is perform a case insensitive search from the
1433 * current position with the following quirks:
1434 * - If two chars or more are pressed in quick succession we search
1435 * for the corresponding string (e.g. 'abc').
1436 * - If there is a delay we wipe away the current search string and
1437 * restart with just that char.
1438 * - If the user keeps pressing the same character, whether slowly or
1439 * fast, so that the search string is entirely composed of this
1440 * character ('aaaaa' for instance), then we search for first item
1441 * that starting with that character.
1442 * - If the user types the above character in quick succession, then
1443 * we must also search for the corresponding string ('aaaaa'), and
1444 * go to that string if there is a match.
1447 * [I] hwnd : handle to the window
1448 * [I] charCode : the character code, the actual character
1449 * [I] keyData : key data
1457 * - The current implementation has a list of characters it will
1458 * accept and it ignores averything else. In particular it will
1459 * ignore accentuated characters which seems to match what
1460 * Windows does. But I'm not sure it makes sense to follow
1462 * - We don't sound a beep when the search fails.
1466 * TREEVIEW_ProcessLetterKeys
1468 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1473 WCHAR buffer[MAX_PATH];
1474 DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1476 /* simple parameter checking */
1477 if (!charCode || !keyData) return 0;
1479 /* only allow the valid WM_CHARs through */
1480 if (!isalnum(charCode) &&
1481 charCode != '.' && charCode != '`' && charCode != '!' &&
1482 charCode != '@' && charCode != '#' && charCode != '$' &&
1483 charCode != '%' && charCode != '^' && charCode != '&' &&
1484 charCode != '*' && charCode != '(' && charCode != ')' &&
1485 charCode != '-' && charCode != '_' && charCode != '+' &&
1486 charCode != '=' && charCode != '\\'&& charCode != ']' &&
1487 charCode != '}' && charCode != '[' && charCode != '{' &&
1488 charCode != '/' && charCode != '?' && charCode != '>' &&
1489 charCode != '<' && charCode != ',' && charCode != '~')
1492 /* if there's one item or less, there is no where to go */
1493 if (infoPtr->nItemCount <= 1) return 0;
1495 /* update the search parameters */
1496 infoPtr->lastKeyPressTimestamp = GetTickCount();
1497 if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1498 if (infoPtr->nSearchParamLength < MAX_PATH)
1499 infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1500 if (infoPtr->charCode != charCode)
1501 infoPtr->charCode = charCode = 0;
1503 infoPtr->charCode=charCode;
1504 infoPtr->szSearchParam[0]=charCode;
1505 infoPtr->nSearchParamLength=1;
1506 /* Redundant with the 1 char string */
1510 /* and search from the current position */
1512 if (infoPtr->nFocusedItem >= 0) {
1513 endidx=infoPtr->nFocusedItem;
1515 /* if looking for single character match,
1516 * then we must always move forward
1518 if (infoPtr->nSearchParamLength == 1)
1521 endidx=infoPtr->nItemCount;
1525 if (idx == infoPtr->nItemCount) {
1526 if (endidx == infoPtr->nItemCount || endidx == 0)
1532 item.mask = LVIF_TEXT;
1535 item.pszText = buffer;
1536 item.cchTextMax = MAX_PATH;
1537 if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1539 /* check for a match */
1540 if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1543 } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1544 (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1545 /* This would work but we must keep looking for a longer match */
1549 } while (idx != endidx);
1552 LISTVIEW_KeySelection(infoPtr, nItem);
1557 /*************************************************************************
1558 * LISTVIEW_UpdateHeaderSize [Internal]
1560 * Function to resize the header control
1563 * [I] hwnd : handle to a window
1564 * [I] nNewScrollPos : scroll pos to set
1569 static void LISTVIEW_UpdateHeaderSize(LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1574 TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1576 GetWindowRect(infoPtr->hwndHeader, &winRect);
1577 point[0].x = winRect.left;
1578 point[0].y = winRect.top;
1579 point[1].x = winRect.right;
1580 point[1].y = winRect.bottom;
1582 MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1583 point[0].x = -nNewScrollPos;
1584 point[1].x += nNewScrollPos;
1586 SetWindowPos(infoPtr->hwndHeader,0,
1587 point[0].x,point[0].y,point[1].x,point[1].y,
1588 SWP_NOZORDER | SWP_NOACTIVATE);
1593 * Update the scrollbars. This functions should be called whenever
1594 * the content, size or view changes.
1597 * [I] infoPtr : valid pointer to the listview structure
1602 static void LISTVIEW_UpdateScroll(LISTVIEW_INFO *infoPtr)
1604 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1605 SCROLLINFO horzInfo, vertInfo;
1607 if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1609 ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1610 horzInfo.cbSize = sizeof(SCROLLINFO);
1611 horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1613 /* for now, we'll set info.nMax to the _count_, and adjust it later */
1614 if (uView == LVS_LIST)
1616 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1617 horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1619 /* scroll by at least one column per page */
1620 if(horzInfo.nPage < infoPtr->nItemWidth)
1621 horzInfo.nPage = infoPtr->nItemWidth;
1623 horzInfo.nPage /= infoPtr->nItemWidth;
1625 else if (uView == LVS_REPORT)
1627 horzInfo.nMax = infoPtr->nItemWidth;
1629 else /* LVS_ICON, or LVS_SMALLICON */
1633 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1636 horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1637 horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1638 SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1639 TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1641 /* Setting the horizontal scroll can change the listview size
1642 * (and potentially everything else) so we need to recompute
1643 * everything again for the vertical scroll
1646 ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1647 vertInfo.cbSize = sizeof(SCROLLINFO);
1648 vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1650 if (uView == LVS_REPORT)
1652 vertInfo.nMax = infoPtr->nItemCount;
1654 /* scroll by at least one page */
1655 if(vertInfo.nPage < infoPtr->nItemHeight)
1656 vertInfo.nPage = infoPtr->nItemHeight;
1658 vertInfo.nPage /= infoPtr->nItemHeight;
1660 else if (uView != LVS_LIST) /* LVS_ICON, or LVS_SMALLICON */
1664 if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1667 vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1668 vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1669 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1670 TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1672 /* Update the Header Control */
1673 if (uView == LVS_REPORT)
1675 horzInfo.fMask = SIF_POS;
1676 GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1677 LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1684 * Shows/hides the focus rectangle.
1687 * [I] infoPtr : valid pointer to the listview structure
1688 * [I] fShow : TRUE to show the focus, FALSE to hide it.
1693 static void LISTVIEW_ShowFocusRect(LISTVIEW_INFO *infoPtr, BOOL fShow)
1695 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1698 TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1700 if (infoPtr->nFocusedItem < 0) return;
1702 /* we need some gymnastics in ICON mode to handle large items */
1703 if ( (infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON )
1707 LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
1708 if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1710 LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1715 if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1717 /* for some reason, owner draw should work only in report mode */
1718 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
1723 item.iItem = infoPtr->nFocusedItem;
1725 item.mask = LVIF_PARAM;
1726 if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1728 ZeroMemory(&dis, sizeof(dis));
1729 dis.CtlType = ODT_LISTVIEW;
1730 dis.CtlID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
1731 dis.itemID = item.iItem;
1732 dis.itemAction = ODA_FOCUS;
1733 if (fShow) dis.itemState |= ODS_FOCUS;
1734 dis.hwndItem = infoPtr->hwndSelf;
1736 LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1737 dis.itemData = item.lParam;
1739 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1743 DrawFocusRect(hdc, &infoPtr->rcFocus);
1746 ReleaseDC(infoPtr->hwndSelf, hdc);
1750 * Invalidates all visible selected items.
1752 static void LISTVIEW_InvalidateSelectedItems(LISTVIEW_INFO *infoPtr)
1756 iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
1757 while(iterator_next(&i))
1759 if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
1760 LISTVIEW_InvalidateItem(infoPtr, i.nItem);
1762 iterator_destroy(&i);
1767 * DESCRIPTION: [INTERNAL]
1768 * Computes an item's (left,top) corner, relative to rcView.
1769 * That is, the position has NOT been made relative to the Origin.
1770 * This is deliberate, to avoid computing the Origin over, and
1771 * over again, when this function is call in a loop. Instead,
1772 * one ca factor the computation of the Origin before the loop,
1773 * and offset the value retured by this function, on every iteration.
1776 * [I] infoPtr : valid pointer to the listview structure
1777 * [I] nItem : item number
1778 * [O] lpptOrig : item top, left corner
1783 static void LISTVIEW_GetItemOrigin(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
1785 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1787 assert(nItem >= 0 && nItem < infoPtr->nItemCount);
1789 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
1791 lpptPosition->x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1792 lpptPosition->y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1794 else if (uView == LVS_LIST)
1796 INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
1797 lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
1798 lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
1800 else /* LVS_REPORT */
1802 lpptPosition->x = 0;
1803 lpptPosition->y = nItem * infoPtr->nItemHeight;
1808 * DESCRIPTION: [INTERNAL]
1809 * Compute the rectangles of an item. This is to localize all
1810 * the computations in one place. If you are not interested in some
1811 * of these values, simply pass in a NULL -- the fucntion is smart
1812 * enough to compute only what's necessary. The function computes
1813 * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
1814 * one, the BOX rectangle. This rectangle is very cheap to compute,
1815 * and is guaranteed to contain all the other rectangles. Computing
1816 * the ICON rect is also cheap, but all the others are potentaily
1817 * expensive. This gives an easy and effective optimization when
1818 * searching (like point inclusion, or rectangle intersection):
1819 * first test against the BOX, and if TRUE, test agains the desired
1821 * If the function does not have all the necessary information
1822 * to computed the requested rectangles, will crash with a
1823 * failed assertion. This is done so we catch all programming
1824 * errors, given that the function is called only from our code.
1826 * We have the following 'special' meanings for a few fields:
1827 * * If LVIS_FOCUSED is set, we assume the item has the focus
1828 * This is important in ICON mode, where it might get a larger
1829 * then usual rectange
1831 * Please note that subitem support works only in REPORT mode.
1834 * [I] infoPtr : valid pointer to the listview structure
1835 * [I] lpLVItem : item to compute the measures for
1836 * [O] lprcBox : ptr to Box rectangle
1837 * The internal LVIR_BOX rectangle
1838 * [0] lprcState : ptr to State icon rectangle
1839 * The internal LVIR_STATE rectangle
1840 * [O] lprcIcon : ptr to Icon rectangle
1841 * Same as LVM_GETITEMRECT with LVIR_ICON
1842 * [O] lprcLabel : ptr to Label rectangle
1843 * Same as LVM_GETITEMRECT with LVIR_LABEL
1848 static void LISTVIEW_GetItemMetrics(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
1849 LPRECT lprcBox, LPRECT lprcState,
1850 LPRECT lprcIcon, LPRECT lprcLabel)
1852 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
1853 BOOL doState = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
1854 RECT Box, State, Icon, Label;
1855 COLUMN_INFO *lpColumnInfo = NULL;
1857 TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
1859 /* Be smart and try to figure out the minimum we have to do */
1860 if (lpLVItem->iSubItem) assert(uView == LVS_REPORT);
1861 if (uView == LVS_ICON && (lprcBox || lprcLabel))
1863 assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
1864 if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
1866 if (lprcLabel) doLabel = TRUE;
1867 if (doLabel || lprcIcon) doIcon = TRUE;
1868 if (doIcon || lprcState) doState = TRUE;
1870 /************************************************************/
1871 /* compute the box rectangle (it should be cheap to do) */
1872 /************************************************************/
1873 if (lpLVItem->iSubItem || uView == LVS_REPORT)
1874 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
1876 if (lpLVItem->iSubItem)
1878 Box = lpColumnInfo->rcHeader;
1883 Box.right = infoPtr->nItemWidth;
1886 Box.bottom = infoPtr->nItemHeight;
1888 /************************************************************/
1889 /* compute STATEICON bounding box */
1890 /************************************************************/
1893 if (uView == LVS_ICON)
1895 State.left = Box.left - infoPtr->iconStateSize.cx - 2;
1896 if (infoPtr->himlNormal)
1897 State.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1898 State.top = Box.top + infoPtr->iconSize.cy - infoPtr->iconStateSize.cy + 4;
1902 /* we need the ident in report mode, if we don't have it, we fail */
1903 State.left = Box.left;
1904 if (uView == LVS_REPORT)
1906 if (lpLVItem->iSubItem == 0)
1908 State.left += REPORT_MARGINX;
1909 assert(lpLVItem->mask & LVIF_INDENT);
1910 State.left += infoPtr->iconSize.cx * lpLVItem->iIndent;
1913 State.top = Box.top;
1915 State.right = State.left;
1916 State.bottom = State.top;
1917 if (infoPtr->himlState && lpLVItem->iSubItem == 0)
1919 State.right += infoPtr->iconStateSize.cx;
1920 State.bottom += infoPtr->iconStateSize.cy;
1922 if (lprcState) *lprcState = State;
1923 TRACE(" - state=%s\n", debugrect(&State));
1926 /************************************************************/
1927 /* compute ICON bounding box (ala LVM_GETITEMRECT) */
1928 /************************************************************/
1931 if (uView == LVS_ICON)
1933 Icon.left = Box.left;
1934 if (infoPtr->himlNormal)
1935 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
1936 Icon.top = Box.top + ICON_TOP_PADDING;
1937 Icon.right = Icon.left;
1938 Icon.bottom = Icon.top;
1939 if (infoPtr->himlNormal)
1941 Icon.right += infoPtr->iconSize.cx;
1942 Icon.bottom += infoPtr->iconSize.cy;
1945 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
1947 Icon.left = State.right;
1949 Icon.right = Icon.left;
1950 if (infoPtr->himlSmall &&
1951 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
1952 ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
1953 Icon.right += infoPtr->iconSize.cx;
1954 Icon.bottom = Icon.top + infoPtr->nItemHeight;
1956 if(lprcIcon) *lprcIcon = Icon;
1957 TRACE(" - icon=%s\n", debugrect(&Icon));
1960 /************************************************************/
1961 /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
1962 /************************************************************/
1965 SIZE labelSize = { 0, 0 };
1967 /* calculate how far to the right can the label strech */
1968 Label.right = Box.right;
1969 if (uView == LVS_REPORT)
1971 if (lpLVItem->iSubItem == 0) Label = lpColumnInfo->rcHeader;
1974 if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && uView == LVS_REPORT))
1976 labelSize.cx = infoPtr->nItemWidth;
1977 labelSize.cy = infoPtr->nItemHeight;
1981 /* we need the text in non owner draw mode */
1982 assert(lpLVItem->mask & LVIF_TEXT);
1983 if (is_textT(lpLVItem->pszText, TRUE))
1985 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1986 HDC hdc = GetDC(infoPtr->hwndSelf);
1987 HFONT hOldFont = SelectObject(hdc, hFont);
1991 /* compute rough rectangle where the label will go */
1992 SetRectEmpty(&rcText);
1993 rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
1994 rcText.bottom = infoPtr->nItemHeight;
1995 if (uView == LVS_ICON)
1996 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
1998 /* now figure out the flags */
1999 if (uView == LVS_ICON)
2000 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2002 uFormat = LV_SL_DT_FLAGS;
2004 DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2006 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2007 labelSize.cy = rcText.bottom - rcText.top;
2009 SelectObject(hdc, hOldFont);
2010 ReleaseDC(infoPtr->hwndSelf, hdc);
2014 if (uView == LVS_ICON)
2016 Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2017 Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2018 infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2019 Label.right = Label.left + labelSize.cx;
2020 Label.bottom = Label.top + infoPtr->nItemHeight;
2021 if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2023 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2024 labelSize.cy /= infoPtr->ntmHeight;
2025 labelSize.cy = max(labelSize.cy, 1);
2026 labelSize.cy *= infoPtr->ntmHeight;
2028 Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2030 else /* LVS_SMALLICON, LVS_LIST or LVS_REPORT */
2032 Label.left = Icon.right;
2033 Label.top = Box.top;
2034 Label.right = min(Label.left + labelSize.cx, Label.right);
2035 Label.bottom = Label.top + infoPtr->nItemHeight;
2038 if (lprcLabel) *lprcLabel = Label;
2039 TRACE(" - label=%s\n", debugrect(&Label));
2042 /* Fix the Box if necessary */
2045 if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2046 else *lprcBox = Box;
2048 TRACE(" - box=%s\n", debugrect(&Box));
2052 * DESCRIPTION: [INTERNAL]
2055 * [I] infoPtr : valid pointer to the listview structure
2056 * [I] nItem : item number
2057 * [O] lprcBox : ptr to Box rectangle
2062 static void LISTVIEW_GetItemBox(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2064 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2065 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2066 POINT Position, Origin;
2069 LISTVIEW_GetOrigin(infoPtr, &Origin);
2070 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2072 /* Be smart and try to figure out the minimum we have to do */
2074 if (uView == LVS_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2075 lvItem.mask |= LVIF_TEXT;
2076 lvItem.iItem = nItem;
2077 lvItem.iSubItem = 0;
2078 lvItem.pszText = szDispText;
2079 lvItem.cchTextMax = DISP_TEXT_SIZE;
2080 if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2081 if (uView == LVS_ICON)
2083 lvItem.mask |= LVIF_STATE;
2084 lvItem.stateMask = LVIS_FOCUSED;
2085 lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2087 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0);
2089 OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2095 * Returns the current icon position, and advances it along the top.
2096 * The returned position is not offset by Origin.
2099 * [I] infoPtr : valid pointer to the listview structure
2100 * [O] lpPos : will get the current icon position
2105 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2107 INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2109 *lpPos = infoPtr->currIconPos;
2111 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2112 if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2114 infoPtr->currIconPos.x = 0;
2115 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2121 * Returns the current icon position, and advances it down the left edge.
2122 * The returned position is not offset by Origin.
2125 * [I] infoPtr : valid pointer to the listview structure
2126 * [O] lpPos : will get the current icon position
2131 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2133 INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2135 *lpPos = infoPtr->currIconPos;
2137 infoPtr->currIconPos.y += infoPtr->nItemHeight;
2138 if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2140 infoPtr->currIconPos.x += infoPtr->nItemWidth;
2141 infoPtr->currIconPos.y = 0;
2147 * Moves an icon to the specified position.
2148 * It takes care of invalidating the item, etc.
2151 * [I] infoPtr : valid pointer to the listview structure
2152 * [I] nItem : the item to move
2153 * [I] lpPos : the new icon position
2154 * [I] isNew : flags the item as being new
2160 static BOOL LISTVIEW_MoveIconTo(LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2166 old.x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2167 old.y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2169 if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2170 LISTVIEW_InvalidateItem(infoPtr, nItem);
2173 /* Allocating a POINTER for every item is too resource intensive,
2174 * so we'll keep the (x,y) in different arrays */
2175 if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)lppt->x)) return FALSE;
2176 if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)lppt->y)) return FALSE;
2178 LISTVIEW_InvalidateItem(infoPtr, nItem);
2185 * Arranges listview items in icon display mode.
2188 * [I] infoPtr : valid pointer to the listview structure
2189 * [I] nAlignCode : alignment code
2195 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2197 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2198 void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2202 if (uView != LVS_ICON && uView != LVS_SMALLICON) return FALSE;
2204 TRACE("nAlignCode=%d\n", nAlignCode);
2206 if (nAlignCode == LVA_DEFAULT)
2208 if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2209 else nAlignCode = LVA_ALIGNTOP;
2214 case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2215 case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2216 case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2217 default: return FALSE;
2220 infoPtr->bAutoarrange = TRUE;
2221 infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2222 for (i = 0; i < infoPtr->nItemCount; i++)
2224 next_pos(infoPtr, &pos);
2225 LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2233 * Retrieves the bounding rectangle of all the items, not offset by Origin.
2236 * [I] infoPtr : valid pointer to the listview structure
2237 * [O] lprcView : bounding rectangle
2243 static void LISTVIEW_GetAreaRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2247 SetRectEmpty(lprcView);
2249 switch (infoPtr->dwStyle & LVS_TYPEMASK)
2253 for (i = 0; i < infoPtr->nItemCount; i++)
2255 x = (LONG)DPA_GetPtr(infoPtr->hdpaPosX, i);
2256 y = (LONG)DPA_GetPtr(infoPtr->hdpaPosY, i);
2257 lprcView->right = max(lprcView->right, x);
2258 lprcView->bottom = max(lprcView->bottom, y);
2260 if (infoPtr->nItemCount > 0)
2262 lprcView->right += infoPtr->nItemWidth;
2263 lprcView->bottom += infoPtr->nItemHeight;
2268 y = LISTVIEW_GetCountPerColumn(infoPtr);
2269 x = infoPtr->nItemCount / y;
2270 if (infoPtr->nItemCount % y) x++;
2271 lprcView->right = x * infoPtr->nItemWidth;
2272 lprcView->bottom = y * infoPtr->nItemHeight;
2276 lprcView->right = infoPtr->nItemWidth;
2277 lprcView->bottom = infoPtr->nItemCount * infoPtr->nItemHeight;
2284 * Retrieves the bounding rectangle of all the items.
2287 * [I] infoPtr : valid pointer to the listview structure
2288 * [O] lprcView : bounding rectangle
2294 static BOOL LISTVIEW_GetViewRect(LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2298 TRACE("(lprcView=%p)\n", lprcView);
2300 if (!lprcView) return FALSE;
2302 LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2303 LISTVIEW_GetAreaRect(infoPtr, lprcView);
2304 OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2306 TRACE("lprcView=%s\n", debugrect(lprcView));
2313 * Retrieves the subitem pointer associated with the subitem index.
2316 * [I] hdpaSubItems : DPA handle for a specific item
2317 * [I] nSubItem : index of subitem
2320 * SUCCESS : subitem pointer
2323 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2325 SUBITEM_INFO *lpSubItem;
2328 /* we should binary search here if need be */
2329 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2331 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
2332 if (lpSubItem->iSubItem == nSubItem)
2342 * Caclulates the desired item width.
2345 * [I] infoPtr : valid pointer to the listview structure
2348 * The desired item width.
2350 static INT LISTVIEW_CalculateItemWidth(LISTVIEW_INFO *infoPtr)
2352 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2355 TRACE("uView=%d\n", uView);
2357 if (uView == LVS_ICON)
2358 nItemWidth = infoPtr->iconSpacing.cx;
2359 else if (uView == LVS_REPORT)
2363 if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2365 LISTVIEW_GetHeaderRect(infoPtr, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, &rcHeader);
2366 nItemWidth = rcHeader.right;
2369 else /* LVS_SMALLICON, or LVS_LIST */
2373 for (i = 0; i < infoPtr->nItemCount; i++)
2374 nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2376 if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2377 if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2379 nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2382 return max(nItemWidth, 1);
2387 * Caclulates the desired item height.
2390 * [I] infoPtr : valid pointer to the listview structure
2393 * The desired item height.
2395 static INT LISTVIEW_CalculateItemHeight(LISTVIEW_INFO *infoPtr)
2397 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2400 TRACE("uView=%d\n", uView);
2402 if (uView == LVS_ICON)
2403 nItemHeight = infoPtr->iconSpacing.cy;
2406 nItemHeight = infoPtr->ntmHeight;
2407 if (infoPtr->himlState)
2408 nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2409 if (infoPtr->himlSmall)
2410 nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2411 if (infoPtr->himlState || infoPtr->himlSmall)
2412 nItemHeight += HEIGHT_PADDING;
2415 return max(nItemHeight, 1);
2420 * Updates the width, and height of an item.
2423 * [I] infoPtr : valid pointer to the listview structure
2428 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2430 infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2431 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2437 * Retrieves and saves important text metrics info for the current
2441 * [I] infoPtr : valid pointer to the listview structure
2444 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2446 HDC hdc = GetDC(infoPtr->hwndSelf);
2447 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2448 HFONT hOldFont = SelectObject(hdc, hFont);
2451 if (GetTextMetricsW(hdc, &tm))
2453 infoPtr->ntmHeight = tm.tmHeight;
2454 infoPtr->ntmAveCharWidth = tm.tmAveCharWidth;
2456 SelectObject(hdc, hOldFont);
2457 ReleaseDC(infoPtr->hwndSelf, hdc);
2459 TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2464 * A compare function for ranges
2467 * [I] range1 : pointer to range 1;
2468 * [I] range2 : pointer to range 2;
2472 * > 0 : if range 1 > range 2
2473 * < 0 : if range 2 > range 1
2474 * = 0 : if range intersects range 2
2476 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2480 if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
2482 else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
2487 TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange((RANGE*)range1), debugrange((RANGE*)range2), cmp);
2493 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2495 #define ranges_check(ranges, desc) do { } while(0)
2498 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2503 TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2505 assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2506 ranges_dump(ranges);
2507 prev = (RANGE *)DPA_GetPtr(ranges->hdpa, 0);
2508 if (DPA_GetPtrCount(ranges->hdpa) > 0)
2509 assert (prev->lower >= 0 && prev->lower < prev->upper);
2510 for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2512 curr = (RANGE *)DPA_GetPtr(ranges->hdpa, i);
2513 assert (prev->upper <= curr->lower);
2514 assert (curr->lower < curr->upper);
2517 TRACE("--- Done checking---\n");
2520 static RANGES ranges_create(int count)
2522 RANGES ranges = (RANGES)Alloc(sizeof(struct tagRANGES));
2523 if (!ranges) return NULL;
2524 ranges->hdpa = DPA_Create(count);
2525 if (ranges->hdpa) return ranges;
2530 static void ranges_clear(RANGES ranges)
2534 for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2535 Free(DPA_GetPtr(ranges->hdpa, i));
2536 DPA_DeleteAllPtrs(ranges->hdpa);
2540 static void ranges_destroy(RANGES ranges)
2542 if (!ranges) return;
2543 ranges_clear(ranges);
2544 DPA_Destroy(ranges->hdpa);
2548 static RANGES ranges_clone(RANGES ranges)
2553 if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2555 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2557 RANGE *newrng = (RANGE *)Alloc(sizeof(RANGE));
2558 if (!newrng) goto fail;
2559 *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2560 DPA_SetPtr(clone->hdpa, i, newrng);
2565 TRACE ("clone failed\n");
2566 ranges_destroy(clone);
2570 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2574 for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2575 ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2580 static void ranges_dump(RANGES ranges)
2584 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2585 TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2588 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2590 RANGE srchrng = { nItem, nItem + 1 };
2592 TRACE("(nItem=%d)\n", nItem);
2593 ranges_check(ranges, "before contain");
2594 return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2597 static INT ranges_itemcount(RANGES ranges)
2601 for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2603 RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2604 count += sel->upper - sel->lower;
2610 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2612 RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2615 index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2616 if (index == -1) return TRUE;
2618 for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2620 chkrng = DPA_GetPtr(ranges->hdpa, index);
2621 if (chkrng->lower >= nItem)
2622 chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2623 if (chkrng->upper > nItem)
2624 chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
2629 static BOOL ranges_add(RANGES ranges, RANGE range)
2634 TRACE("(%s)\n", debugrange(&range));
2635 ranges_check(ranges, "before add");
2637 /* try find overlapping regions first */
2638 srchrgn.lower = range.lower - 1;
2639 srchrgn.upper = range.upper + 1;
2640 index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
2646 TRACE("Adding new range\n");
2648 /* create the brand new range to insert */
2649 newrgn = (RANGE *)Alloc(sizeof(RANGE));
2650 if(!newrgn) goto fail;
2653 /* figure out where to insert it */
2654 index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2655 TRACE("index=%d\n", index);
2656 if (index == -1) index = 0;
2658 /* and get it over with */
2659 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2667 RANGE *chkrgn, *mrgrgn;
2668 INT fromindex, mergeindex;
2670 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2671 TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
2673 chkrgn->lower = min(range.lower, chkrgn->lower);
2674 chkrgn->upper = max(range.upper, chkrgn->upper);
2676 TRACE("New range %s @%d\n", debugrange(chkrgn), index);
2678 /* merge now common anges */
2680 srchrgn.lower = chkrgn->lower - 1;
2681 srchrgn.upper = chkrgn->upper + 1;
2685 mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
2686 if (mergeindex == -1) break;
2687 if (mergeindex == index)
2689 fromindex = index + 1;
2693 TRACE("Merge with index %i\n", mergeindex);
2695 mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
2696 chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
2697 chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
2699 DPA_DeletePtr(ranges->hdpa, mergeindex);
2700 if (mergeindex < index) index --;
2704 ranges_check(ranges, "after add");
2708 ranges_check(ranges, "failed add");
2712 static BOOL ranges_del(RANGES ranges, RANGE range)
2717 TRACE("(%s)\n", debugrange(&range));
2718 ranges_check(ranges, "before del");
2720 /* we don't use DPAS_SORTED here, since we need *
2721 * to find the first overlapping range */
2722 index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
2725 chkrgn = DPA_GetPtr(ranges->hdpa, index);
2727 TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
2729 /* case 1: Same range */
2730 if ( (chkrgn->upper == range.upper) &&
2731 (chkrgn->lower == range.lower) )
2733 DPA_DeletePtr(ranges->hdpa, index);
2736 /* case 2: engulf */
2737 else if ( (chkrgn->upper <= range.upper) &&
2738 (chkrgn->lower >= range.lower) )
2740 DPA_DeletePtr(ranges->hdpa, index);
2742 /* case 3: overlap upper */
2743 else if ( (chkrgn->upper <= range.upper) &&
2744 (chkrgn->lower < range.lower) )
2746 chkrgn->upper = range.lower;
2748 /* case 4: overlap lower */
2749 else if ( (chkrgn->upper > range.upper) &&
2750 (chkrgn->lower >= range.lower) )
2752 chkrgn->lower = range.upper;
2755 /* case 5: fully internal */
2758 RANGE tmprgn = *chkrgn, *newrgn;
2760 if (!(newrgn = (RANGE *)Alloc(sizeof(RANGE)))) goto fail;
2761 newrgn->lower = chkrgn->lower;
2762 newrgn->upper = range.lower;
2763 chkrgn->lower = range.upper;
2764 if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
2773 index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
2776 ranges_check(ranges, "after del");
2780 ranges_check(ranges, "failed del");
2786 * Removes all selection ranges
2789 * [I] infoPtr : valid pointer to the listview structure
2790 * [I] toSkip : item range to skip removing the selection
2796 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
2805 lvItem.stateMask = LVIS_SELECTED;
2807 /* need to clone the DPA because callbacks can change it */
2808 if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
2809 iterator_rangesitems(&i, ranges_diff(clone, toSkip));
2810 while(iterator_next(&i))
2811 LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
2812 /* note that the iterator destructor will free the cloned range */
2813 iterator_destroy(&i);
2818 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
2822 if (!(toSkip = ranges_create(1))) return FALSE;
2823 if (nItem != -1) ranges_additem(toSkip, nItem);
2824 LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
2825 ranges_destroy(toSkip);
2829 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
2831 return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
2836 * Retrieves the number of items that are marked as selected.
2839 * [I] infoPtr : valid pointer to the listview structure
2842 * Number of items selected.
2844 static INT LISTVIEW_GetSelectedCount(LISTVIEW_INFO *infoPtr)
2846 INT nSelectedCount = 0;
2848 if (infoPtr->uCallbackMask & LVIS_SELECTED)
2851 for (i = 0; i < infoPtr->nItemCount; i++)
2853 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
2858 nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
2860 TRACE("nSelectedCount=%d\n", nSelectedCount);
2861 return nSelectedCount;
2866 * Manages the item focus.
2869 * [I] infoPtr : valid pointer to the listview structure
2870 * [I] nItem : item index
2873 * TRUE : focused item changed
2874 * FALSE : focused item has NOT changed
2876 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
2878 INT oldFocus = infoPtr->nFocusedItem;
2881 if (nItem == infoPtr->nFocusedItem) return FALSE;
2883 lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
2884 lvItem.stateMask = LVIS_FOCUSED;
2885 LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
2887 return oldFocus != infoPtr->nFocusedItem;
2890 /* Helper function for LISTVIEW_ShiftIndices *only* */
2891 static INT shift_item(LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
2893 if (nShiftItem < nItem) return nShiftItem;
2895 if (nShiftItem > nItem) return nShiftItem + direction;
2897 if (direction > 0) return nShiftItem + direction;
2899 return min(nShiftItem, infoPtr->nItemCount - 1);
2904 * Updates the various indices after an item has been inserted or deleted.
2907 * [I] infoPtr : valid pointer to the listview structure
2908 * [I] nItem : item index
2909 * [I] direction : Direction of shift, +1 or -1.
2914 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
2919 /* temporarily disable change notification while shifting items */
2920 bOldChange = infoPtr->bDoChangeNotify;
2921 infoPtr->bDoChangeNotify = FALSE;
2923 TRACE("Shifting %iu, %i steps\n", nItem, direction);
2925 ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
2927 assert(abs(direction) == 1);
2929 infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
2931 nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
2932 if (nNewFocus != infoPtr->nFocusedItem)
2933 LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
2935 /* But we are not supposed to modify nHotItem! */
2937 infoPtr->bDoChangeNotify = bOldChange;
2943 * Adds a block of selections.
2946 * [I] infoPtr : valid pointer to the listview structure
2947 * [I] nItem : item index
2952 static void LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2954 INT nFirst = min(infoPtr->nSelectionMark, nItem);
2955 INT nLast = max(infoPtr->nSelectionMark, nItem);
2959 if (nFirst == -1) nFirst = nItem;
2961 item.state = LVIS_SELECTED;
2962 item.stateMask = LVIS_SELECTED;
2964 /* FIXME: this is not correct LVS_OWNERDATA
2965 * setting the item states individually will generate
2966 * a LVN_ITEMCHANGED notification for each one. Instead,
2967 * we have to send a LVN_ODSTATECHANGED notification.
2968 * See MSDN documentation for LVN_ITEMCHANGED.
2970 for (i = nFirst; i <= nLast; i++)
2971 LISTVIEW_SetItemState(infoPtr,i,&item);
2977 * Sets a single group selection.
2980 * [I] infoPtr : valid pointer to the listview structure
2981 * [I] nItem : item index
2986 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
2988 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
2993 if (!(selection = ranges_create(100))) return;
2995 item.state = LVIS_SELECTED;
2996 item.stateMask = LVIS_SELECTED;
2998 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
3000 if (infoPtr->nSelectionMark == -1)
3002 infoPtr->nSelectionMark = nItem;
3003 ranges_additem(selection, nItem);
3009 sel.lower = min(infoPtr->nSelectionMark, nItem);
3010 sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3011 ranges_add(selection, sel);
3016 RECT rcItem, rcSel, rcSelMark;
3019 rcItem.left = LVIR_BOUNDS;
3020 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return;
3021 rcSelMark.left = LVIR_BOUNDS;
3022 if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) return;
3023 UnionRect(&rcSel, &rcItem, &rcSelMark);
3024 iterator_frameditems(&i, infoPtr, &rcSel);
3025 while(iterator_next(&i))
3027 LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3028 if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3030 iterator_destroy(&i);
3033 LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3034 iterator_rangesitems(&i, selection);
3035 while(iterator_next(&i))
3036 LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3037 /* this will also destroy the selection */
3038 iterator_destroy(&i);
3040 LISTVIEW_SetItemFocus(infoPtr, nItem);
3045 * Sets a single selection.
3048 * [I] infoPtr : valid pointer to the listview structure
3049 * [I] nItem : item index
3054 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3058 TRACE("nItem=%d\n", nItem);
3060 LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3062 lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3063 lvItem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
3064 LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3066 infoPtr->nSelectionMark = nItem;
3071 * Set selection(s) with keyboard.
3074 * [I] infoPtr : valid pointer to the listview structure
3075 * [I] nItem : item index
3078 * SUCCESS : TRUE (needs to be repainted)
3079 * FAILURE : FALSE (nothing has changed)
3081 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *infoPtr, INT nItem)
3083 /* FIXME: pass in the state */
3084 WORD wShift = HIWORD(GetKeyState(VK_SHIFT));
3085 WORD wCtrl = HIWORD(GetKeyState(VK_CONTROL));
3086 BOOL bResult = FALSE;
3088 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3090 if (infoPtr->dwStyle & LVS_SINGLESEL)
3093 LISTVIEW_SetSelection(infoPtr, nItem);
3100 LISTVIEW_SetGroupSelection(infoPtr, nItem);
3104 bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3109 LISTVIEW_SetSelection(infoPtr, nItem);
3112 LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3115 UpdateWindow(infoPtr->hwndSelf); /* update client area */
3122 * Called when the mouse is being actively tracked and has hovered for a specified
3126 * [I] infoPtr : valid pointer to the listview structure
3127 * [I] fwKeys : key indicator
3128 * [I] pts : mouse position
3131 * 0 if the message was processed, non-zero if there was an error
3134 * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3135 * over the item for a certain period of time.
3138 static LRESULT LISTVIEW_MouseHover(LISTVIEW_INFO *infoPtr, WORD fwKyes, POINTS pts)
3140 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
3141 /* FIXME: select the item!!! */
3142 /*LISTVIEW_GetItemAtPt(infoPtr, pt)*/;
3149 * Called whenever WM_MOUSEMOVE is received.
3152 * [I] infoPtr : valid pointer to the listview structure
3153 * [I] fwKeys : key indicator
3154 * [I] pts : mouse position
3157 * 0 if the message is processed, non-zero if there was an error
3159 static LRESULT LISTVIEW_MouseMove(LISTVIEW_INFO *infoPtr, WORD fwKeys, POINTS pts)
3161 TRACKMOUSEEVENT trackinfo;
3163 /* see if we are supposed to be tracking mouse hovering */
3164 if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) {
3165 /* fill in the trackinfo struct */
3166 trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
3167 trackinfo.dwFlags = TME_QUERY;
3168 trackinfo.hwndTrack = infoPtr->hwndSelf;
3169 trackinfo.dwHoverTime = infoPtr->dwHoverTime;
3171 /* see if we are already tracking this hwnd */
3172 _TrackMouseEvent(&trackinfo);
3174 if(!(trackinfo.dwFlags & TME_HOVER)) {
3175 trackinfo.dwFlags = TME_HOVER;
3177 /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
3178 _TrackMouseEvent(&trackinfo);
3187 * Tests wheather the item is assignable to a list with style lStyle
3189 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
3191 if ( (lpLVItem->mask & LVIF_TEXT) &&
3192 (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
3193 (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
3201 * Helper for LISTVIEW_SetItemT *only*: sets item attributes.
3204 * [I] infoPtr : valid pointer to the listview structure
3205 * [I] lpLVItem : valid pointer to new item atttributes
3206 * [I] isNew : the item being set is being inserted
3207 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3208 * [O] bChanged : will be set to TRUE if the item really changed
3214 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
3216 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3224 assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
3226 if (lpLVItem->mask == 0) return TRUE;
3228 if (infoPtr->dwStyle & LVS_OWNERDATA)
3230 /* a virtual listview we stores only selection and focus */
3231 if (lpLVItem->mask & ~LVIF_STATE)
3237 HDPA hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3238 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
3242 /* we need to get the lParam and state of the item */
3243 item.iItem = lpLVItem->iItem;
3244 item.iSubItem = lpLVItem->iSubItem;
3245 item.mask = LVIF_STATE | LVIF_PARAM;
3246 item.stateMask = ~0;
3249 if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
3251 TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
3252 /* determine what fields will change */
3253 if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & lpLVItem->stateMask & ~infoPtr->uCallbackMask))
3254 uChanged |= LVIF_STATE;
3256 if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
3257 uChanged |= LVIF_IMAGE;
3259 if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
3260 uChanged |= LVIF_PARAM;
3262 if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
3263 uChanged |= LVIF_INDENT;
3265 if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
3266 uChanged |= LVIF_TEXT;
3268 TRACE("uChanged=0x%x\n", uChanged);
3269 if (!uChanged) return TRUE;
3272 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
3273 nmlv.iItem = lpLVItem->iItem;
3274 nmlv.uNewState = (item.state & ~lpLVItem->stateMask) | (lpLVItem->state & lpLVItem->stateMask);
3275 nmlv.uOldState = item.state;
3276 nmlv.uChanged = uChanged;
3277 nmlv.lParam = item.lParam;
3279 /* send LVN_ITEMCHANGING notification, if the item is not being inserted */
3280 /* and we are _NOT_ virtual (LVS_OWERNDATA), and change notifications */
3282 if(lpItem && !isNew && infoPtr->bDoChangeNotify &&
3283 notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
3286 /* copy information */
3287 if (lpLVItem->mask & LVIF_TEXT)
3288 textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
3290 if (lpLVItem->mask & LVIF_IMAGE)
3291 lpItem->hdr.iImage = lpLVItem->iImage;
3293 if (lpLVItem->mask & LVIF_PARAM)
3294 lpItem->lParam = lpLVItem->lParam;
3296 if (lpLVItem->mask & LVIF_INDENT)
3297 lpItem->iIndent = lpLVItem->iIndent;
3299 if (uChanged & LVIF_STATE)
3301 if (lpItem && (lpLVItem->stateMask & ~infoPtr->uCallbackMask & ~(LVIS_FOCUSED | LVIS_SELECTED)))
3303 lpItem->state &= ~lpLVItem->stateMask;
3304 lpItem->state |= (lpLVItem->state & lpLVItem->stateMask);
3306 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
3308 if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
3309 ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
3311 else if (lpLVItem->stateMask & LVIS_SELECTED)
3312 ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
3314 /* if we are asked to change focus, and we manage it, do it */
3315 if (lpLVItem->state & lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
3317 if (lpLVItem->state & LVIS_FOCUSED)
3319 LISTVIEW_SetItemFocus(infoPtr, -1);
3320 infoPtr->nFocusedItem = lpLVItem->iItem;
3321 LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, uView == LVS_LIST);
3323 else if (infoPtr->nFocusedItem == lpLVItem->iItem)
3324 infoPtr->nFocusedItem = -1;
3328 /* if we're inserting the item, we're done */
3329 if (isNew) return TRUE;
3331 /* send LVN_ITEMCHANGED notification */
3332 if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
3333 if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
3340 * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
3343 * [I] infoPtr : valid pointer to the listview structure
3344 * [I] lpLVItem : valid pointer to new subitem atttributes
3345 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3346 * [O] bChanged : will be set to TRUE if the item really changed
3352 static BOOL set_sub_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
3355 SUBITEM_INFO *lpSubItem;
3357 /* we do not support subitems for virtual listviews */
3358 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
3360 /* set subitem only if column is present */
3361 if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
3363 /* First do some sanity checks */
3364 if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE)) return FALSE;
3365 if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE))) return TRUE;
3367 /* get the subitem structure, and create it if not there */
3368 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
3369 assert (hdpaSubItems);
3371 lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
3374 SUBITEM_INFO *tmpSubItem;
3377 lpSubItem = (SUBITEM_INFO *)Alloc(sizeof(SUBITEM_INFO));
3378 if (!lpSubItem) return FALSE;
3379 /* we could binary search here, if need be...*/
3380 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
3382 tmpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
3383 if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
3385 if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
3390 lpSubItem->iSubItem = lpLVItem->iSubItem;
3391 lpSubItem->hdr.iImage = I_IMAGECALLBACK;
3395 if (lpLVItem->mask & LVIF_IMAGE)
3396 if (lpSubItem->hdr.iImage != lpLVItem->iImage)
3398 lpSubItem->hdr.iImage = lpLVItem->iImage;
3402 if (lpLVItem->mask & LVIF_TEXT)
3403 if (lpSubItem->hdr.pszText != lpLVItem->pszText)
3405 textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
3414 * Sets item attributes.
3417 * [I] infoPtr : valid pointer to the listview structure
3418 * [I] lpLVItem : new item atttributes
3419 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
3425 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
3427 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3428 LPWSTR pszText = NULL;
3429 BOOL bResult, bChanged = FALSE;
3431 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
3433 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
3436 /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
3437 if ((lpLVItem->mask & LVIF_TEXT) && is_textW(lpLVItem->pszText))
3439 pszText = lpLVItem->pszText;
3440 ((LVITEMW *)lpLVItem)->pszText = textdupTtoW(lpLVItem->pszText, isW);
3443 /* actually set the fields */
3444 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
3446 if (lpLVItem->iSubItem)
3447 bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
3449 bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
3451 /* redraw item, if necessary */
3452 if (bChanged && !infoPtr->bIsDrawing)
3454 /* this little optimization eliminates some nasty flicker */
3455 if ( uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
3456 (!(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) || lpLVItem->iSubItem) )
3457 LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
3459 LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
3464 textfreeT(lpLVItem->pszText, isW);
3465 ((LVITEMW *)lpLVItem)->pszText = pszText;
3473 * Retrieves the index of the item at coordinate (0, 0) of the client area.
3476 * [I] infoPtr : valid pointer to the listview structure
3481 static INT LISTVIEW_GetTopIndex(LISTVIEW_INFO *infoPtr)
3483 LONG lStyle = infoPtr->dwStyle;
3484 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3486 SCROLLINFO scrollInfo;
3488 scrollInfo.cbSize = sizeof(SCROLLINFO);
3489 scrollInfo.fMask = SIF_POS;
3491 if (uView == LVS_LIST)
3493 if ((lStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
3494 nItem = scrollInfo.nPos * LISTVIEW_GetCountPerColumn(infoPtr);
3496 else if (uView == LVS_REPORT)
3498 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3499 nItem = scrollInfo.nPos;
3503 if ((lStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3504 nItem = LISTVIEW_GetCountPerRow(infoPtr) * (scrollInfo.nPos / infoPtr->nItemHeight);
3507 TRACE("nItem=%d\n", nItem);
3515 * Erases the background of the given rectangle
3518 * [I] infoPtr : valid pointer to the listview structure
3519 * [I] hdc : device context handle
3520 * [I] lprcBox : clipping rectangle
3526 static inline BOOL LISTVIEW_FillBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
3528 if (!infoPtr->hBkBrush) return FALSE;
3530 TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, debugrect(lprcBox), infoPtr->hBkBrush);
3532 return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
3540 * [I] infoPtr : valid pointer to the listview structure
3541 * [I] hdc : device context handle
3542 * [I] nItem : item index
3543 * [I] nSubItem : subitem index
3544 * [I] pos : item position in client coordinates
3545 * [I] cdmode : custom draw mode
3551 static BOOL LISTVIEW_DrawItem(LISTVIEW_INFO *infoPtr, HDC hdc, INT nItem, INT nSubItem, POINT pos, DWORD cdmode)
3553 UINT uFormat, uView = infoPtr->dwStyle & LVS_TYPEMASK;
3554 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
3555 static const WCHAR szCallback[] = { '(', 'c', 'a', 'l', 'l', 'b', 'a', 'c', 'k', ')', 0 };
3556 DWORD cdsubitemmode = CDRF_DODEFAULT;
3557 RECT* lprcFocus, rcSelect, rcBox, rcState, rcIcon, rcLabel;
3558 NMLVCUSTOMDRAW nmlvcd;
3562 TRACE("(hdc=%p, nItem=%d, nSubItem=%d, pos=%s)\n", hdc, nItem, nSubItem, debugpoint(&pos));
3564 /* get information needed for drawing the item */
3565 lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
3566 if (nSubItem == 0) lvItem.mask |= LVIF_STATE | LVIF_PARAM;
3567 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
3568 lvItem.stateMask = LVIS_SELECTED | LVIS_FOCUSED | LVIS_STATEIMAGEMASK;
3569 lvItem.iItem = nItem;
3570 lvItem.iSubItem = nSubItem;
3573 lvItem.cchTextMax = DISP_TEXT_SIZE;
3574 lvItem.pszText = szDispText;
3575 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
3576 if (nSubItem > 0 && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3577 lvItem.state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3578 if (lvItem.pszText == LPSTR_TEXTCALLBACKW) lvItem.pszText = (LPWSTR)szCallback;
3579 TRACE(" lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
3581 /* now check if we need to update the focus rectangle */
3582 lprcFocus = infoPtr->bFocus && (lvItem.state & LVIS_FOCUSED) ? &infoPtr->rcFocus : 0;
3584 if (!lprcFocus) lvItem.state &= ~LVIS_FOCUSED;
3585 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
3586 OffsetRect(&rcBox, pos.x, pos.y);
3587 OffsetRect(&rcState, pos.x, pos.y);
3588 OffsetRect(&rcIcon, pos.x, pos.y);
3589 OffsetRect(&rcLabel, pos.x, pos.y);
3590 TRACE(" rcBox=%s, rcState=%s, rcIcon=%s. rcLabel=%s\n",
3591 debugrect(&rcBox), debugrect(&rcState), debugrect(&rcIcon), debugrect(&rcLabel));
3593 /* fill in the custom draw structure */
3594 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcBox, &lvItem);
3596 if (nSubItem > 0) cdmode = infoPtr->cditemmode;
3597 if (cdmode & CDRF_NOTIFYITEMDRAW)
3598 cdsubitemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3599 if (nSubItem == 0) infoPtr->cditemmode = cdsubitemmode;
3600 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3601 /* we have to send a CDDS_SUBITEM customdraw explicitly for subitem 0 */
3602 if (nSubItem == 0 && cdsubitemmode == CDRF_NOTIFYITEMDRAW)
3604 cdsubitemmode = notify_customdraw(infoPtr, CDDS_SUBITEM | CDDS_ITEMPREPAINT, &nmlvcd);
3605 if (cdsubitemmode & CDRF_SKIPDEFAULT) goto postpaint;
3607 if (nSubItem == 0 || (cdmode & CDRF_NOTIFYITEMDRAW))
3608 prepaint_setup(infoPtr, hdc, &nmlvcd);
3610 /* in full row select, subitems, will just use main item's colors */
3611 if (nSubItem && uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3612 nmlvcd.clrTextBk = CLR_NONE;
3615 if (infoPtr->himlState && !IsRectEmpty(&rcState))
3617 UINT uStateImage = (lvItem.state & LVIS_STATEIMAGEMASK) >> 12;
3620 TRACE("uStateImage=%d\n", uStateImage);
3621 ImageList_Draw(infoPtr->himlState, uStateImage - 1, hdc, rcState.left, rcState.top, ILD_NORMAL);
3626 himl = (uView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
3627 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon))
3629 TRACE("iImage=%d\n", lvItem.iImage);
3630 ImageList_Draw(himl, lvItem.iImage, hdc, rcIcon.left, rcIcon.top,
3631 (lvItem.state & LVIS_SELECTED) && (infoPtr->bFocus) ? ILD_SELECTED : ILD_NORMAL);
3634 /* Don't bother painting item being edited */
3635 if (infoPtr->hwndEdit && nItem == infoPtr->nEditLabelItem && nSubItem == 0) goto postpaint;
3637 /* draw the selection background, if we're drawing the main item */
3641 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
3642 rcSelect.right = rcBox.right;
3644 if (nmlvcd.clrTextBk != CLR_NONE)
3645 ExtTextOutW(hdc, rcSelect.left, rcSelect.top, ETO_OPAQUE, &rcSelect, 0, 0, 0);
3646 if(lprcFocus) *lprcFocus = rcSelect;
3649 /* figure out the text drawing flags */
3650 uFormat = (uView == LVS_ICON ? (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS) : LV_SL_DT_FLAGS);
3651 if (uView == LVS_ICON)
3652 uFormat = (lprcFocus ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS);
3655 switch (LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->fmt & LVCFMT_JUSTIFYMASK)
3657 case LVCFMT_RIGHT: uFormat |= DT_RIGHT; break;
3658 case LVCFMT_CENTER: uFormat |= DT_CENTER; break;
3659 default: uFormat |= DT_LEFT;
3662 if (!(uFormat & (DT_RIGHT | DT_CENTER)))
3664 if (himl && lvItem.iImage >= 0 && !IsRectEmpty(&rcIcon)) rcLabel.left += IMAGE_PADDING;
3665 else rcLabel.left += LABEL_HOR_PADDING;
3667 else if (uFormat & DT_RIGHT) rcLabel.right -= LABEL_HOR_PADDING;
3668 DrawTextW(hdc, lvItem.pszText, -1, &rcLabel, uFormat);
3671 if (cdsubitemmode & CDRF_NOTIFYPOSTPAINT)
3672 notify_postpaint(infoPtr, &nmlvcd);
3678 * Draws listview items when in owner draw mode.
3681 * [I] infoPtr : valid pointer to the listview structure
3682 * [I] hdc : device context handle
3687 static void LISTVIEW_RefreshOwnerDraw(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3689 UINT uID = GetWindowLongW(infoPtr->hwndSelf, GWL_ID);
3690 DWORD cditemmode = CDRF_DODEFAULT;
3691 NMLVCUSTOMDRAW nmlvcd;
3692 POINT Origin, Position;
3698 ZeroMemory(&dis, sizeof(dis));
3700 /* Get scroll info once before loop */
3701 LISTVIEW_GetOrigin(infoPtr, &Origin);
3703 /* iterate through the invalidated rows */
3704 while(iterator_next(i))
3706 item.iItem = i->nItem;
3708 item.mask = LVIF_PARAM | LVIF_STATE;
3709 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
3710 if (!LISTVIEW_GetItemW(infoPtr, &item)) continue;
3712 dis.CtlType = ODT_LISTVIEW;
3714 dis.itemID = item.iItem;
3715 dis.itemAction = ODA_DRAWENTIRE;
3717 if (item.state & LVIS_SELECTED) dis.itemState |= ODS_SELECTED;
3718 if (infoPtr->bFocus && (item.state & LVIS_FOCUSED)) dis.itemState |= ODS_FOCUS;
3719 dis.hwndItem = infoPtr->hwndSelf;
3721 LISTVIEW_GetItemOrigin(infoPtr, dis.itemID, &Position);
3722 dis.rcItem.left = Position.x + Origin.x;
3723 dis.rcItem.right = dis.rcItem.left + infoPtr->nItemWidth;
3724 dis.rcItem.top = Position.y + Origin.y;
3725 dis.rcItem.bottom = dis.rcItem.top + infoPtr->nItemHeight;
3726 dis.itemData = item.lParam;
3728 TRACE("item=%s, rcItem=%s\n", debuglvitem_t(&item, TRUE), debugrect(&dis.rcItem));
3731 * Even if we do not send the CDRF_NOTIFYITEMDRAW we need to fill the nmlvcd
3732 * structure for the rest. of the paint cycle
3734 customdraw_fill(&nmlvcd, infoPtr, hdc, &dis.rcItem, &item);
3735 if (cdmode & CDRF_NOTIFYITEMDRAW)
3736 cditemmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3738 if (!(cditemmode & CDRF_SKIPDEFAULT))
3740 prepaint_setup (infoPtr, hdc, &nmlvcd);
3741 SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
3744 if (cditemmode & CDRF_NOTIFYPOSTPAINT)
3745 notify_postpaint(infoPtr, &nmlvcd);
3751 * Draws listview items when in report display mode.
3754 * [I] infoPtr : valid pointer to the listview structure
3755 * [I] hdc : device context handle
3756 * [I] cdmode : custom draw mode
3761 static void LISTVIEW_RefreshReport(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3764 RECT rcClip, rcItem;
3765 POINT Origin, Position;
3771 /* figure out what to draw */
3772 rgntype = GetClipBox(hdc, &rcClip);
3773 if (rgntype == NULLREGION) return;
3775 /* Get scroll info once before loop */
3776 LISTVIEW_GetOrigin(infoPtr, &Origin);
3778 /* narrow down the columns we need to paint */
3779 for(colRange.lower = 0; colRange.lower < DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.lower++)
3781 LISTVIEW_GetHeaderRect(infoPtr, colRange.lower, &rcItem);
3782 if (rcItem.right + Origin.x >= rcClip.left) break;
3784 for(colRange.upper = DPA_GetPtrCount(infoPtr->hdpaColumns); colRange.upper > 0; colRange.upper--)
3786 LISTVIEW_GetHeaderRect(infoPtr, colRange.upper - 1, &rcItem);
3787 if (rcItem.left + Origin.x < rcClip.right) break;
3789 iterator_rangeitems(&j, colRange);
3791 /* in full row select, we _have_ to draw the main item */
3792 if (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT)
3795 /* iterate through the invalidated rows */
3796 while(iterator_next(i))
3798 /* iterate through the invalidated columns */
3799 while(iterator_next(&j))
3801 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3802 Position.x += Origin.x;
3803 Position.y += Origin.y;
3805 if (rgntype == COMPLEXREGION && !((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && j.nItem == 0))
3807 LISTVIEW_GetHeaderRect(infoPtr, j.nItem, &rcItem);
3809 rcItem.bottom = infoPtr->nItemHeight;
3810 OffsetRect(&rcItem, Position.x, Position.y);
3811 if (!RectVisible(hdc, &rcItem)) continue;
3814 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, j.nItem, Position, cdmode);
3817 iterator_destroy(&j);
3822 * Draws listview items when in list display mode.
3825 * [I] infoPtr : valid pointer to the listview structure
3826 * [I] hdc : device context handle
3827 * [I] cdmode : custom draw mode
3832 static void LISTVIEW_RefreshList(LISTVIEW_INFO *infoPtr, ITERATOR *i, HDC hdc, DWORD cdmode)
3834 POINT Origin, Position;
3836 /* Get scroll info once before loop */
3837 LISTVIEW_GetOrigin(infoPtr, &Origin);
3839 while(iterator_prev(i))
3841 LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
3842 Position.x += Origin.x;
3843 Position.y += Origin.y;
3845 LISTVIEW_DrawItem(infoPtr, hdc, i->nItem, 0, Position, cdmode);
3852 * Draws listview items.
3855 * [I] infoPtr : valid pointer to the listview structure
3856 * [I] hdc : device context handle
3861 static void LISTVIEW_Refresh(LISTVIEW_INFO *infoPtr, HDC hdc)
3863 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3864 COLORREF oldTextColor, oldClrTextBk, oldClrText;
3865 NMLVCUSTOMDRAW nmlvcd;
3872 LISTVIEW_DUMP(infoPtr);
3874 infoPtr->bIsDrawing = TRUE;
3876 /* save dc values we're gonna trash while drawing */
3877 hOldFont = SelectObject(hdc, infoPtr->hFont);
3878 oldBkMode = GetBkMode(hdc);
3879 infoPtr->clrTextBkDefault = GetBkColor(hdc);
3880 oldTextColor = GetTextColor(hdc);
3882 oldClrTextBk = infoPtr->clrTextBk;
3883 oldClrText = infoPtr->clrText;
3885 infoPtr->cditemmode = CDRF_DODEFAULT;
3887 GetClientRect(infoPtr->hwndSelf, &rcClient);
3888 customdraw_fill(&nmlvcd, infoPtr, hdc, &rcClient, 0);
3889 cdmode = notify_customdraw(infoPtr, CDDS_PREPAINT, &nmlvcd);
3890 if (cdmode & CDRF_SKIPDEFAULT) goto enddraw;
3891 prepaint_setup(infoPtr, hdc, &nmlvcd);
3893 /* Use these colors to draw the items */
3894 infoPtr->clrTextBk = nmlvcd.clrTextBk;
3895 infoPtr->clrText = nmlvcd.clrText;
3897 /* nothing to draw */
3898 if(infoPtr->nItemCount == 0) goto enddraw;
3900 /* figure out what we need to draw */
3901 iterator_visibleitems(&i, infoPtr, hdc);
3903 /* send cache hint notification */
3904 if (infoPtr->dwStyle & LVS_OWNERDATA)
3906 RANGE range = iterator_range(&i);
3909 ZeroMemory(&nmlv, sizeof(NMLVCACHEHINT));
3910 nmlv.iFrom = range.lower;
3911 nmlv.iTo = range.upper - 1;
3912 notify_hdr(infoPtr, LVN_ODCACHEHINT, &nmlv.hdr);
3915 if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (uView == LVS_REPORT))
3916 LISTVIEW_RefreshOwnerDraw(infoPtr, &i, hdc, cdmode);
3919 if (uView == LVS_REPORT)
3920 LISTVIEW_RefreshReport(infoPtr, &i, hdc, cdmode);
3921 else /* LVS_LIST, LVS_ICON or LVS_SMALLICON */
3922 LISTVIEW_RefreshList(infoPtr, &i, hdc, cdmode);
3924 /* if we have a focus rect, draw it */
3925 if (infoPtr->bFocus)
3926 DrawFocusRect(hdc, &infoPtr->rcFocus);
3928 iterator_destroy(&i);
3931 if (cdmode & CDRF_NOTIFYPOSTPAINT)
3932 notify_postpaint(infoPtr, &nmlvcd);
3934 infoPtr->clrTextBk = oldClrTextBk;
3935 infoPtr->clrText = oldClrText;
3937 SelectObject(hdc, hOldFont);
3938 SetBkMode(hdc, oldBkMode);
3939 SetBkColor(hdc, infoPtr->clrTextBkDefault);
3940 SetTextColor(hdc, oldTextColor);
3941 infoPtr->bIsDrawing = FALSE;
3947 * Calculates the approximate width and height of a given number of items.
3950 * [I] infoPtr : valid pointer to the listview structure
3951 * [I] nItemCount : number of items
3952 * [I] wWidth : width
3953 * [I] wHeight : height
3956 * Returns a DWORD. The width in the low word and the height in high word.
3958 static DWORD LISTVIEW_ApproximateViewRect(LISTVIEW_INFO *infoPtr, INT nItemCount,
3959 WORD wWidth, WORD wHeight)
3961 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
3962 INT nItemCountPerColumn = 1;
3963 INT nColumnCount = 0;
3964 DWORD dwViewRect = 0;
3966 if (nItemCount == -1)
3967 nItemCount = infoPtr->nItemCount;
3969 if (uView == LVS_LIST)
3971 if (wHeight == 0xFFFF)
3973 /* use current height */
3974 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
3977 if (wHeight < infoPtr->nItemHeight)
3978 wHeight = infoPtr->nItemHeight;
3982 if (infoPtr->nItemHeight > 0)
3984 nItemCountPerColumn = wHeight / infoPtr->nItemHeight;
3985 if (nItemCountPerColumn == 0)
3986 nItemCountPerColumn = 1;
3988 if (nItemCount % nItemCountPerColumn != 0)
3989 nColumnCount = nItemCount / nItemCountPerColumn;
3991 nColumnCount = nItemCount / nItemCountPerColumn + 1;
3995 /* Microsoft padding magic */
3996 wHeight = nItemCountPerColumn * infoPtr->nItemHeight + 2;
3997 wWidth = nColumnCount * infoPtr->nItemWidth + 2;
3999 dwViewRect = MAKELONG(wWidth, wHeight);
4001 else if (uView == LVS_REPORT)
4005 if (infoPtr->nItemCount > 0)
4007 LISTVIEW_GetItemBox(infoPtr, 0, &rcBox);
4008 wWidth = rcBox.right - rcBox.left;
4009 wHeight = (rcBox.bottom - rcBox.top) * nItemCount;
4013 /* use current height and width */
4014 if (wHeight == 0xffff)
4015 wHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
4016 if (wWidth == 0xffff)
4017 wWidth = infoPtr->rcList.right - infoPtr->rcList.left;
4020 dwViewRect = MAKELONG(wWidth, wHeight);
4022 else if (uView == LVS_SMALLICON)
4023 FIXME("uView == LVS_SMALLICON: not implemented\n");
4024 else if (uView == LVS_ICON)
4025 FIXME("uView == LVS_ICON: not implemented\n");
4033 * Create a drag image list for the specified item.
4036 * [I] infoPtr : valid pointer to the listview structure
4037 * [I] iItem : index of item
4038 * [O] lppt : Upperr-left corner of the image
4041 * Returns a handle to the image list if successful, NULL otherwise.
4043 static HIMAGELIST LISTVIEW_CreateDragImage(LISTVIEW_INFO *infoPtr, INT iItem, LPPOINT lppt)
4049 HBITMAP hbmp, hOldbmp;
4050 HIMAGELIST dragList = 0;
4051 TRACE("iItem=%d Count=%d \n", iItem, infoPtr->nItemCount);
4053 if (iItem < 0 || iItem >= infoPtr->nItemCount)
4056 rcItem.left = LVIR_BOUNDS;
4057 if (!LISTVIEW_GetItemRect(infoPtr, iItem, &rcItem))
4060 lppt->x = rcItem.left;
4061 lppt->y = rcItem.top;
4063 size.cx = rcItem.right - rcItem.left;
4064 size.cy = rcItem.bottom - rcItem.top;
4066 hdcOrig = GetDC(infoPtr->hwndSelf);
4067 hdc = CreateCompatibleDC(hdcOrig);
4068 hbmp = CreateCompatibleBitmap(hdcOrig, size.cx, size.cy);
4069 hOldbmp = SelectObject(hdc, hbmp);
4071 rcItem.left = rcItem.top = 0;
4072 rcItem.right = size.cx;
4073 rcItem.bottom = size.cy;
4074 FillRect(hdc, &rcItem, infoPtr->hBkBrush);
4077 if (LISTVIEW_DrawItem(infoPtr, hdc, iItem, 0, pos, infoPtr->cditemmode))
4079 dragList = ImageList_Create(size.cx, size.cy, ILC_COLOR, 10, 10);
4080 SelectObject(hdc, hOldbmp);
4081 ImageList_Add(dragList, hbmp, 0);
4084 SelectObject(hdc, hOldbmp);
4088 ReleaseDC(infoPtr->hwndSelf, hdcOrig);
4090 TRACE("ret=%p\n", dragList);
4098 * Removes all listview items and subitems.
4101 * [I] infoPtr : valid pointer to the listview structure
4107 static BOOL LISTVIEW_DeleteAllItems(LISTVIEW_INFO *infoPtr)
4110 HDPA hdpaSubItems = NULL;
4117 /* we do it directly, to avoid notifications */
4118 ranges_clear(infoPtr->selectionRanges);
4119 infoPtr->nSelectionMark = -1;
4120 infoPtr->nFocusedItem = -1;
4121 SetRectEmpty(&infoPtr->rcFocus);
4122 /* But we are supposed to leave nHotItem as is! */
4125 /* send LVN_DELETEALLITEMS notification */
4126 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
4128 bSuppress = notify_listview(infoPtr, LVN_DELETEALLITEMS, &nmlv);
4130 for (i = infoPtr->nItemCount - 1; i >= 0; i--)
4132 /* send LVN_DELETEITEM notification, if not supressed */
4133 if (!bSuppress) notify_deleteitem(infoPtr, i);
4134 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4136 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
4137 for (j = 0; j < DPA_GetPtrCount(hdpaSubItems); j++)
4139 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, j);
4140 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4143 DPA_Destroy(hdpaSubItems);
4144 DPA_DeletePtr(infoPtr->hdpaItems, i);
4146 DPA_DeletePtr(infoPtr->hdpaPosX, i);
4147 DPA_DeletePtr(infoPtr->hdpaPosY, i);
4148 infoPtr->nItemCount --;
4151 LISTVIEW_UpdateScroll(infoPtr);
4153 LISTVIEW_InvalidateList(infoPtr);
4160 * Scrolls, and updates the columns, when a column is changing width.
4163 * [I] infoPtr : valid pointer to the listview structure
4164 * [I] nColumn : column to scroll
4165 * [I] dx : amount of scroll, in pixels
4170 static void LISTVIEW_ScrollColumns(LISTVIEW_INFO *infoPtr, INT nColumn, INT dx)
4172 COLUMN_INFO *lpColumnInfo;
4176 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) < 1) return;
4177 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns) - 1));
4178 rcCol = lpColumnInfo->rcHeader;
4179 if (nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns))
4180 rcCol.left = rcCol.right;
4182 /* ajust the other columns */
4183 for (nCol = nColumn; nCol < DPA_GetPtrCount(infoPtr->hdpaColumns); nCol++)
4185 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nCol);
4186 lpColumnInfo->rcHeader.left += dx;
4187 lpColumnInfo->rcHeader.right += dx;
4190 /* do not update screen if not in report mode */
4191 if (!is_redrawing(infoPtr) || (infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return;
4193 /* if we have a focus, must first erase the focus rect */
4194 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, FALSE);
4196 /* Need to reset the item width when inserting a new column */
4197 infoPtr->nItemWidth += dx;
4199 LISTVIEW_UpdateScroll(infoPtr);
4201 /* scroll to cover the deleted column, and invalidate for redraw */
4202 rcOld = infoPtr->rcList;
4203 rcOld.left = rcCol.left;
4204 ScrollWindowEx(infoPtr->hwndSelf, dx, 0, &rcOld, &rcOld, 0, 0, SW_ERASE | SW_INVALIDATE);
4206 /* we can restore focus now */
4207 if (infoPtr->bFocus) LISTVIEW_ShowFocusRect(infoPtr, TRUE);
4212 * Removes a column from the listview control.
4215 * [I] infoPtr : valid pointer to the listview structure
4216 * [I] nColumn : column index
4222 static BOOL LISTVIEW_DeleteColumn(LISTVIEW_INFO *infoPtr, INT nColumn)
4226 TRACE("nColumn=%d\n", nColumn);
4228 if (nColumn < 0 || DPA_GetPtrCount(infoPtr->hdpaColumns) == 0
4229 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4231 /* While the MSDN specifically says that column zero should not be deleted,
4232 what actually happens is that the column itself is deleted but no items or subitems
4236 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
4238 if (!Header_DeleteItem(infoPtr->hwndHeader, nColumn))
4241 Free(DPA_GetPtr(infoPtr->hdpaColumns, nColumn));
4242 DPA_DeletePtr(infoPtr->hdpaColumns, nColumn);
4244 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && nColumn)
4246 SUBITEM_INFO *lpSubItem, *lpDelItem;
4248 INT nItem, nSubItem, i;
4250 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
4252 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
4255 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4257 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
4258 if (lpSubItem->iSubItem == nColumn)
4261 lpDelItem = lpSubItem;
4263 else if (lpSubItem->iSubItem > nColumn)
4265 lpSubItem->iSubItem--;
4269 /* if we found our subitem, zapp it */
4273 if (is_textW(lpDelItem->hdr.pszText))
4274 Free(lpDelItem->hdr.pszText);
4279 /* free dpa memory */
4280 DPA_DeletePtr(hdpaSubItems, nSubItem);
4285 /* update the other column info */
4286 if(DPA_GetPtrCount(infoPtr->hdpaColumns) == 0)
4287 LISTVIEW_InvalidateList(infoPtr);
4289 LISTVIEW_ScrollColumns(infoPtr, nColumn, -(rcCol.right - rcCol.left));
4296 * Invalidates the listview after an item's insertion or deletion.
4299 * [I] infoPtr : valid pointer to the listview structure
4300 * [I] nItem : item index
4301 * [I] dir : -1 if deleting, 1 if inserting
4306 static void LISTVIEW_ScrollOnInsert(LISTVIEW_INFO *infoPtr, INT nItem, INT dir)
4308 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4309 INT nPerCol, nItemCol, nItemRow;
4313 /* if we don't refresh, what's the point of scrolling? */
4314 if (!is_redrawing(infoPtr)) return;
4316 assert (abs(dir) == 1);
4318 /* arrange icons if autoarrange is on */
4319 if (is_autoarrange(infoPtr))
4321 BOOL arrange = TRUE;
4322 if (dir < 0 && nItem >= infoPtr->nItemCount) arrange = FALSE;
4323 if (dir > 0 && nItem == infoPtr->nItemCount - 1) arrange = FALSE;
4324 if (arrange) LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
4327 /* scrollbars need updating */
4328 LISTVIEW_UpdateScroll(infoPtr);
4330 /* figure out the item's position */
4331 if (uView == LVS_REPORT)
4332 nPerCol = infoPtr->nItemCount + 1;
4333 else if (uView == LVS_LIST)
4334 nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
4335 else /* LVS_ICON, or LVS_SMALLICON */
4338 nItemCol = nItem / nPerCol;
4339 nItemRow = nItem % nPerCol;
4340 LISTVIEW_GetOrigin(infoPtr, &Origin);
4342 /* move the items below up a slot */
4343 rcScroll.left = nItemCol * infoPtr->nItemWidth;
4344 rcScroll.top = nItemRow * infoPtr->nItemHeight;
4345 rcScroll.right = rcScroll.left + infoPtr->nItemWidth;
4346 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4347 OffsetRect(&rcScroll, Origin.x, Origin.y);
4348 TRACE("rcScroll=%s, dx=%d\n", debugrect(&rcScroll), dir * infoPtr->nItemHeight);
4349 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4351 TRACE("Scrolling rcScroll=%s, rcList=%s\n", debugrect(&rcScroll), debugrect(&infoPtr->rcList));
4352 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4353 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4356 /* report has only that column, so we're done */
4357 if (uView == LVS_REPORT) return;
4359 /* now for LISTs, we have to deal with the columns to the right */
4360 rcScroll.left = (nItemCol + 1) * infoPtr->nItemWidth;
4362 rcScroll.right = (infoPtr->nItemCount / nPerCol + 1) * infoPtr->nItemWidth;
4363 rcScroll.bottom = nPerCol * infoPtr->nItemHeight;
4364 OffsetRect(&rcScroll, Origin.x, Origin.y);
4365 if (IntersectRect(&rcScroll, &rcScroll, &infoPtr->rcList))
4366 ScrollWindowEx(infoPtr->hwndSelf, 0, dir * infoPtr->nItemHeight,
4367 &rcScroll, &rcScroll, 0, 0, SW_ERASE | SW_INVALIDATE);
4372 * Removes an item from the listview control.
4375 * [I] infoPtr : valid pointer to the listview structure
4376 * [I] nItem : item index
4382 static BOOL LISTVIEW_DeleteItem(LISTVIEW_INFO *infoPtr, INT nItem)
4384 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4387 TRACE("(nItem=%d)\n", nItem);
4389 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
4391 /* remove selection, and focus */
4393 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
4394 LISTVIEW_SetItemState(infoPtr, nItem, &item);
4396 /* send LVN_DELETEITEM notification. */
4397 notify_deleteitem(infoPtr, nItem);
4399 /* we need to do this here, because we'll be deleting stuff */
4400 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4401 LISTVIEW_InvalidateItem(infoPtr, nItem);
4403 if (!(infoPtr->dwStyle & LVS_OWNERDATA))
4409 hdpaSubItems = (HDPA)DPA_DeletePtr(infoPtr->hdpaItems, nItem);
4410 for (i = 0; i < DPA_GetPtrCount(hdpaSubItems); i++)
4412 hdrItem = (ITEMHDR *)DPA_GetPtr(hdpaSubItems, i);
4413 if (is_textW(hdrItem->pszText)) Free(hdrItem->pszText);
4416 DPA_Destroy(hdpaSubItems);
4419 if (uView == LVS_SMALLICON || uView == LVS_ICON)
4421 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
4422 DPA_DeletePtr(infoPtr->hdpaPosY, nItem);
4425 infoPtr->nItemCount--;
4426 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
4428 /* now is the invalidation fun */
4429 LISTVIEW_ScrollOnInsert(infoPtr, nItem, -1);
4436 * Callback implementation for editlabel control
4439 * [I] infoPtr : valid pointer to the listview structure
4440 * [I] pszText : modified text
4441 * [I] isW : TRUE if psxText is Unicode, FALSE if it's ANSI
4447 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *infoPtr, LPWSTR pszText, BOOL isW)
4449 NMLVDISPINFOW dispInfo;
4451 TRACE("(pszText=%s, isW=%d)\n", debugtext_t(pszText, isW), isW);
4453 ZeroMemory(&dispInfo, sizeof(dispInfo));
4454 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE;
4455 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4456 dispInfo.item.iSubItem = 0;
4457 dispInfo.item.stateMask = ~0;
4458 if (!LISTVIEW_GetItemW(infoPtr, &dispInfo.item)) return FALSE;
4459 /* add the text from the edit in */
4460 dispInfo.item.mask |= LVIF_TEXT;
4461 dispInfo.item.pszText = pszText;
4462 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4464 /* Do we need to update the Item Text */
4465 if (!notify_dispinfoT(infoPtr, LVN_ENDLABELEDITW, &dispInfo, isW)) return FALSE;
4466 if (!pszText) return TRUE;
4468 ZeroMemory(&dispInfo, sizeof(dispInfo));
4469 dispInfo.item.mask = LVIF_TEXT;
4470 dispInfo.item.iItem = infoPtr->nEditLabelItem;
4471 dispInfo.item.iSubItem = 0;
4472 dispInfo.item.pszText = pszText;
4473 dispInfo.item.cchTextMax = textlenT(pszText, isW);
4474 return LISTVIEW_SetItemT(infoPtr, &dispInfo.item, isW);
4479 * Begin in place editing of specified list view item
4482 * [I] infoPtr : valid pointer to the listview structure
4483 * [I] nItem : item index
4484 * [I] isW : TRUE if it's a Unicode req, FALSE if ASCII
4490 static HWND LISTVIEW_EditLabelT(LISTVIEW_INFO *infoPtr, INT nItem, BOOL isW)
4492 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
4493 NMLVDISPINFOW dispInfo;
4496 TRACE("(nItem=%d, isW=%d)\n", nItem, isW);
4498 if (~infoPtr->dwStyle & LVS_EDITLABELS) return 0;
4499 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
4501 infoPtr->nEditLabelItem = nItem;
4503 /* Is the EditBox still there, if so remove it */
4504 if(infoPtr->hwndEdit != 0)
4506 SetFocus(infoPtr->hwndSelf);
4507 infoPtr->hwndEdit = 0;
4510 LISTVIEW_SetSelection(infoPtr, nItem);
4511 LISTVIEW_SetItemFocus(infoPtr, nItem);
4512 LISTVIEW_InvalidateItem(infoPtr, nItem);
4514 rect.left = LVIR_LABEL;
4515 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rect)) return 0;
4517 ZeroMemory(&dispInfo, sizeof(dispInfo));
4518 dispInfo.item.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
4519 dispInfo.item.iItem = nItem;
4520 dispInfo.item.iSubItem = 0;
4521 dispInfo.item.stateMask = ~0;
4522 dispInfo.item.pszText = szDispText;
4523 dispInfo.item.cchTextMax = DISP_TEXT_SIZE;
4524 if (!LISTVIEW_GetItemT(infoPtr, &dispInfo.item, isW)) return 0;
4526 infoPtr->hwndEdit = CreateEditLabelT(infoPtr, dispInfo.item.pszText, WS_VISIBLE,
4527 rect.left-2, rect.top-1, 0, rect.bottom - rect.top+2, isW);
4528 if (!infoPtr->hwndEdit) return 0;
4530 if (notify_dispinfoT(infoPtr, LVN_BEGINLABELEDITW, &dispInfo, isW))
4532 SendMessageW(infoPtr->hwndEdit, WM_CLOSE, 0, 0);
4533 infoPtr->hwndEdit = 0;
4537 ShowWindow(infoPtr->hwndEdit, SW_NORMAL);
4538 SetFocus(infoPtr->hwndEdit);
4539 SendMessageW(infoPtr->hwndEdit, EM_SETSEL, 0, -1);
4540 return infoPtr->hwndEdit;
4546 * Ensures the specified item is visible, scrolling into view if necessary.
4549 * [I] infoPtr : valid pointer to the listview structure
4550 * [I] nItem : item index
4551 * [I] bPartial : partially or entirely visible
4557 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *infoPtr, INT nItem, BOOL bPartial)
4559 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4560 INT nScrollPosHeight = 0;
4561 INT nScrollPosWidth = 0;
4562 INT nHorzAdjust = 0;
4563 INT nVertAdjust = 0;
4566 RECT rcItem, rcTemp;
4568 rcItem.left = LVIR_BOUNDS;
4569 if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) return FALSE;
4571 if (bPartial && IntersectRect(&rcTemp, &infoPtr->rcList, &rcItem)) return TRUE;
4573 if (rcItem.left < infoPtr->rcList.left || rcItem.right > infoPtr->rcList.right)
4575 /* scroll left/right, but in LVS_REPORT mode */
4576 if (uView == LVS_LIST)
4577 nScrollPosWidth = infoPtr->nItemWidth;
4578 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
4579 nScrollPosWidth = 1;
4581 if (rcItem.left < infoPtr->rcList.left)
4584 if (uView != LVS_REPORT) nHorzDiff = rcItem.left - infoPtr->rcList.left;
4589 if (uView != LVS_REPORT) nHorzDiff = rcItem.right - infoPtr->rcList.right;
4593 if (rcItem.top < infoPtr->rcList.top || rcItem.bottom > infoPtr->rcList.bottom)
4595 /* scroll up/down, but not in LVS_LIST mode */
4596 if (uView == LVS_REPORT)
4597 nScrollPosHeight = infoPtr->nItemHeight;
4598 else if ((uView == LVS_ICON) || (uView == LVS_SMALLICON))
4599 nScrollPosHeight = 1;
4601 if (rcItem.top < infoPtr->rcList.top)
4604 if (uView != LVS_LIST) nVertDiff = rcItem.top - infoPtr->rcList.top;
4609 if (uView != LVS_LIST) nVertDiff = rcItem.bottom - infoPtr->rcList.bottom;
4613 if (!nScrollPosWidth && !nScrollPosHeight) return TRUE;
4615 if (nScrollPosWidth)
4617 INT diff = nHorzDiff / nScrollPosWidth;
4618 if (nHorzDiff % nScrollPosWidth) diff += nHorzAdjust;
4619 LISTVIEW_HScroll(infoPtr, SB_INTERNAL, diff, 0);
4622 if (nScrollPosHeight)
4624 INT diff = nVertDiff / nScrollPosHeight;
4625 if (nVertDiff % nScrollPosHeight) diff += nVertAdjust;
4626 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, diff, 0);
4634 * Searches for an item with specific characteristics.
4637 * [I] hwnd : window handle
4638 * [I] nStart : base item index
4639 * [I] lpFindInfo : item information to look for
4642 * SUCCESS : index of item
4645 static INT LISTVIEW_FindItemW(LISTVIEW_INFO *infoPtr, INT nStart,
4646 const LVFINDINFOW *lpFindInfo)
4648 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
4649 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
4650 BOOL bWrap = FALSE, bNearest = FALSE;
4651 INT nItem = nStart + 1, nLast = infoPtr->nItemCount, nNearestItem = -1;
4652 ULONG xdist, ydist, dist, mindist = 0x7fffffff;
4653 POINT Position, Destination;
4656 if (!lpFindInfo || nItem < 0) return -1;
4659 if (lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL))
4661 lvItem.mask |= LVIF_TEXT;
4662 lvItem.pszText = szDispText;
4663 lvItem.cchTextMax = DISP_TEXT_SIZE;
4666 if (lpFindInfo->flags & LVFI_WRAP)
4669 if ((lpFindInfo->flags & LVFI_NEARESTXY) &&
4670 (uView == LVS_ICON || uView ==LVS_SMALLICON))
4675 LISTVIEW_GetOrigin(infoPtr, &Origin);
4676 Destination.x = lpFindInfo->pt.x - Origin.x;
4677 Destination.y = lpFindInfo->pt.y - Origin.y;
4678 switch(lpFindInfo->vkDirection)
4680 case VK_DOWN: Destination.y += infoPtr->nItemHeight; break;
4681 case VK_UP: Destination.y -= infoPtr->nItemHeight; break;
4682 case VK_RIGHT: Destination.x += infoPtr->nItemWidth; break;
4683 case VK_LEFT: Destination.x -= infoPtr->nItemWidth; break;
4684 case VK_HOME: Destination.x = Destination.y = 0; break;
4685 case VK_NEXT: Destination.y += infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4686 case VK_PRIOR: Destination.y -= infoPtr->rcList.bottom - infoPtr->rcList.top; break;
4688 LISTVIEW_GetAreaRect(infoPtr, &rcArea);
4689 Destination.x = rcArea.right;
4690 Destination.y = rcArea.bottom;
4692 default: ERR("Unknown vkDirection=%d\n", lpFindInfo->vkDirection);
4697 /* if LVFI_PARAM is specified, all other flags are ignored */
4698 if (lpFindInfo->flags & LVFI_PARAM)
4700 lvItem.mask |= LVIF_PARAM;
4702 lvItem.mask &= ~LVIF_TEXT;
4706 for (; nItem < nLast; nItem++)
4708 lvItem.iItem = nItem;
4709 lvItem.iSubItem = 0;
4710 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
4712 if (lvItem.mask & LVIF_PARAM)
4714 if (lpFindInfo->lParam == lvItem.lParam)
4720 if (lvItem.mask & LVIF_TEXT)
4722 if (lpFindInfo->flags & LVFI_PARTIAL)
4724 if (strstrW(lvItem.pszText, lpFindInfo->psz) == NULL) continue;
4728 if (lstrcmpW(lvItem.pszText, lpFindInfo->psz) != 0) continue;
4732 if (!bNearest) return nItem;
4734 /* This is very inefficient. To do a good job here,
4735 * we need a sorted array of (x,y) item positions */
4736 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
4738 /* compute the distance^2 to the destination */
4739 xdist = Destination.x - Position.x;
4740 ydist = Destination.y - Position.y;
4741 dist = xdist * xdist + ydist * ydist;
4743 /* remember the distance, and item if it's closer */
4747 nNearestItem = nItem;
4754 nLast = min(nStart + 1, infoPtr->nItemCount);
4759 return nNearestItem;
4764 * Searches for an item with specific characteristics.
4767 * [I] hwnd : window handle
4768 * [I] nStart : base item index
4769 * [I] lpFindInfo : item information to look for
4772 * SUCCESS : index of item
4775 static INT LISTVIEW_FindItemA(LISTVIEW_INFO *infoPtr, INT nStart,
4776 const LVFINDINFOA *lpFindInfo)
4778 BOOL hasText = lpFindInfo->flags & (LVFI_STRING | LVFI_PARTIAL);
4782 memcpy(&fiw, lpFindInfo, sizeof(fiw));
4783 if (hasText) fiw.psz = textdupTtoW((LPCWSTR)lpFindInfo->psz, FALSE);
4784 res = LISTVIEW_FindItemW(infoPtr, nStart, &fiw);
4785 if (hasText) textfreeT((LPWSTR)fiw.psz, FALSE);
4791 * Retrieves the background image of the listview control.
4794 * [I] infoPtr : valid pointer to the listview structure
4795 * [O] lpBkImage : background image attributes
4801 /* static BOOL LISTVIEW_GetBkImage(LISTVIEW_INFO *infoPtr, LPLVBKIMAGE lpBkImage) */
4803 /* FIXME (listview, "empty stub!\n"); */
4809 * Retrieves column attributes.
4812 * [I] infoPtr : valid pointer to the listview structure
4813 * [I] nColumn : column index
4814 * [IO] lpColumn : column information
4815 * [I] isW : if TRUE, then lpColumn is a LPLVCOLUMNW
4816 * otherwise it is in fact a LPLVCOLUMNA
4822 static BOOL LISTVIEW_GetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn, LPLVCOLUMNW lpColumn, BOOL isW)
4824 COLUMN_INFO *lpColumnInfo;
4827 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4828 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
4830 /* initialize memory */
4831 ZeroMemory(&hdi, sizeof(hdi));
4833 if (lpColumn->mask & LVCF_TEXT)
4835 hdi.mask |= HDI_TEXT;
4836 hdi.pszText = lpColumn->pszText;
4837 hdi.cchTextMax = lpColumn->cchTextMax;
4840 if (lpColumn->mask & LVCF_IMAGE)
4841 hdi.mask |= HDI_IMAGE;
4843 if (lpColumn->mask & LVCF_ORDER)
4844 hdi.mask |= HDI_ORDER;
4846 if (!SendMessageW(infoPtr->hwndHeader, isW ? HDM_GETITEMW : HDM_GETITEMA, nColumn, (LPARAM)&hdi)) return FALSE;
4848 if (lpColumn->mask & LVCF_FMT)
4849 lpColumn->fmt = lpColumnInfo->fmt;
4851 if (lpColumn->mask & LVCF_WIDTH)
4852 lpColumn->cx = lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
4854 if (lpColumn->mask & LVCF_IMAGE)
4855 lpColumn->iImage = hdi.iImage;
4857 if (lpColumn->mask & LVCF_ORDER)
4858 lpColumn->iOrder = hdi.iOrder;
4864 static BOOL LISTVIEW_GetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, LPINT lpiArray)
4871 /* FIXME: little hack */
4872 for (i = 0; i < iCount; i++)
4880 * Retrieves the column width.
4883 * [I] infoPtr : valid pointer to the listview structure
4884 * [I] int : column index
4887 * SUCCESS : column width
4890 static INT LISTVIEW_GetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn)
4892 INT nColumnWidth = 0;
4895 TRACE("nColumn=%d\n", nColumn);
4897 /* we have a 'column' in LIST and REPORT mode only */
4898 switch(infoPtr->dwStyle & LVS_TYPEMASK)
4901 nColumnWidth = infoPtr->nItemWidth;
4904 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
4905 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
4906 nColumnWidth = rcHeader.right - rcHeader.left;
4910 TRACE("nColumnWidth=%d\n", nColumnWidth);
4911 return nColumnWidth;
4916 * In list or report display mode, retrieves the number of items that can fit
4917 * vertically in the visible area. In icon or small icon display mode,
4918 * retrieves the total number of visible items.
4921 * [I] infoPtr : valid pointer to the listview structure
4924 * Number of fully visible items.
4926 static INT LISTVIEW_GetCountPerPage(LISTVIEW_INFO *infoPtr)
4928 switch (infoPtr->dwStyle & LVS_TYPEMASK)
4932 return infoPtr->nItemCount;
4934 return LISTVIEW_GetCountPerColumn(infoPtr);
4936 return LISTVIEW_GetCountPerRow(infoPtr) * LISTVIEW_GetCountPerColumn(infoPtr);
4944 * Retrieves an image list handle.
4947 * [I] infoPtr : valid pointer to the listview structure
4948 * [I] nImageList : image list identifier
4951 * SUCCESS : image list handle
4954 static HIMAGELIST LISTVIEW_GetImageList(LISTVIEW_INFO *infoPtr, INT nImageList)
4958 case LVSIL_NORMAL: return infoPtr->himlNormal;
4959 case LVSIL_SMALL: return infoPtr->himlSmall;
4960 case LVSIL_STATE: return infoPtr->himlState;
4965 /* LISTVIEW_GetISearchString */
4969 * Retrieves item attributes.
4972 * [I] hwnd : window handle
4973 * [IO] lpLVItem : item info
4974 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
4975 * if FALSE, the lpLVItem is a LPLVITEMA.
4978 * This is the internal 'GetItem' interface -- it tries to
4979 * be smart, and avoids text copies, if possible, by modifing
4980 * lpLVItem->pszText to point to the text string. Please note
4981 * that this is not always possible (e.g. OWNERDATA), so on
4982 * entry you *must* supply valid values for pszText, and cchTextMax.
4983 * The only difference to the documented interface is that upon
4984 * return, you should use *only* the lpLVItem->pszText, rather than
4985 * the buffer pointer you provided on input. Most code already does
4986 * that, so it's not a problem.
4987 * For the two cases when the text must be copied (that is,
4988 * for LVM_GETITEM, and LVM_GETITEMTEXT), use LISTVIEW_GetItemExtT.
4994 static BOOL LISTVIEW_GetItemT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
4996 ITEMHDR callbackHdr = { LPSTR_TEXTCALLBACKW, I_IMAGECALLBACK };
4997 NMLVDISPINFOW dispInfo;
5003 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
5005 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5008 if (lpLVItem->mask == 0) return TRUE;
5010 /* make a local copy */
5011 isubitem = lpLVItem->iSubItem;
5013 /* a quick optimization if all we're asked is the focus state
5014 * these queries are worth optimising since they are common,
5015 * and can be answered in constant time, without the heavy accesses */
5016 if ( (lpLVItem->mask == LVIF_STATE) && (lpLVItem->stateMask == LVIS_FOCUSED) &&
5017 !(infoPtr->uCallbackMask & LVIS_FOCUSED) )
5019 lpLVItem->state = 0;
5020 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5021 lpLVItem->state |= LVIS_FOCUSED;
5025 ZeroMemory(&dispInfo, sizeof(dispInfo));
5027 /* if the app stores all the data, handle it separately */
5028 if (infoPtr->dwStyle & LVS_OWNERDATA)
5030 dispInfo.item.state = 0;
5032 /* apprently, we should not callback for lParam in LVS_OWNERDATA */
5033 if ((lpLVItem->mask & ~(LVIF_STATE | LVIF_PARAM)) || infoPtr->uCallbackMask)
5035 /* NOTE: copy only fields which we _know_ are initialized, some apps
5036 * depend on the uninitialized fields being 0 */
5037 dispInfo.item.mask = lpLVItem->mask & ~LVIF_PARAM;
5038 dispInfo.item.iItem = lpLVItem->iItem;
5039 dispInfo.item.iSubItem = isubitem;
5040 if (lpLVItem->mask & LVIF_TEXT)
5042 dispInfo.item.pszText = lpLVItem->pszText;
5043 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5045 if (lpLVItem->mask & LVIF_STATE)
5046 dispInfo.item.stateMask = lpLVItem->stateMask & infoPtr->uCallbackMask;
5047 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5048 dispInfo.item.stateMask = lpLVItem->stateMask;
5049 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
5051 /* full size structure expected - _WIN32IE >= 0x560 */
5052 *lpLVItem = dispInfo.item;
5054 else if (lpLVItem->mask & LVIF_INDENT)
5056 /* indent member expected - _WIN32IE >= 0x300 */
5057 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iGroupId ));
5061 /* minimal structure expected */
5062 memcpy(lpLVItem, &dispInfo.item, offsetof( LVITEMW, iIndent ));
5064 TRACE(" getdispinfo(1):lpLVItem=%s\n", debuglvitem_t(lpLVItem, isW));
5067 /* make sure lParam is zeroed out */
5068 if (lpLVItem->mask & LVIF_PARAM) lpLVItem->lParam = 0;
5070 /* we store only a little state, so if we're not asked, we're done */
5071 if (!(lpLVItem->mask & LVIF_STATE) || isubitem) return TRUE;
5073 /* if focus is handled by us, report it */
5074 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5076 lpLVItem->state &= ~LVIS_FOCUSED;
5077 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5078 lpLVItem->state |= LVIS_FOCUSED;
5081 /* and do the same for selection, if we handle it */
5082 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5084 lpLVItem->state &= ~LVIS_SELECTED;
5085 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5086 lpLVItem->state |= LVIS_SELECTED;
5092 /* find the item and subitem structures before we proceed */
5093 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
5094 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
5099 SUBITEM_INFO *lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, isubitem);
5100 pItemHdr = lpSubItem ? &lpSubItem->hdr : &callbackHdr;
5103 WARN(" iSubItem invalid (%08x), ignored.\n", isubitem);
5108 pItemHdr = &lpItem->hdr;
5110 /* Do we need to query the state from the app? */
5111 if ((lpLVItem->mask & LVIF_STATE) && infoPtr->uCallbackMask && isubitem == 0)
5113 dispInfo.item.mask |= LVIF_STATE;
5114 dispInfo.item.stateMask = infoPtr->uCallbackMask;
5117 /* Do we need to enquire about the image? */
5118 if ((lpLVItem->mask & LVIF_IMAGE) && pItemHdr->iImage == I_IMAGECALLBACK &&
5119 (isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES)))
5121 dispInfo.item.mask |= LVIF_IMAGE;
5122 dispInfo.item.iImage = I_IMAGECALLBACK;
5125 /* Apps depend on calling back for text if it is NULL or LPSTR_TEXTCALLBACKW */
5126 if ((lpLVItem->mask & LVIF_TEXT) && !is_textW(pItemHdr->pszText))
5128 dispInfo.item.mask |= LVIF_TEXT;
5129 dispInfo.item.pszText = lpLVItem->pszText;
5130 dispInfo.item.cchTextMax = lpLVItem->cchTextMax;
5131 if (dispInfo.item.pszText && dispInfo.item.cchTextMax > 0)
5132 *dispInfo.item.pszText = '\0';
5135 /* If we don't have all the requested info, query the application */
5136 if (dispInfo.item.mask != 0)
5138 dispInfo.item.iItem = lpLVItem->iItem;
5139 dispInfo.item.iSubItem = lpLVItem->iSubItem; /* yes: the original subitem */
5140 dispInfo.item.lParam = lpItem->lParam;
5141 notify_dispinfoT(infoPtr, LVN_GETDISPINFOW, &dispInfo, isW);
5142 TRACE(" getdispinfo(2):item=%s\n", debuglvitem_t(&dispInfo.item, isW));
5145 /* we should not store values for subitems */
5146 if (isubitem) dispInfo.item.mask &= ~LVIF_DI_SETITEM;
5148 /* Now, handle the iImage field */
5149 if (dispInfo.item.mask & LVIF_IMAGE)
5151 lpLVItem->iImage = dispInfo.item.iImage;
5152 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->iImage == I_IMAGECALLBACK)
5153 pItemHdr->iImage = dispInfo.item.iImage;
5155 else if (lpLVItem->mask & LVIF_IMAGE)
5157 if(isubitem == 0 || (infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES))
5158 lpLVItem->iImage = pItemHdr->iImage;
5160 lpLVItem->iImage = 0;
5163 /* The pszText field */
5164 if (dispInfo.item.mask & LVIF_TEXT)
5166 if ((dispInfo.item.mask & LVIF_DI_SETITEM) && pItemHdr->pszText)
5167 textsetptrT(&pItemHdr->pszText, dispInfo.item.pszText, isW);
5169 lpLVItem->pszText = dispInfo.item.pszText;
5171 else if (lpLVItem->mask & LVIF_TEXT)
5173 if (isW) lpLVItem->pszText = pItemHdr->pszText;
5174 else textcpynT(lpLVItem->pszText, isW, pItemHdr->pszText, TRUE, lpLVItem->cchTextMax);
5177 /* if this is a subitem, we're done */
5178 if (isubitem) return TRUE;
5180 /* Next is the lParam field */
5181 if (dispInfo.item.mask & LVIF_PARAM)
5183 lpLVItem->lParam = dispInfo.item.lParam;
5184 if ((dispInfo.item.mask & LVIF_DI_SETITEM))
5185 lpItem->lParam = dispInfo.item.lParam;
5187 else if (lpLVItem->mask & LVIF_PARAM)
5188 lpLVItem->lParam = lpItem->lParam;
5190 /* ... the state field (this one is different due to uCallbackmask) */
5191 if (lpLVItem->mask & LVIF_STATE)
5193 lpLVItem->state = lpItem->state;
5194 if (dispInfo.item.mask & LVIF_STATE)
5196 lpLVItem->state &= ~dispInfo.item.stateMask;
5197 lpLVItem->state |= (dispInfo.item.state & dispInfo.item.stateMask);
5199 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED )
5201 lpLVItem->state &= ~LVIS_FOCUSED;
5202 if (infoPtr->nFocusedItem == lpLVItem->iItem)
5203 lpLVItem->state |= LVIS_FOCUSED;
5205 if ( lpLVItem->stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED )
5207 lpLVItem->state &= ~LVIS_SELECTED;
5208 if (ranges_contain(infoPtr->selectionRanges, lpLVItem->iItem))
5209 lpLVItem->state |= LVIS_SELECTED;
5213 /* and last, but not least, the indent field */
5214 if (lpLVItem->mask & LVIF_INDENT)
5215 lpLVItem->iIndent = lpItem->iIndent;
5222 * Retrieves item attributes.
5225 * [I] hwnd : window handle
5226 * [IO] lpLVItem : item info
5227 * [I] isW : if TRUE, then lpLVItem is a LPLVITEMW,
5228 * if FALSE, the lpLVItem is a LPLVITEMA.
5231 * This is the external 'GetItem' interface -- it properly copies
5232 * the text in the provided buffer.
5238 static BOOL LISTVIEW_GetItemExtT(LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, BOOL isW)
5243 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
5246 pszText = lpLVItem->pszText;
5247 bResult = LISTVIEW_GetItemT(infoPtr, lpLVItem, isW);
5248 if (bResult && lpLVItem->pszText != pszText)
5249 textcpynT(pszText, isW, lpLVItem->pszText, isW, lpLVItem->cchTextMax);
5250 lpLVItem->pszText = pszText;
5258 * Retrieves the position (upper-left) of the listview control item.
5259 * Note that for LVS_ICON style, the upper-left is that of the icon
5260 * and not the bounding box.
5263 * [I] infoPtr : valid pointer to the listview structure
5264 * [I] nItem : item index
5265 * [O] lpptPosition : coordinate information
5271 static BOOL LISTVIEW_GetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
5273 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5276 TRACE("(nItem=%d, lpptPosition=%p)\n", nItem, lpptPosition);
5278 if (!lpptPosition || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5280 LISTVIEW_GetOrigin(infoPtr, &Origin);
5281 LISTVIEW_GetItemOrigin(infoPtr, nItem, lpptPosition);
5283 if (uView == LVS_ICON)
5285 lpptPosition->x += (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
5286 lpptPosition->y += ICON_TOP_PADDING;
5288 lpptPosition->x += Origin.x;
5289 lpptPosition->y += Origin.y;
5291 TRACE (" lpptPosition=%s\n", debugpoint(lpptPosition));
5298 * Retrieves the bounding rectangle for a listview control item.
5301 * [I] infoPtr : valid pointer to the listview structure
5302 * [I] nItem : item index
5303 * [IO] lprc : bounding rectangle coordinates
5304 * lprc->left specifies the portion of the item for which the bounding
5305 * rectangle will be retrieved.
5307 * LVIR_BOUNDS Returns the bounding rectangle of the entire item,
5308 * including the icon and label.
5311 * * Experiment shows that native control returns:
5312 * * width = min (48, length of text line)
5313 * * .left = position.x - (width - iconsize.cx)/2
5314 * * .right = .left + width
5315 * * height = #lines of text * ntmHeight + icon height + 8
5316 * * .top = position.y - 2
5317 * * .bottom = .top + height
5318 * * separation between items .y = itemSpacing.cy - height
5319 * * .x = itemSpacing.cx - width
5320 * LVIR_ICON Returns the bounding rectangle of the icon or small icon.
5323 * * Experiment shows that native control returns:
5324 * * width = iconSize.cx + 16
5325 * * .left = position.x - (width - iconsize.cx)/2
5326 * * .right = .left + width
5327 * * height = iconSize.cy + 4
5328 * * .top = position.y - 2
5329 * * .bottom = .top + height
5330 * * separation between items .y = itemSpacing.cy - height
5331 * * .x = itemSpacing.cx - width
5332 * LVIR_LABEL Returns the bounding rectangle of the item text.
5335 * * Experiment shows that native control returns:
5336 * * width = text length
5337 * * .left = position.x - width/2
5338 * * .right = .left + width
5339 * * height = ntmH * linecount + 2
5340 * * .top = position.y + iconSize.cy + 6
5341 * * .bottom = .top + height
5342 * * separation between items .y = itemSpacing.cy - height
5343 * * .x = itemSpacing.cx - width
5344 * LVIR_SELECTBOUNDS Returns the union of the LVIR_ICON and LVIR_LABEL
5345 * rectangles, but excludes columns in report view.
5352 * Note that the bounding rectangle of the label in the LVS_ICON view depends
5353 * upon whether the window has the focus currently and on whether the item
5354 * is the one with the focus. Ensure that the control's record of which
5355 * item has the focus agrees with the items' records.
5357 static BOOL LISTVIEW_GetItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5359 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5360 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5361 BOOL doLabel = TRUE, oversizedBox = FALSE;
5362 POINT Position, Origin;
5366 TRACE("(hwnd=%p, nItem=%d, lprc=%p)\n", infoPtr->hwndSelf, nItem, lprc);
5368 if (!lprc || nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
5370 LISTVIEW_GetOrigin(infoPtr, &Origin);
5371 LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
5373 /* Be smart and try to figure out the minimum we have to do */
5374 if (lprc->left == LVIR_ICON) doLabel = FALSE;
5375 if (uView == LVS_REPORT && lprc->left == LVIR_BOUNDS) doLabel = FALSE;
5376 if (uView == LVS_ICON && lprc->left != LVIR_ICON &&
5377 infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
5378 oversizedBox = TRUE;
5380 /* get what we need from the item before hand, so we make
5381 * only one request. This can speed up things, if data
5382 * is stored on the app side */
5384 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5385 if (doLabel) lvItem.mask |= LVIF_TEXT;
5386 lvItem.iItem = nItem;
5387 lvItem.iSubItem = 0;
5388 lvItem.pszText = szDispText;
5389 lvItem.cchTextMax = DISP_TEXT_SIZE;
5390 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5391 /* we got the state already up, simulate it here, to avoid a reget */
5392 if (uView == LVS_ICON && (lprc->left != LVIR_ICON))
5394 lvItem.mask |= LVIF_STATE;
5395 lvItem.stateMask = LVIS_FOCUSED;
5396 lvItem.state = (oversizedBox ? LVIS_FOCUSED : 0);
5399 if (uView == LVS_REPORT && (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) && lprc->left == LVIR_SELECTBOUNDS)
5400 lprc->left = LVIR_BOUNDS;
5404 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5408 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, NULL, lprc);
5412 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5415 case LVIR_SELECTBOUNDS:
5416 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, &label_rect);
5417 UnionRect(lprc, lprc, &label_rect);
5421 WARN("Unknown value: %ld\n", lprc->left);
5425 OffsetRect(lprc, Position.x + Origin.x, Position.y + Origin.y);
5427 TRACE(" rect=%s\n", debugrect(lprc));
5434 * Retrieves the spacing between listview control items.
5437 * [I] infoPtr : valid pointer to the listview structure
5438 * [IO] lprc : rectangle to receive the output
5439 * on input, lprc->top = nSubItem
5440 * lprc->left = LVIR_ICON | LVIR_BOUNDS | LVIR_LABEL
5442 * NOTE: for subItem = 0, we should return the bounds of the _entire_ item,
5443 * not only those of the first column.
5444 * Fortunately, LISTVIEW_GetItemMetrics does the right thing.
5450 static BOOL LISTVIEW_GetSubItemRect(LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprc)
5455 if (!lprc) return FALSE;
5457 TRACE("(nItem=%d, nSubItem=%ld)\n", nItem, lprc->top);
5458 /* On WinNT, a subitem of '0' calls LISTVIEW_GetItemRect */
5460 return LISTVIEW_GetItemRect(infoPtr, nItem, lprc);
5462 if ((infoPtr->dwStyle & LVS_TYPEMASK) != LVS_REPORT) return FALSE;
5464 if (!LISTVIEW_GetItemPosition(infoPtr, nItem, &Position)) return FALSE;
5467 lvItem.iItem = nItem;
5468 lvItem.iSubItem = lprc->top;
5470 if (lvItem.mask && !LISTVIEW_GetItemW(infoPtr, &lvItem)) return FALSE;
5474 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, NULL, NULL, lprc, NULL);
5479 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprc, NULL, NULL, NULL);
5483 ERR("Unknown bounds=%ld\n", lprc->left);
5487 OffsetRect(lprc, Position.x, Position.y);
5494 * Retrieves the width of a label.
5497 * [I] infoPtr : valid pointer to the listview structure
5500 * SUCCESS : string width (in pixels)
5503 static INT LISTVIEW_GetLabelWidth(LISTVIEW_INFO *infoPtr, INT nItem)
5505 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5508 TRACE("(nItem=%d)\n", nItem);
5510 lvItem.mask = LVIF_TEXT;
5511 lvItem.iItem = nItem;
5512 lvItem.iSubItem = 0;
5513 lvItem.pszText = szDispText;
5514 lvItem.cchTextMax = DISP_TEXT_SIZE;
5515 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5517 return LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
5522 * Retrieves the spacing between listview control items.
5525 * [I] infoPtr : valid pointer to the listview structure
5526 * [I] bSmall : flag for small or large icon
5529 * Horizontal + vertical spacing
5531 static LONG LISTVIEW_GetItemSpacing(LISTVIEW_INFO *infoPtr, BOOL bSmall)
5537 lResult = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
5541 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_ICON)
5542 lResult = MAKELONG(DEFAULT_COLUMN_WIDTH, GetSystemMetrics(SM_CXSMICON)+HEIGHT_PADDING);
5544 lResult = MAKELONG(infoPtr->nItemWidth, infoPtr->nItemHeight);
5551 * Retrieves the state of a listview control item.
5554 * [I] infoPtr : valid pointer to the listview structure
5555 * [I] nItem : item index
5556 * [I] uMask : state mask
5559 * State specified by the mask.
5561 static UINT LISTVIEW_GetItemState(LISTVIEW_INFO *infoPtr, INT nItem, UINT uMask)
5565 if (nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5567 lvItem.iItem = nItem;
5568 lvItem.iSubItem = 0;
5569 lvItem.mask = LVIF_STATE;
5570 lvItem.stateMask = uMask;
5571 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return 0;
5573 return lvItem.state & uMask;
5578 * Retrieves the text of a listview control item or subitem.
5581 * [I] hwnd : window handle
5582 * [I] nItem : item index
5583 * [IO] lpLVItem : item information
5584 * [I] isW : TRUE if lpLVItem is Unicode
5587 * SUCCESS : string length
5590 static INT LISTVIEW_GetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, LPLVITEMW lpLVItem, BOOL isW)
5592 if (!lpLVItem || nItem < 0 || nItem >= infoPtr->nItemCount) return 0;
5594 lpLVItem->mask = LVIF_TEXT;
5595 lpLVItem->iItem = nItem;
5596 if (!LISTVIEW_GetItemExtT(infoPtr, lpLVItem, isW)) return 0;
5598 return textlenT(lpLVItem->pszText, isW);
5603 * Searches for an item based on properties + relationships.
5606 * [I] infoPtr : valid pointer to the listview structure
5607 * [I] nItem : item index
5608 * [I] uFlags : relationship flag
5611 * SUCCESS : item index
5614 static INT LISTVIEW_GetNextItem(LISTVIEW_INFO *infoPtr, INT nItem, UINT uFlags)
5616 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5618 LVFINDINFOW lvFindInfo;
5619 INT nCountPerColumn;
5623 TRACE("nItem=%d, uFlags=%x, nItemCount=%d\n", nItem, uFlags, infoPtr->nItemCount);
5624 if (nItem < -1 || nItem >= infoPtr->nItemCount) return -1;
5626 ZeroMemory(&lvFindInfo, sizeof(lvFindInfo));
5628 if (uFlags & LVNI_CUT)
5631 if (uFlags & LVNI_DROPHILITED)
5632 uMask |= LVIS_DROPHILITED;
5634 if (uFlags & LVNI_FOCUSED)
5635 uMask |= LVIS_FOCUSED;
5637 if (uFlags & LVNI_SELECTED)
5638 uMask |= LVIS_SELECTED;
5640 /* if we're asked for the focused item, that's only one,
5641 * so it's worth optimizing */
5642 if (uFlags & LVNI_FOCUSED)
5644 if (!(LISTVIEW_GetItemState(infoPtr, infoPtr->nFocusedItem, uMask) & uMask) == uMask) return -1;
5645 return (infoPtr->nFocusedItem == nItem) ? -1 : infoPtr->nFocusedItem;
5648 if (uFlags & LVNI_ABOVE)
5650 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5655 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5661 /* Special case for autoarrange - move 'til the top of a list */
5662 if (is_autoarrange(infoPtr))
5664 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5665 while (nItem - nCountPerRow >= 0)
5667 nItem -= nCountPerRow;
5668 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5673 lvFindInfo.flags = LVFI_NEARESTXY;
5674 lvFindInfo.vkDirection = VK_UP;
5675 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5676 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5678 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5683 else if (uFlags & LVNI_BELOW)
5685 if ((uView == LVS_LIST) || (uView == LVS_REPORT))
5687 while (nItem < infoPtr->nItemCount)
5690 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5696 /* Special case for autoarrange - move 'til the bottom of a list */
5697 if (is_autoarrange(infoPtr))
5699 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5700 while (nItem + nCountPerRow < infoPtr->nItemCount )
5702 nItem += nCountPerRow;
5703 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5708 lvFindInfo.flags = LVFI_NEARESTXY;
5709 lvFindInfo.vkDirection = VK_DOWN;
5710 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5711 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5713 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5718 else if (uFlags & LVNI_TOLEFT)
5720 if (uView == LVS_LIST)
5722 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5723 while (nItem - nCountPerColumn >= 0)
5725 nItem -= nCountPerColumn;
5726 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5730 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5732 /* Special case for autoarrange - move 'ti the beginning of a row */
5733 if (is_autoarrange(infoPtr))
5735 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5736 while (nItem % nCountPerRow > 0)
5739 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5744 lvFindInfo.flags = LVFI_NEARESTXY;
5745 lvFindInfo.vkDirection = VK_LEFT;
5746 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5747 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5749 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5754 else if (uFlags & LVNI_TORIGHT)
5756 if (uView == LVS_LIST)
5758 nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
5759 while (nItem + nCountPerColumn < infoPtr->nItemCount)
5761 nItem += nCountPerColumn;
5762 if ((ListView_GetItemState(infoPtr->hwndSelf, nItem, uMask) & uMask) == uMask)
5766 else if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
5768 /* Special case for autoarrange - move 'til the end of a row */
5769 if (is_autoarrange(infoPtr))
5771 nCountPerRow = LISTVIEW_GetCountPerRow(infoPtr);
5772 while (nItem % nCountPerRow < nCountPerRow - 1 )
5775 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5780 lvFindInfo.flags = LVFI_NEARESTXY;
5781 lvFindInfo.vkDirection = VK_RIGHT;
5782 ListView_GetItemPosition(infoPtr->hwndSelf, nItem, &lvFindInfo.pt);
5783 while ((nItem = ListView_FindItemW(infoPtr->hwndSelf, nItem, &lvFindInfo)) != -1)
5785 if ((LISTVIEW_GetItemState(infoPtr, nItem, uMask) & uMask) == uMask)
5794 /* search by index */
5795 for (i = nItem; i < infoPtr->nItemCount; i++)
5797 if ((LISTVIEW_GetItemState(infoPtr, i, uMask) & uMask) == uMask)
5805 /* LISTVIEW_GetNumberOfWorkAreas */
5809 * Retrieves the origin coordinates when in icon or small icon display mode.
5812 * [I] infoPtr : valid pointer to the listview structure
5813 * [O] lpptOrigin : coordinate information
5818 static void LISTVIEW_GetOrigin(LISTVIEW_INFO *infoPtr, LPPOINT lpptOrigin)
5820 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5821 INT nHorzPos = 0, nVertPos = 0;
5822 SCROLLINFO scrollInfo;
5824 scrollInfo.cbSize = sizeof(SCROLLINFO);
5825 scrollInfo.fMask = SIF_POS;
5827 if ((infoPtr->dwStyle & WS_HSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
5828 nHorzPos = scrollInfo.nPos;
5829 if ((infoPtr->dwStyle & WS_VSCROLL) && GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
5830 nVertPos = scrollInfo.nPos;
5832 TRACE("nHorzPos=%d, nVertPos=%d\n", nHorzPos, nVertPos);
5834 lpptOrigin->x = infoPtr->rcList.left;
5835 lpptOrigin->y = infoPtr->rcList.top;
5836 if (uView == LVS_LIST)
5837 nHorzPos *= infoPtr->nItemWidth;
5838 else if (uView == LVS_REPORT)
5839 nVertPos *= infoPtr->nItemHeight;
5841 lpptOrigin->x -= nHorzPos;
5842 lpptOrigin->y -= nVertPos;
5844 TRACE(" origin=%s\n", debugpoint(lpptOrigin));
5849 * Retrieves the width of a string.
5852 * [I] hwnd : window handle
5853 * [I] lpszText : text string to process
5854 * [I] isW : TRUE if lpszText is Unicode, FALSE otherwise
5857 * SUCCESS : string width (in pixels)
5860 static INT LISTVIEW_GetStringWidthT(LISTVIEW_INFO *infoPtr, LPCWSTR lpszText, BOOL isW)
5865 if (is_textT(lpszText, isW))
5867 HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
5868 HDC hdc = GetDC(infoPtr->hwndSelf);
5869 HFONT hOldFont = SelectObject(hdc, hFont);
5872 GetTextExtentPointW(hdc, lpszText, lstrlenW(lpszText), &stringSize);
5874 GetTextExtentPointA(hdc, (LPCSTR)lpszText, lstrlenA((LPCSTR)lpszText), &stringSize);
5875 SelectObject(hdc, hOldFont);
5876 ReleaseDC(infoPtr->hwndSelf, hdc);
5878 return stringSize.cx;
5883 * Determines which listview item is located at the specified position.
5886 * [I] infoPtr : valid pointer to the listview structure
5887 * [IO] lpht : hit test information
5888 * [I] subitem : fill out iSubItem.
5889 * [I] select : return the index only if the hit selects the item
5892 * (mm 20001022): We must not allow iSubItem to be touched, for
5893 * an app might pass only a structure with space up to iItem!
5894 * (MS Office 97 does that for instance in the file open dialog)
5897 * SUCCESS : item index
5900 static INT LISTVIEW_HitTest(LISTVIEW_INFO *infoPtr, LPLVHITTESTINFO lpht, BOOL subitem, BOOL select)
5902 WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
5903 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
5904 RECT rcBox, rcBounds, rcState, rcIcon, rcLabel, rcSearch;
5905 POINT Origin, Position, opt;
5910 TRACE("(pt=%s, subitem=%d, select=%d)\n", debugpoint(&lpht->pt), subitem, select);
5914 if (subitem) lpht->iSubItem = 0;
5916 if (infoPtr->rcList.left > lpht->pt.x)
5917 lpht->flags |= LVHT_TOLEFT;
5918 else if (infoPtr->rcList.right < lpht->pt.x)
5919 lpht->flags |= LVHT_TORIGHT;
5921 if (infoPtr->rcList.top > lpht->pt.y)
5922 lpht->flags |= LVHT_ABOVE;
5923 else if (infoPtr->rcList.bottom < lpht->pt.y)
5924 lpht->flags |= LVHT_BELOW;
5926 TRACE("lpht->flags=0x%x\n", lpht->flags);
5927 if (lpht->flags) return -1;
5929 lpht->flags |= LVHT_NOWHERE;
5931 LISTVIEW_GetOrigin(infoPtr, &Origin);
5933 /* first deal with the large items */
5934 rcSearch.left = lpht->pt.x;
5935 rcSearch.top = lpht->pt.y;
5936 rcSearch.right = rcSearch.left + 1;
5937 rcSearch.bottom = rcSearch.top + 1;
5939 iterator_frameditems(&i, infoPtr, &rcSearch);
5940 iterator_next(&i); /* go to first item in the sequence */
5942 iterator_destroy(&i);
5944 TRACE("lpht->iItem=%d\n", iItem);
5945 if (iItem == -1) return -1;
5947 lvItem.mask = LVIF_STATE | LVIF_TEXT;
5948 if (uView == LVS_REPORT) lvItem.mask |= LVIF_INDENT;
5949 lvItem.stateMask = LVIS_STATEIMAGEMASK;
5950 if (uView == LVS_ICON) lvItem.stateMask |= LVIS_FOCUSED;
5951 lvItem.iItem = iItem;
5952 lvItem.iSubItem = 0;
5953 lvItem.pszText = szDispText;
5954 lvItem.cchTextMax = DISP_TEXT_SIZE;
5955 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) return -1;
5956 if (!infoPtr->bFocus) lvItem.state &= ~LVIS_FOCUSED;
5958 LISTVIEW_GetItemMetrics(infoPtr, &lvItem, &rcBox, &rcState, &rcIcon, &rcLabel);
5959 LISTVIEW_GetItemOrigin(infoPtr, iItem, &Position);
5960 opt.x = lpht->pt.x - Position.x - Origin.x;
5961 opt.y = lpht->pt.y - Position.y - Origin.y;
5963 if (uView == LVS_REPORT)
5966 UnionRect(&rcBounds, &rcIcon, &rcLabel);
5967 TRACE("rcBounds=%s\n", debugrect(&rcBounds));
5968 if (!PtInRect(&rcBounds, opt)) return -1;
5970 if (PtInRect(&rcIcon, opt))
5971 lpht->flags |= LVHT_ONITEMICON;
5972 else if (PtInRect(&rcLabel, opt))
5973 lpht->flags |= LVHT_ONITEMLABEL;
5974 else if (infoPtr->himlState && ((lvItem.state & LVIS_STATEIMAGEMASK) >> 12) && PtInRect(&rcState, opt))
5975 lpht->flags |= LVHT_ONITEMSTATEICON;
5976 if (lpht->flags & LVHT_ONITEM)
5977 lpht->flags &= ~LVHT_NOWHERE;
5979 TRACE("lpht->flags=0x%x\n", lpht->flags);
5980 if (uView == LVS_REPORT && subitem)
5984 rcBounds.right = rcBounds.left;
5985 for (j = 0; j < DPA_GetPtrCount(infoPtr->hdpaColumns); j++)
5987 rcBounds.left = rcBounds.right;
5988 rcBounds.right += LISTVIEW_GetColumnWidth(infoPtr, j);
5989 if (PtInRect(&rcBounds, opt))
5997 if (select && !(uView == LVS_REPORT &&
5998 ((infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) ||
5999 (infoPtr->dwStyle & LVS_OWNERDRAWFIXED))))
6001 if (uView == LVS_REPORT)
6003 UnionRect(&rcBounds, &rcIcon, &rcLabel);
6004 UnionRect(&rcBounds, &rcBounds, &rcState);
6006 if (!PtInRect(&rcBounds, opt)) iItem = -1;
6008 return lpht->iItem = iItem;
6012 /* LISTVIEW_InsertCompare: callback routine for comparing pszText members of the LV_ITEMS
6013 in a LISTVIEW on insert. Passed to DPA_Sort in LISTVIEW_InsertItem.
6014 This function should only be used for inserting items into a sorted list (LVM_INSERTITEM)
6015 and not during the processing of a LVM_SORTITEMS message. Applications should provide
6016 their own sort proc. when sending LVM_SORTITEMS.
6019 (remarks on LVITEM: LVM_INSERTITEM will insert the new item in the proper sort postion...
6021 LVS_SORTXXX must be specified,
6022 LVS_OWNERDRAW is not set,
6023 <item>.pszText is not LPSTR_TEXTCALLBACK.
6025 (LVS_SORT* flags): "For the LVS_SORTASCENDING... styles, item indices
6026 are sorted based on item text..."
6028 static INT WINAPI LISTVIEW_InsertCompare( LPVOID first, LPVOID second, LPARAM lParam)
6030 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
6031 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
6032 INT cmpv = textcmpWT(lv_first->hdr.pszText, lv_second->hdr.pszText, TRUE);
6034 /* if we're sorting descending, negate the return value */
6035 return (((LISTVIEW_INFO *)lParam)->dwStyle & LVS_SORTDESCENDING) ? -cmpv : cmpv;
6040 * Inserts a new item in the listview control.
6043 * [I] infoPtr : valid pointer to the listview structure
6044 * [I] lpLVItem : item information
6045 * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
6048 * SUCCESS : new item index
6051 static INT LISTVIEW_InsertItemT(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW)
6053 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6058 BOOL is_sorted, has_changed;
6061 TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
6063 if (infoPtr->dwStyle & LVS_OWNERDATA) return infoPtr->nItemCount++;
6065 /* make sure it's an item, and not a subitem; cannot insert a subitem */
6066 if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iSubItem) return -1;
6068 if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return -1;
6070 if ( !(lpItem = (ITEM_INFO *)Alloc(sizeof(ITEM_INFO))) )
6073 /* insert item in listview control data structure */
6074 if ( !(hdpaSubItems = DPA_Create(8)) ) goto fail;
6075 if ( !DPA_SetPtr(hdpaSubItems, 0, lpItem) ) assert (FALSE);
6077 is_sorted = (infoPtr->dwStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) &&
6078 !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (LPSTR_TEXTCALLBACKW != lpLVItem->pszText);
6080 nItem = is_sorted ? infoPtr->nItemCount : min(lpLVItem->iItem, infoPtr->nItemCount);
6081 TRACE(" inserting at %d, sorted=%d, count=%d, iItem=%d\n", nItem, is_sorted, infoPtr->nItemCount, lpLVItem->iItem);
6082 nItem = DPA_InsertPtr( infoPtr->hdpaItems, nItem, hdpaSubItems );
6083 if (nItem == -1) goto fail;
6084 infoPtr->nItemCount++;
6086 /* shift indices first so they don't get tangled */
6087 LISTVIEW_ShiftIndices(infoPtr, nItem, 1);
6089 /* set the item attributes */
6090 if (lpLVItem->mask & (LVIF_GROUPID|LVIF_COLUMNS))
6092 /* full size structure expected - _WIN32IE >= 0x560 */
6095 else if (lpLVItem->mask & LVIF_INDENT)
6097 /* indent member expected - _WIN32IE >= 0x300 */
6098 memcpy(&item, lpLVItem, offsetof( LVITEMW, iGroupId ));
6102 /* minimal structure expected */
6103 memcpy(&item, lpLVItem, offsetof( LVITEMW, iIndent ));
6106 item.state &= ~LVIS_STATEIMAGEMASK;
6107 if (!set_main_item(infoPtr, &item, TRUE, isW, &has_changed)) goto undo;
6109 /* if we're sorted, sort the list, and update the index */
6112 DPA_Sort( infoPtr->hdpaItems, LISTVIEW_InsertCompare, (LPARAM)infoPtr );
6113 nItem = DPA_GetPtrIndex( infoPtr->hdpaItems, hdpaSubItems );
6114 assert(nItem != -1);
6117 /* make room for the position, if we are in the right mode */
6118 if ((uView == LVS_SMALLICON) || (uView == LVS_ICON))
6120 if (DPA_InsertPtr(infoPtr->hdpaPosX, nItem, 0) == -1)
6122 if (DPA_InsertPtr(infoPtr->hdpaPosY, nItem, 0) == -1)
6124 DPA_DeletePtr(infoPtr->hdpaPosX, nItem);
6129 /* send LVN_INSERTITEM notification */
6130 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
6132 nmlv.lParam = lpItem->lParam;
6133 notify_listview(infoPtr, LVN_INSERTITEM, &nmlv);
6135 /* align items (set position of each item) */
6136 if ((uView == LVS_SMALLICON || uView == LVS_ICON))
6140 if (infoPtr->dwStyle & LVS_ALIGNLEFT)
6141 LISTVIEW_NextIconPosLeft(infoPtr, &pt);
6143 LISTVIEW_NextIconPosTop(infoPtr, &pt);
6145 LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, TRUE);
6148 /* now is the invalidation fun */
6149 LISTVIEW_ScrollOnInsert(infoPtr, nItem, 1);
6153 LISTVIEW_ShiftIndices(infoPtr, nItem, -1);
6154 DPA_DeletePtr(infoPtr->hdpaItems, nItem);
6155 infoPtr->nItemCount--;
6157 DPA_DeletePtr(hdpaSubItems, 0);
6158 DPA_Destroy (hdpaSubItems);
6165 * Redraws a range of items.
6168 * [I] infoPtr : valid pointer to the listview structure
6169 * [I] nFirst : first item
6170 * [I] nLast : last item
6176 static BOOL LISTVIEW_RedrawItems(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast)
6180 if (nLast < nFirst || min(nFirst, nLast) < 0 ||
6181 max(nFirst, nLast) >= infoPtr->nItemCount)
6184 for (i = nFirst; i <= nLast; i++)
6185 LISTVIEW_InvalidateItem(infoPtr, i);
6192 * Scroll the content of a listview.
6195 * [I] infoPtr : valid pointer to the listview structure
6196 * [I] dx : horizontal scroll amount in pixels
6197 * [I] dy : vertical scroll amount in pixels
6204 * If the control is in report mode (LVS_REPORT) the control can
6205 * be scrolled only in line increments. "dy" will be rounded to the
6206 * nearest number of pixels that are a whole line. Ex: if line height
6207 * is 16 and an 8 is passed, the list will be scrolled by 16. If a 7
6208 * is passed the the scroll will be 0. (per MSDN 7/2002)
6210 * For: (per experimentaion with native control and CSpy ListView)
6211 * LVS_ICON dy=1 = 1 pixel (vertical only)
6213 * LVS_SMALLICON dy=1 = 1 pixel (vertical only)
6215 * LVS_LIST dx=1 = 1 column (horizontal only)
6216 * but will only scroll 1 column per message
6217 * no matter what the value.
6218 * dy must be 0 or FALSE returned.
6219 * LVS_REPORT dx=1 = 1 pixel
6223 static BOOL LISTVIEW_Scroll(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
6225 switch(infoPtr->dwStyle & LVS_TYPEMASK) {
6227 dy += (dy < 0 ? -1 : 1) * infoPtr->nItemHeight/2;
6228 dy /= infoPtr->nItemHeight;
6231 if (dy != 0) return FALSE;
6238 if (dx != 0) LISTVIEW_HScroll(infoPtr, SB_INTERNAL, dx, 0);
6239 if (dy != 0) LISTVIEW_VScroll(infoPtr, SB_INTERNAL, dy, 0);
6246 * Sets the background color.
6249 * [I] infoPtr : valid pointer to the listview structure
6250 * [I] clrBk : background color
6256 static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrBk)
6258 TRACE("(clrBk=%lx)\n", clrBk);
6260 if(infoPtr->clrBk != clrBk) {
6261 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
6262 infoPtr->clrBk = clrBk;
6263 if (clrBk == CLR_NONE)
6264 infoPtr->hBkBrush = (HBRUSH)GetClassLongW(infoPtr->hwndSelf, GCL_HBRBACKGROUND);
6266 infoPtr->hBkBrush = CreateSolidBrush(clrBk);
6267 LISTVIEW_InvalidateList(infoPtr);
6273 /* LISTVIEW_SetBkImage */
6275 /*** Helper for {Insert,Set}ColumnT *only* */
6276 static void column_fill_hditem(LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn, const LVCOLUMNW *lpColumn, BOOL isW)
6278 if (lpColumn->mask & LVCF_FMT)
6280 /* format member is valid */
6281 lphdi->mask |= HDI_FORMAT;
6283 /* set text alignment (leftmost column must be left-aligned) */
6284 if (nColumn == 0 || lpColumn->fmt & LVCFMT_LEFT)
6285 lphdi->fmt |= HDF_LEFT;
6286 else if (lpColumn->fmt & LVCFMT_RIGHT)
6287 lphdi->fmt |= HDF_RIGHT;
6288 else if (lpColumn->fmt & LVCFMT_CENTER)
6289 lphdi->fmt |= HDF_CENTER;
6291 if (lpColumn->fmt & LVCFMT_BITMAP_ON_RIGHT)
6292 lphdi->fmt |= HDF_BITMAP_ON_RIGHT;
6294 if (lpColumn->fmt & LVCFMT_COL_HAS_IMAGES)
6296 lphdi->fmt |= HDF_IMAGE;
6297 lphdi->iImage = I_IMAGECALLBACK;
6301 if (lpColumn->mask & LVCF_WIDTH)
6303 lphdi->mask |= HDI_WIDTH;
6304 if(lpColumn->cx == LVSCW_AUTOSIZE_USEHEADER)
6306 /* make it fill the remainder of the controls width */
6310 for(item_index = 0; item_index < (nColumn - 1); item_index++)
6312 LISTVIEW_GetHeaderRect(infoPtr, item_index, &rcHeader);
6313 lphdi->cxy += rcHeader.right - rcHeader.left;
6316 /* retrieve the layout of the header */
6317 GetClientRect(infoPtr->hwndSelf, &rcHeader);
6318 TRACE("start cxy=%d rcHeader=%s\n", lphdi->cxy, debugrect(&rcHeader));
6320 lphdi->cxy = (rcHeader.right - rcHeader.left) - lphdi->cxy;
6323 lphdi->cxy = lpColumn->cx;
6326 if (lpColumn->mask & LVCF_TEXT)
6328 lphdi->mask |= HDI_TEXT | HDI_FORMAT;
6329 lphdi->fmt |= HDF_STRING;
6330 lphdi->pszText = lpColumn->pszText;
6331 lphdi->cchTextMax = textlenT(lpColumn->pszText, isW);
6334 if (lpColumn->mask & LVCF_IMAGE)
6336 lphdi->mask |= HDI_IMAGE;
6337 lphdi->iImage = lpColumn->iImage;
6340 if (lpColumn->mask & LVCF_ORDER)
6342 lphdi->mask |= HDI_ORDER;
6343 lphdi->iOrder = lpColumn->iOrder;
6350 * Inserts a new column.
6353 * [I] infoPtr : valid pointer to the listview structure
6354 * [I] nColumn : column index
6355 * [I] lpColumn : column information
6356 * [I] isW : TRUE if lpColumn is Unicode, FALSE otherwise
6359 * SUCCESS : new column index
6362 static INT LISTVIEW_InsertColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6363 const LVCOLUMNW *lpColumn, BOOL isW)
6365 COLUMN_INFO *lpColumnInfo;
6369 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6371 if (!lpColumn || nColumn < 0) return -1;
6372 nColumn = min(nColumn, DPA_GetPtrCount(infoPtr->hdpaColumns));
6374 ZeroMemory(&hdi, sizeof(HDITEMW));
6375 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6377 /* insert item in header control */
6378 nNewColumn = SendMessageW(infoPtr->hwndHeader,
6379 isW ? HDM_INSERTITEMW : HDM_INSERTITEMA,
6380 (WPARAM)nColumn, (LPARAM)&hdi);
6381 if (nNewColumn == -1) return -1;
6382 if (nNewColumn != nColumn) ERR("nColumn=%d, nNewColumn=%d\n", nColumn, nNewColumn);
6384 /* create our own column info */
6385 if (!(lpColumnInfo = Alloc(sizeof(COLUMN_INFO)))) goto fail;
6386 if (DPA_InsertPtr(infoPtr->hdpaColumns, nNewColumn, lpColumnInfo) == -1) goto fail;
6388 if (lpColumn->mask & LVCF_FMT) lpColumnInfo->fmt = lpColumn->fmt;
6389 if (!Header_GetItemRect(infoPtr->hwndHeader, nNewColumn, &lpColumnInfo->rcHeader)) goto fail;
6391 /* now we have to actually adjust the data */
6392 if (!(infoPtr->dwStyle & LVS_OWNERDATA) && infoPtr->nItemCount > 0)
6394 SUBITEM_INFO *lpSubItem;
6398 for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
6400 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, nItem);
6401 for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
6403 lpSubItem = (SUBITEM_INFO *)DPA_GetPtr(hdpaSubItems, i);
6404 if (lpSubItem->iSubItem >= nNewColumn)
6405 lpSubItem->iSubItem++;
6410 /* make space for the new column */
6411 LISTVIEW_ScrollColumns(infoPtr, nNewColumn + 1, lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
6416 if (nNewColumn != -1) SendMessageW(infoPtr->hwndHeader, HDM_DELETEITEM, nNewColumn, 0);
6419 DPA_DeletePtr(infoPtr->hdpaColumns, nNewColumn);
6427 * Sets the attributes of a header item.
6430 * [I] infoPtr : valid pointer to the listview structure
6431 * [I] nColumn : column index
6432 * [I] lpColumn : column attributes
6433 * [I] isW: if TRUE, the lpColumn is a LPLVCOLUMNW, else it is a LPLVCOLUMNA
6439 static BOOL LISTVIEW_SetColumnT(LISTVIEW_INFO *infoPtr, INT nColumn,
6440 const LVCOLUMNW *lpColumn, BOOL isW)
6442 HDITEMW hdi, hdiget;
6445 TRACE("(nColumn=%d, lpColumn=%s, isW=%d)\n", nColumn, debuglvcolumn_t(lpColumn, isW), isW);
6447 if (!lpColumn || nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6449 ZeroMemory(&hdi, sizeof(HDITEMW));
6450 if (lpColumn->mask & LVCF_FMT)
6452 hdi.mask |= HDI_FORMAT;
6453 hdiget.mask = HDI_FORMAT;
6454 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, &hdiget))
6455 hdi.fmt = hdiget.fmt & HDF_STRING;
6457 column_fill_hditem(infoPtr, &hdi, nColumn, lpColumn, isW);
6459 /* set header item attributes */
6460 bResult = SendMessageW(infoPtr->hwndHeader, isW ? HDM_SETITEMW : HDM_SETITEMA, (WPARAM)nColumn, (LPARAM)&hdi);
6461 if (!bResult) return FALSE;
6463 if (lpColumn->mask & LVCF_FMT)
6465 COLUMN_INFO *lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, nColumn);
6466 int oldFmt = lpColumnInfo->fmt;
6468 lpColumnInfo->fmt = lpColumn->fmt;
6469 if ((oldFmt ^ lpColumn->fmt) & (LVCFMT_JUSTIFYMASK | LVCFMT_IMAGE))
6471 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6472 if (uView == LVS_REPORT) LISTVIEW_InvalidateColumn(infoPtr, nColumn);
6481 * Sets the column order array
6484 * [I] infoPtr : valid pointer to the listview structure
6485 * [I] iCount : number of elements in column order array
6486 * [I] lpiArray : pointer to column order array
6492 static BOOL LISTVIEW_SetColumnOrderArray(LISTVIEW_INFO *infoPtr, INT iCount, const INT *lpiArray)
6494 FIXME("iCount %d lpiArray %p\n", iCount, lpiArray);
6505 * Sets the width of a column
6508 * [I] infoPtr : valid pointer to the listview structure
6509 * [I] nColumn : column index
6510 * [I] cx : column width
6516 static BOOL LISTVIEW_SetColumnWidth(LISTVIEW_INFO *infoPtr, INT nColumn, INT cx)
6518 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6519 WCHAR szDispText[DISP_TEXT_SIZE] = { 0 };
6523 TRACE("(nColumn=%d, cx=%d\n", nColumn, cx);
6525 /* set column width only if in report or list mode */
6526 if (uView != LVS_REPORT && uView != LVS_LIST) return FALSE;
6528 /* take care of invalid cx values */
6529 if(uView == LVS_REPORT && cx < -2) cx = LVSCW_AUTOSIZE;
6530 else if (uView == LVS_LIST && cx < 1) return FALSE;
6532 /* resize all columns if in LVS_LIST mode */
6533 if(uView == LVS_LIST)
6535 infoPtr->nItemWidth = cx;
6536 LISTVIEW_InvalidateList(infoPtr);
6540 if (nColumn < 0 || nColumn >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
6542 if (cx == LVSCW_AUTOSIZE || (cx == LVSCW_AUTOSIZE_USEHEADER && nColumn < DPA_GetPtrCount(infoPtr->hdpaColumns) -1))
6547 lvItem.mask = LVIF_TEXT;
6549 lvItem.iSubItem = nColumn;
6550 lvItem.pszText = szDispText;
6551 lvItem.cchTextMax = DISP_TEXT_SIZE;
6552 for (; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
6554 if (!LISTVIEW_GetItemW(infoPtr, &lvItem)) continue;
6555 nLabelWidth = LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE);
6556 if (max_cx < nLabelWidth) max_cx = nLabelWidth;
6558 if (infoPtr->himlSmall && (nColumn == 0 || (LISTVIEW_GetColumnInfo(infoPtr, nColumn)->fmt & LVCFMT_IMAGE)))
6559 max_cx += infoPtr->iconSize.cx;
6560 max_cx += TRAILING_LABEL_PADDING;
6563 /* autosize based on listview items width */
6564 if(cx == LVSCW_AUTOSIZE)
6566 else if(cx == LVSCW_AUTOSIZE_USEHEADER)
6568 /* if iCol is the last column make it fill the remainder of the controls width */
6569 if(nColumn == DPA_GetPtrCount(infoPtr->hdpaColumns) - 1)
6574 LISTVIEW_GetOrigin(infoPtr, &Origin);
6575 LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcHeader);
6577 cx = infoPtr->rcList.right - Origin.x - rcHeader.left;
6581 /* Despite what the MS docs say, if this is not the last
6582 column, then MS resizes the column to the width of the
6583 largest text string in the column, including headers
6584 and items. This is different from LVSCW_AUTOSIZE in that
6585 LVSCW_AUTOSIZE ignores the header string length. */
6588 /* retrieve header text */
6589 hdi.mask = HDI_TEXT;
6590 hdi.cchTextMax = DISP_TEXT_SIZE;
6591 hdi.pszText = szDispText;
6592 if (Header_GetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi))
6594 HDC hdc = GetDC(infoPtr->hwndSelf);
6595 HFONT old_font = SelectObject(hdc, (HFONT)SendMessageW(infoPtr->hwndHeader, WM_GETFONT, 0, 0));
6598 if (GetTextExtentPoint32W(hdc, hdi.pszText, lstrlenW(hdi.pszText), &size))
6599 cx = size.cx + TRAILING_HEADER_PADDING;
6600 /* FIXME: Take into account the header image, if one is present */
6601 SelectObject(hdc, old_font);
6602 ReleaseDC(infoPtr->hwndSelf, hdc);
6604 cx = max (cx, max_cx);
6608 if (cx < 0) return FALSE;
6610 /* call header to update the column change */
6611 hdi.mask = HDI_WIDTH;
6613 TRACE("hdi.cxy=%d\n", hdi.cxy);
6614 return Header_SetItemW(infoPtr->hwndHeader, nColumn, (LPARAM)&hdi);
6618 * Creates the checkbox imagelist. Helper for LISTVIEW_SetExtendedListViewStyle
6621 static HIMAGELIST LISTVIEW_CreateCheckBoxIL(LISTVIEW_INFO *infoPtr)
6624 HBITMAP hbm_im, hbm_mask, hbm_orig;
6626 HBRUSH hbr_white = GetStockObject(WHITE_BRUSH);
6627 HBRUSH hbr_black = GetStockObject(BLACK_BRUSH);
6630 himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
6631 ILC_COLOR | ILC_MASK, 2, 2);
6632 hdc_wnd = GetDC(infoPtr->hwndSelf);
6633 hdc = CreateCompatibleDC(hdc_wnd);
6634 hbm_im = CreateCompatibleBitmap(hdc_wnd, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
6635 hbm_mask = CreateBitmap(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 1, 1, NULL);
6636 ReleaseDC(infoPtr->hwndSelf, hdc_wnd);
6638 rc.left = rc.top = 0;
6639 rc.right = GetSystemMetrics(SM_CXSMICON);
6640 rc.bottom = GetSystemMetrics(SM_CYSMICON);
6642 hbm_orig = SelectObject(hdc, hbm_mask);
6643 FillRect(hdc, &rc, hbr_white);
6644 InflateRect(&rc, -3, -3);
6645 FillRect(hdc, &rc, hbr_black);
6647 SelectObject(hdc, hbm_im);
6648 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO);
6649 SelectObject(hdc, hbm_orig);
6650 ImageList_Add(himl, hbm_im, hbm_mask);
6652 SelectObject(hdc, hbm_im);
6653 DrawFrameControl(hdc, &rc, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_MONO | DFCS_CHECKED);
6654 SelectObject(hdc, hbm_orig);
6655 ImageList_Add(himl, hbm_im, hbm_mask);
6657 DeleteObject(hbm_mask);
6658 DeleteObject(hbm_im);
6666 * Sets the extended listview style.
6669 * [I] infoPtr : valid pointer to the listview structure
6671 * [I] dwStyle : style
6674 * SUCCESS : previous style
6677 static DWORD LISTVIEW_SetExtendedListViewStyle(LISTVIEW_INFO *infoPtr, DWORD dwMask, DWORD dwStyle)
6679 DWORD dwOldStyle = infoPtr->dwLvExStyle;
6683 infoPtr->dwLvExStyle = (dwOldStyle & ~dwMask) | (dwStyle & dwMask);
6685 infoPtr->dwLvExStyle = dwStyle;
6687 if((infoPtr->dwLvExStyle ^ dwOldStyle) & LVS_EX_CHECKBOXES)
6689 HIMAGELIST himl = 0;
6690 if(infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES)
6691 himl = LISTVIEW_CreateCheckBoxIL(infoPtr);
6692 LISTVIEW_SetImageList(infoPtr, LVSIL_STATE, himl);
6700 * Sets the new hot cursor used during hot tracking and hover selection.
6703 * [I] infoPtr : valid pointer to the listview structure
6704 * [I} hCurosr : the new hot cursor handle
6707 * Returns the previous hot cursor
6709 static HCURSOR LISTVIEW_SetHotCursor(LISTVIEW_INFO *infoPtr, HCURSOR hCursor)
6711 HCURSOR oldCursor = infoPtr->hHotCursor;
6713 infoPtr->hHotCursor = hCursor;
6721 * Sets the hot item index.
6724 * [I] infoPtr : valid pointer to the listview structure
6725 * [I] iIndex : index
6728 * SUCCESS : previous hot item index
6729 * FAILURE : -1 (no hot item)
6731 static INT LISTVIEW_SetHotItem(LISTVIEW_INFO *infoPtr, INT iIndex)
6733 INT iOldIndex = infoPtr->nHotItem;
6735 infoPtr->nHotItem = iIndex;
6743 * Sets the amount of time the cursor must hover over an item before it is selected.
6746 * [I] infoPtr : valid pointer to the listview structure
6747 * [I] dwHoverTime : hover time, if -1 the hover time is set to the default
6750 * Returns the previous hover time
6752 static DWORD LISTVIEW_SetHoverTime(LISTVIEW_INFO *infoPtr, DWORD dwHoverTime)
6754 DWORD oldHoverTime = infoPtr->dwHoverTime;
6756 infoPtr->dwHoverTime = dwHoverTime;
6758 return oldHoverTime;
6763 * Sets spacing for icons of LVS_ICON style.
6766 * [I] infoPtr : valid pointer to the listview structure
6767 * [I] cx : horizontal spacing (-1 = system spacing, 0 = autosize)
6768 * [I] cy : vertical spacing (-1 = system spacing, 0 = autosize)
6771 * MAKELONG(oldcx, oldcy)
6773 static DWORD LISTVIEW_SetIconSpacing(LISTVIEW_INFO *infoPtr, INT cx, INT cy)
6775 DWORD oldspacing = MAKELONG(infoPtr->iconSpacing.cx, infoPtr->iconSpacing.cy);
6776 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6778 TRACE("requested=(%d,%d)\n", cx, cy);
6780 /* this is supported only for LVS_ICON style */
6781 if (uView != LVS_ICON) return oldspacing;
6783 /* set to defaults, if instructed to */
6784 if (cx == -1) cx = GetSystemMetrics(SM_CXICONSPACING);
6785 if (cy == -1) cy = GetSystemMetrics(SM_CYICONSPACING);
6787 /* if 0 then compute width
6788 * FIXME: Should scan each item and determine max width of
6789 * icon or label, then make that the width */
6791 cx = infoPtr->iconSpacing.cx;
6793 /* if 0 then compute height */
6795 cy = infoPtr->iconSize.cy + 2 * infoPtr->ntmHeight +
6796 ICON_BOTTOM_PADDING + ICON_TOP_PADDING + LABEL_VERT_PADDING;
6799 infoPtr->iconSpacing.cx = cx;
6800 infoPtr->iconSpacing.cy = cy;
6802 TRACE("old=(%d,%d), new=(%d,%d), iconSize=(%ld,%ld), ntmH=%d\n",
6803 LOWORD(oldspacing), HIWORD(oldspacing), cx, cy,
6804 infoPtr->iconSize.cx, infoPtr->iconSize.cy,
6805 infoPtr->ntmHeight);
6807 /* these depend on the iconSpacing */
6808 LISTVIEW_UpdateItemSize(infoPtr);
6813 inline void set_icon_size(SIZE *size, HIMAGELIST himl, BOOL small)
6817 if (himl && ImageList_GetIconSize(himl, &cx, &cy))
6824 size->cx = GetSystemMetrics(small ? SM_CXSMICON : SM_CXICON);
6825 size->cy = GetSystemMetrics(small ? SM_CYSMICON : SM_CYICON);
6834 * [I] infoPtr : valid pointer to the listview structure
6835 * [I] nType : image list type
6836 * [I] himl : image list handle
6839 * SUCCESS : old image list
6842 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *infoPtr, INT nType, HIMAGELIST himl)
6844 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6845 INT oldHeight = infoPtr->nItemHeight;
6846 HIMAGELIST himlOld = 0;
6848 TRACE("(nType=%d, himl=%p\n", nType, himl);
6853 himlOld = infoPtr->himlNormal;
6854 infoPtr->himlNormal = himl;
6855 if (uView == LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, FALSE);
6856 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
6860 himlOld = infoPtr->himlSmall;
6861 infoPtr->himlSmall = himl;
6862 if (uView != LVS_ICON) set_icon_size(&infoPtr->iconSize, himl, TRUE);
6866 himlOld = infoPtr->himlState;
6867 infoPtr->himlState = himl;
6868 set_icon_size(&infoPtr->iconStateSize, himl, TRUE);
6869 ImageList_SetBkColor(infoPtr->himlState, CLR_NONE);
6873 ERR("Unknown icon type=%d\n", nType);
6877 infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
6878 if (infoPtr->nItemHeight != oldHeight)
6879 LISTVIEW_UpdateScroll(infoPtr);
6886 * Preallocates memory (does *not* set the actual count of items !)
6889 * [I] infoPtr : valid pointer to the listview structure
6890 * [I] nItems : item count (projected number of items to allocate)
6891 * [I] dwFlags : update flags
6897 static BOOL LISTVIEW_SetItemCount(LISTVIEW_INFO *infoPtr, INT nItems, DWORD dwFlags)
6899 TRACE("(nItems=%d, dwFlags=%lx)\n", nItems, dwFlags);
6901 if (infoPtr->dwStyle & LVS_OWNERDATA)
6903 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
6904 INT nOldCount = infoPtr->nItemCount;
6906 if (nItems < nOldCount)
6908 RANGE range = { nItems, nOldCount };
6909 ranges_del(infoPtr->selectionRanges, range);
6910 if (infoPtr->nFocusedItem >= nItems)
6912 infoPtr->nFocusedItem = -1;
6913 SetRectEmpty(&infoPtr->rcFocus);
6917 infoPtr->nItemCount = nItems;
6918 LISTVIEW_UpdateScroll(infoPtr);
6920 /* the flags are valid only in ownerdata report and list modes */
6921 if (uView == LVS_ICON || uView == LVS_SMALLICON) dwFlags = 0;
6923 if (!(dwFlags & LVSICF_NOSCROLL) && infoPtr->nFocusedItem != -1)
6924 LISTVIEW_EnsureVisible(infoPtr, infoPtr->nFocusedItem, FALSE);
6926 if (!(dwFlags & LVSICF_NOINVALIDATEALL))
6927 LISTVIEW_InvalidateList(infoPtr);
6934 LISTVIEW_GetOrigin(infoPtr, &Origin);
6935 nFrom = min(nOldCount, nItems);
6936 nTo = max(nOldCount, nItems);
6938 if (uView == LVS_REPORT)
6941 rcErase.top = nFrom * infoPtr->nItemHeight;
6942 rcErase.right = infoPtr->nItemWidth;
6943 rcErase.bottom = nTo * infoPtr->nItemHeight;
6944 OffsetRect(&rcErase, Origin.x, Origin.y);
6945 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6946 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6950 INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
6952 rcErase.left = (nFrom / nPerCol) * infoPtr->nItemWidth;
6953 rcErase.top = (nFrom % nPerCol) * infoPtr->nItemHeight;
6954 rcErase.right = rcErase.left + infoPtr->nItemWidth;
6955 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6956 OffsetRect(&rcErase, Origin.x, Origin.y);
6957 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6958 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6960 rcErase.left = (nFrom / nPerCol + 1) * infoPtr->nItemWidth;
6962 rcErase.right = (nTo / nPerCol + 1) * infoPtr->nItemWidth;
6963 rcErase.bottom = nPerCol * infoPtr->nItemHeight;
6964 OffsetRect(&rcErase, Origin.x, Origin.y);
6965 if (IntersectRect(&rcErase, &rcErase, &infoPtr->rcList))
6966 LISTVIEW_InvalidateRect(infoPtr, &rcErase);
6972 /* According to MSDN for non-LVS_OWNERDATA this is just
6973 * a performance issue. The control allocates its internal
6974 * data structures for the number of items specified. It
6975 * cuts down on the number of memory allocations. Therefore
6976 * we will just issue a WARN here
6978 WARN("for non-ownerdata performance option not implemented.\n");
6986 * Sets the position of an item.
6989 * [I] infoPtr : valid pointer to the listview structure
6990 * [I] nItem : item index
6991 * [I] pt : coordinate
6997 static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, POINT pt)
6999 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7002 TRACE("(nItem=%d, &pt=%s\n", nItem, debugpoint(&pt));
7004 if (nItem < 0 || nItem >= infoPtr->nItemCount ||
7005 !(uView == LVS_ICON || uView == LVS_SMALLICON)) return FALSE;
7007 LISTVIEW_GetOrigin(infoPtr, &Origin);
7009 /* This point value seems to be an undocumented feature.
7010 * The best guess is that it means either at the origin,
7011 * or at true beginning of the list. I will assume the origin. */
7012 if ((pt.x == -1) && (pt.y == -1))
7015 if (uView == LVS_ICON)
7017 pt.x -= (infoPtr->nItemWidth - infoPtr->iconSize.cx) / 2;
7018 pt.y -= ICON_TOP_PADDING;
7023 infoPtr->bAutoarrange = FALSE;
7025 return LISTVIEW_MoveIconTo(infoPtr, nItem, &pt, FALSE);
7030 * Sets the state of one or many items.
7033 * [I] infoPtr : valid pointer to the listview structure
7034 * [I] nItem : item index
7035 * [I] lpLVItem : item or subitem info
7041 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem)
7043 BOOL bResult = TRUE;
7046 lvItem.iItem = nItem;
7047 lvItem.iSubItem = 0;
7048 lvItem.mask = LVIF_STATE;
7049 lvItem.state = lpLVItem->state;
7050 lvItem.stateMask = lpLVItem->stateMask;
7051 TRACE("lvItem=%s\n", debuglvitem_t(&lvItem, TRUE));
7055 /* apply to all items */
7056 for (lvItem.iItem = 0; lvItem.iItem < infoPtr->nItemCount; lvItem.iItem++)
7057 if (!LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE)) bResult = FALSE;
7060 bResult = LISTVIEW_SetItemT(infoPtr, &lvItem, TRUE);
7063 *update selection mark
7065 * Investigation on windows 2k showed that selection mark was updated
7066 * whenever a new selection was made, but if the selected item was
7067 * unselected it was not updated.
7069 * we are probably still not 100% accurate, but this at least sets the
7070 * proper selection mark when it is needed
7073 if (bResult && (lvItem.state & lvItem.stateMask & LVIS_SELECTED) &&
7074 ((infoPtr->nSelectionMark == -1) || (lvItem.iItem <= infoPtr->nSelectionMark)))
7077 infoPtr->nSelectionMark = -1;
7078 for (i = 0; i < infoPtr->nItemCount; i++)
7080 if (infoPtr->uCallbackMask & LVIS_SELECTED)
7082 if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
7084 infoPtr->nSelectionMark = i;
7088 else if (ranges_contain(infoPtr->selectionRanges, i))
7090 infoPtr->nSelectionMark = i;
7101 * Sets the text of an item or subitem.
7104 * [I] hwnd : window handle
7105 * [I] nItem : item index
7106 * [I] lpLVItem : item or subitem info
7107 * [I] isW : TRUE if input is Unicode
7113 static BOOL LISTVIEW_SetItemTextT(LISTVIEW_INFO *infoPtr, INT nItem, const LVITEMW *lpLVItem, BOOL isW)
7117 if (nItem < 0 && nItem >= infoPtr->nItemCount) return FALSE;
7119 lvItem.iItem = nItem;
7120 lvItem.iSubItem = lpLVItem->iSubItem;
7121 lvItem.mask = LVIF_TEXT;
7122 lvItem.pszText = lpLVItem->pszText;
7123 lvItem.cchTextMax = lpLVItem->cchTextMax;
7125 TRACE("(nItem=%d, lpLVItem=%s, isW=%d)\n", nItem, debuglvitem_t(&lvItem, isW), isW);
7127 return LISTVIEW_SetItemT(infoPtr, &lvItem, isW);
7132 * Set item index that marks the start of a multiple selection.
7135 * [I] infoPtr : valid pointer to the listview structure
7136 * [I] nIndex : index
7139 * Index number or -1 if there is no selection mark.
7141 static INT LISTVIEW_SetSelectionMark(LISTVIEW_INFO *infoPtr, INT nIndex)
7143 INT nOldIndex = infoPtr->nSelectionMark;
7145 TRACE("(nIndex=%d)\n", nIndex);
7147 infoPtr->nSelectionMark = nIndex;
7154 * Sets the text background color.
7157 * [I] infoPtr : valid pointer to the listview structure
7158 * [I] clrTextBk : text background color
7164 static BOOL LISTVIEW_SetTextBkColor(LISTVIEW_INFO *infoPtr, COLORREF clrTextBk)
7166 TRACE("(clrTextBk=%lx)\n", clrTextBk);
7168 if (infoPtr->clrTextBk != clrTextBk)
7170 infoPtr->clrTextBk = clrTextBk;
7171 LISTVIEW_InvalidateList(infoPtr);
7179 * Sets the text foreground color.
7182 * [I] infoPtr : valid pointer to the listview structure
7183 * [I] clrText : text color
7189 static BOOL LISTVIEW_SetTextColor (LISTVIEW_INFO *infoPtr, COLORREF clrText)
7191 TRACE("(clrText=%lx)\n", clrText);
7193 if (infoPtr->clrText != clrText)
7195 infoPtr->clrText = clrText;
7196 LISTVIEW_InvalidateList(infoPtr);
7204 * Determines which listview item is located at the specified position.
7207 * [I] infoPtr : valid pointer to the listview structure
7208 * [I] hwndNewToolTip : handle to new ToolTip
7213 static HWND LISTVIEW_SetToolTips( LISTVIEW_INFO *infoPtr, HWND hwndNewToolTip)
7215 HWND hwndOldToolTip = infoPtr->hwndToolTip;
7216 infoPtr->hwndToolTip = hwndNewToolTip;
7217 return hwndOldToolTip;
7220 /* LISTVIEW_SetUnicodeFormat */
7221 /* LISTVIEW_SetWorkAreas */
7225 * Callback internally used by LISTVIEW_SortItems()
7228 * [I] first : pointer to first ITEM_INFO to compare
7229 * [I] second : pointer to second ITEM_INFO to compare
7230 * [I] lParam : HWND of control
7233 * if first comes before second : negative
7234 * if first comes after second : positive
7235 * if first and second are equivalent : zero
7237 static INT WINAPI LISTVIEW_CallBackCompare(LPVOID first, LPVOID second, LPARAM lParam)
7239 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)lParam;
7240 ITEM_INFO* lv_first = (ITEM_INFO*) DPA_GetPtr( (HDPA)first, 0 );
7241 ITEM_INFO* lv_second = (ITEM_INFO*) DPA_GetPtr( (HDPA)second, 0 );
7243 /* Forward the call to the client defined callback */
7244 return (infoPtr->pfnCompare)( lv_first->lParam , lv_second->lParam, infoPtr->lParamSort );
7249 * Sorts the listview items.
7252 * [I] infoPtr : valid pointer to the listview structure
7253 * [I] pfnCompare : application-defined value
7254 * [I] lParamSort : pointer to comparision callback
7260 static BOOL LISTVIEW_SortItems(LISTVIEW_INFO *infoPtr, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
7262 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7265 LPVOID selectionMarkItem;
7269 TRACE("(pfnCompare=%p, lParamSort=%lx)\n", pfnCompare, lParamSort);
7271 if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
7273 if (!infoPtr->hdpaItems) return FALSE;
7275 /* if there are 0 or 1 items, there is no need to sort */
7276 if (infoPtr->nItemCount < 2) return TRUE;
7278 if (infoPtr->nFocusedItem >= 0)
7280 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nFocusedItem);
7281 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7282 if (lpItem) lpItem->state |= LVIS_FOCUSED;
7284 /* FIXME: go thorugh selected items and mark them so in lpItem->state */
7285 /* clear the lpItem->state for non-selected ones */
7286 /* remove the selection ranges */
7288 infoPtr->pfnCompare = pfnCompare;
7289 infoPtr->lParamSort = lParamSort;
7290 DPA_Sort(infoPtr->hdpaItems, LISTVIEW_CallBackCompare, (LPARAM)infoPtr);
7292 /* Adjust selections and indices so that they are the way they should
7293 * be after the sort (otherwise, the list items move around, but
7294 * whatever is at the item's previous original position will be
7297 selectionMarkItem=(infoPtr->nSelectionMark>=0)?DPA_GetPtr(infoPtr->hdpaItems, infoPtr->nSelectionMark):NULL;
7298 for (i=0; i < infoPtr->nItemCount; i++)
7300 hdpaSubItems = (HDPA)DPA_GetPtr(infoPtr->hdpaItems, i);
7301 lpItem = (ITEM_INFO *)DPA_GetPtr(hdpaSubItems, 0);
7303 if (lpItem->state & LVIS_SELECTED)
7305 item.state = LVIS_SELECTED;
7306 item.stateMask = LVIS_SELECTED;
7307 LISTVIEW_SetItemState(infoPtr, i, &item);
7309 if (lpItem->state & LVIS_FOCUSED)
7311 infoPtr->nFocusedItem = i;
7312 lpItem->state &= ~LVIS_FOCUSED;
7315 if (selectionMarkItem != NULL)
7316 infoPtr->nSelectionMark = DPA_GetPtrIndex(infoPtr->hdpaItems, selectionMarkItem);
7317 /* I believe nHotItem should be left alone, see LISTVIEW_ShiftIndices */
7319 /* refresh the display */
7320 if (uView != LVS_ICON && uView != LVS_SMALLICON)
7321 LISTVIEW_InvalidateList(infoPtr);
7328 * Updates an items or rearranges the listview control.
7331 * [I] infoPtr : valid pointer to the listview structure
7332 * [I] nItem : item index
7338 static BOOL LISTVIEW_Update(LISTVIEW_INFO *infoPtr, INT nItem)
7340 TRACE("(nItem=%d)\n", nItem);
7342 if (nItem < 0 || nItem >= infoPtr->nItemCount) return FALSE;
7344 /* rearrange with default alignment style */
7345 if (is_autoarrange(infoPtr))
7346 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
7348 LISTVIEW_InvalidateItem(infoPtr, nItem);
7356 * Creates the listview control.
7359 * [I] hwnd : window handle
7360 * [I] lpcs : the create parameters
7366 static LRESULT LISTVIEW_Create(HWND hwnd, const CREATESTRUCTW *lpcs)
7368 LISTVIEW_INFO *infoPtr;
7369 UINT uView = lpcs->style & LVS_TYPEMASK;
7372 TRACE("(lpcs=%p)\n", lpcs);
7374 /* initialize info pointer */
7375 infoPtr = (LISTVIEW_INFO *)Alloc(sizeof(LISTVIEW_INFO));
7376 if (!infoPtr) return -1;
7378 SetWindowLongW(hwnd, 0, (LONG)infoPtr);
7380 infoPtr->hwndSelf = hwnd;
7381 infoPtr->dwStyle = lpcs->style;
7382 /* determine the type of structures to use */
7383 infoPtr->hwndNotify = lpcs->hwndParent;
7384 infoPtr->notifyFormat = SendMessageW(infoPtr->hwndNotify, WM_NOTIFYFORMAT,
7385 (WPARAM)infoPtr->hwndSelf, (LPARAM)NF_QUERY);
7387 /* initialize color information */
7388 infoPtr->clrBk = CLR_NONE;
7389 infoPtr->clrText = comctl32_color.clrWindowText;
7390 infoPtr->clrTextBk = CLR_DEFAULT;
7391 LISTVIEW_SetBkColor(infoPtr, comctl32_color.clrWindow);
7393 /* set default values */
7394 infoPtr->nFocusedItem = -1;
7395 infoPtr->nSelectionMark = -1;
7396 infoPtr->nHotItem = -1;
7397 infoPtr->bRedraw = TRUE;
7398 infoPtr->bNoItemMetrics = TRUE;
7399 infoPtr->bDoChangeNotify = TRUE;
7400 infoPtr->iconSpacing.cx = GetSystemMetrics(SM_CXICONSPACING);
7401 infoPtr->iconSpacing.cy = GetSystemMetrics(SM_CYICONSPACING);
7402 infoPtr->nEditLabelItem = -1;
7403 infoPtr->dwHoverTime = -1; /* default system hover time */
7405 /* get default font (icon title) */
7406 SystemParametersInfoW(SPI_GETICONTITLELOGFONT, 0, &logFont, 0);
7407 infoPtr->hDefaultFont = CreateFontIndirectW(&logFont);
7408 infoPtr->hFont = infoPtr->hDefaultFont;
7409 LISTVIEW_SaveTextMetrics(infoPtr);
7412 infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL,
7413 WS_CHILD | HDS_HORZ | (DWORD)((LVS_NOSORTHEADER & lpcs->style)?0:HDS_BUTTONS),
7414 0, 0, 0, 0, hwnd, NULL,
7415 lpcs->hInstance, NULL);
7416 if (!infoPtr->hwndHeader) goto fail;
7418 /* set header unicode format */
7419 SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)NULL);
7421 /* set header font */
7422 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
7424 /* allocate memory for the data structure */
7425 if (!(infoPtr->selectionRanges = ranges_create(10))) goto fail;
7426 if (!(infoPtr->hdpaItems = DPA_Create(10))) goto fail;
7427 if (!(infoPtr->hdpaPosX = DPA_Create(10))) goto fail;
7428 if (!(infoPtr->hdpaPosY = DPA_Create(10))) goto fail;
7429 if (!(infoPtr->hdpaColumns = DPA_Create(10))) goto fail;
7431 /* initialize the icon sizes */
7432 set_icon_size(&infoPtr->iconSize, infoPtr->himlNormal, uView != LVS_ICON);
7433 set_icon_size(&infoPtr->iconStateSize, infoPtr->himlState, TRUE);
7435 /* init item size to avoid division by 0 */
7436 LISTVIEW_UpdateItemSize (infoPtr);
7438 if (uView == LVS_REPORT)
7440 if (!(LVS_NOCOLUMNHEADER & lpcs->style))
7442 ShowWindow(infoPtr->hwndHeader, SW_SHOWNORMAL);
7446 /* set HDS_HIDDEN flag to hide the header bar */
7447 SetWindowLongW(infoPtr->hwndHeader, GWL_STYLE,
7448 GetWindowLongW(infoPtr->hwndHeader, GWL_STYLE) | HDS_HIDDEN);
7455 DestroyWindow(infoPtr->hwndHeader);
7456 ranges_destroy(infoPtr->selectionRanges);
7457 DPA_Destroy(infoPtr->hdpaItems);
7458 DPA_Destroy(infoPtr->hdpaPosX);
7459 DPA_Destroy(infoPtr->hdpaPosY);
7460 DPA_Destroy(infoPtr->hdpaColumns);
7467 * Erases the background of the listview control.
7470 * [I] infoPtr : valid pointer to the listview structure
7471 * [I] hdc : device context handle
7477 static inline BOOL LISTVIEW_EraseBkgnd(LISTVIEW_INFO *infoPtr, HDC hdc)
7481 TRACE("(hdc=%p)\n", hdc);
7483 if (!GetClipBox(hdc, &rc)) return FALSE;
7485 return LISTVIEW_FillBkgnd(infoPtr, hdc, &rc);
7491 * Helper function for LISTVIEW_[HV]Scroll *only*.
7492 * Performs vertical/horizontal scrolling by a give amount.
7495 * [I] infoPtr : valid pointer to the listview structure
7496 * [I] dx : amount of horizontal scroll
7497 * [I] dy : amount of vertical scroll
7499 static void scroll_list(LISTVIEW_INFO *infoPtr, INT dx, INT dy)
7501 /* now we can scroll the list */
7502 ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &infoPtr->rcList,
7503 &infoPtr->rcList, 0, 0, SW_ERASE | SW_INVALIDATE);
7504 /* if we have focus, adjust rect */
7505 OffsetRect(&infoPtr->rcFocus, dx, dy);
7506 UpdateWindow(infoPtr->hwndSelf);
7511 * Performs vertical scrolling.
7514 * [I] infoPtr : valid pointer to the listview structure
7515 * [I] nScrollCode : scroll code
7516 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7517 * [I] hScrollWnd : scrollbar control window handle
7523 * SB_LINEUP/SB_LINEDOWN:
7524 * for LVS_ICON, LVS_SMALLICON is 37 by experiment
7525 * for LVS_REPORT is 1 line
7526 * for LVS_LIST cannot occur
7529 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7530 INT nScrollDiff, HWND hScrollWnd)
7532 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7533 INT nOldScrollPos, nNewScrollPos;
7534 SCROLLINFO scrollInfo;
7537 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7538 debugscrollcode(nScrollCode), nScrollDiff);
7540 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7542 scrollInfo.cbSize = sizeof(SCROLLINFO);
7543 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7545 is_an_icon = ((uView == LVS_ICON) || (uView == LVS_SMALLICON));
7547 if (!GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo)) return 1;
7549 nOldScrollPos = scrollInfo.nPos;
7550 switch (nScrollCode)
7556 nScrollDiff = (is_an_icon) ? -LISTVIEW_SCROLL_ICON_LINE_SIZE : -1;
7560 nScrollDiff = (is_an_icon) ? LISTVIEW_SCROLL_ICON_LINE_SIZE : 1;
7564 nScrollDiff = -scrollInfo.nPage;
7568 nScrollDiff = scrollInfo.nPage;
7571 case SB_THUMBPOSITION:
7573 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7580 /* quit right away if pos isn't changing */
7581 if (nScrollDiff == 0) return 0;
7583 /* calculate new position, and handle overflows */
7584 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7585 if (nScrollDiff > 0) {
7586 if (nNewScrollPos < nOldScrollPos ||
7587 nNewScrollPos > scrollInfo.nMax)
7588 nNewScrollPos = scrollInfo.nMax;
7590 if (nNewScrollPos > nOldScrollPos ||
7591 nNewScrollPos < scrollInfo.nMin)
7592 nNewScrollPos = scrollInfo.nMin;
7595 /* set the new position, and reread in case it changed */
7596 scrollInfo.fMask = SIF_POS;
7597 scrollInfo.nPos = nNewScrollPos;
7598 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo, TRUE);
7600 /* carry on only if it really changed */
7601 if (nNewScrollPos == nOldScrollPos) return 0;
7603 /* now adjust to client coordinates */
7604 nScrollDiff = nOldScrollPos - nNewScrollPos;
7605 if (uView == LVS_REPORT) nScrollDiff *= infoPtr->nItemHeight;
7607 /* and scroll the window */
7608 scroll_list(infoPtr, 0, nScrollDiff);
7615 * Performs horizontal scrolling.
7618 * [I] infoPtr : valid pointer to the listview structure
7619 * [I] nScrollCode : scroll code
7620 * [I] nScrollDiff : units to scroll in SB_INTERNAL mode, 0 otherwise
7621 * [I] hScrollWnd : scrollbar control window handle
7627 * SB_LINELEFT/SB_LINERIGHT:
7628 * for LVS_ICON, LVS_SMALLICON 1 pixel
7629 * for LVS_REPORT is 1 pixel
7630 * for LVS_LIST is 1 column --> which is a 1 because the
7631 * scroll is based on columns not pixels
7634 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *infoPtr, INT nScrollCode,
7635 INT nScrollDiff, HWND hScrollWnd)
7637 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7638 INT nOldScrollPos, nNewScrollPos;
7639 SCROLLINFO scrollInfo;
7641 TRACE("(nScrollCode=%d(%s), nScrollDiff=%d)\n", nScrollCode,
7642 debugscrollcode(nScrollCode), nScrollDiff);
7644 if (infoPtr->hwndEdit) SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
7646 scrollInfo.cbSize = sizeof(SCROLLINFO);
7647 scrollInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
7649 if (!GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo)) return 1;
7651 nOldScrollPos = scrollInfo.nPos;
7653 switch (nScrollCode)
7667 nScrollDiff = -scrollInfo.nPage;
7671 nScrollDiff = scrollInfo.nPage;
7674 case SB_THUMBPOSITION:
7676 nScrollDiff = scrollInfo.nTrackPos - scrollInfo.nPos;
7683 /* quit right away if pos isn't changing */
7684 if (nScrollDiff == 0) return 0;
7686 /* calculate new position, and handle overflows */
7687 nNewScrollPos = scrollInfo.nPos + nScrollDiff;
7688 if (nScrollDiff > 0) {
7689 if (nNewScrollPos < nOldScrollPos ||
7690 nNewScrollPos > scrollInfo.nMax)
7691 nNewScrollPos = scrollInfo.nMax;
7693 if (nNewScrollPos > nOldScrollPos ||
7694 nNewScrollPos < scrollInfo.nMin)
7695 nNewScrollPos = scrollInfo.nMin;
7698 /* set the new position, and reread in case it changed */
7699 scrollInfo.fMask = SIF_POS;
7700 scrollInfo.nPos = nNewScrollPos;
7701 nNewScrollPos = SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo, TRUE);
7703 /* carry on only if it really changed */
7704 if (nNewScrollPos == nOldScrollPos) return 0;
7706 if(uView == LVS_REPORT)
7707 LISTVIEW_UpdateHeaderSize(infoPtr, nNewScrollPos);
7709 /* now adjust to client coordinates */
7710 nScrollDiff = nOldScrollPos - nNewScrollPos;
7711 if (uView == LVS_LIST) nScrollDiff *= infoPtr->nItemWidth;
7713 /* and scroll the window */
7714 scroll_list(infoPtr, nScrollDiff, 0);
7719 static LRESULT LISTVIEW_MouseWheel(LISTVIEW_INFO *infoPtr, INT wheelDelta)
7721 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7722 INT gcWheelDelta = 0;
7723 UINT pulScrollLines = 3;
7724 SCROLLINFO scrollInfo;
7726 TRACE("(wheelDelta=%d)\n", wheelDelta);
7728 SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
7729 gcWheelDelta -= wheelDelta;
7731 scrollInfo.cbSize = sizeof(SCROLLINFO);
7732 scrollInfo.fMask = SIF_POS;
7739 * listview should be scrolled by a multiple of 37 dependently on its dimension or its visible item number
7740 * should be fixed in the future.
7742 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, (gcWheelDelta < 0) ?
7743 -LISTVIEW_SCROLL_ICON_LINE_SIZE : LISTVIEW_SCROLL_ICON_LINE_SIZE, 0);
7747 if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines)
7749 int cLineScroll = min(LISTVIEW_GetCountPerColumn(infoPtr), pulScrollLines);
7750 cLineScroll *= (gcWheelDelta / WHEEL_DELTA);
7751 LISTVIEW_VScroll(infoPtr, SB_INTERNAL, cLineScroll, 0);
7756 LISTVIEW_HScroll(infoPtr, (gcWheelDelta < 0) ? SB_LINELEFT : SB_LINERIGHT, 0, 0);
7767 * [I] infoPtr : valid pointer to the listview structure
7768 * [I] nVirtualKey : virtual key
7769 * [I] lKeyData : key data
7774 static LRESULT LISTVIEW_KeyDown(LISTVIEW_INFO *infoPtr, INT nVirtualKey, LONG lKeyData)
7776 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
7778 NMLVKEYDOWN nmKeyDown;
7780 TRACE("(nVirtualKey=%d, lKeyData=%ld)\n", nVirtualKey, lKeyData);
7782 /* send LVN_KEYDOWN notification */
7783 nmKeyDown.wVKey = nVirtualKey;
7784 nmKeyDown.flags = 0;
7785 notify_hdr(infoPtr, LVN_KEYDOWN, &nmKeyDown.hdr);
7787 switch (nVirtualKey)
7790 if ((infoPtr->nItemCount > 0) && (infoPtr->nFocusedItem != -1))
7792 notify(infoPtr, NM_RETURN);
7793 notify(infoPtr, LVN_ITEMACTIVATE);
7798 if (infoPtr->nItemCount > 0)
7803 if (infoPtr->nItemCount > 0)
7804 nItem = infoPtr->nItemCount - 1;
7808 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TOLEFT);
7812 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_ABOVE);
7816 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_TORIGHT);
7820 nItem = ListView_GetNextItem(infoPtr->hwndSelf, infoPtr->nFocusedItem, LVNI_BELOW);
7824 if (uView == LVS_REPORT)
7825 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr);
7827 nItem = infoPtr->nFocusedItem - LISTVIEW_GetCountPerColumn(infoPtr)
7828 * LISTVIEW_GetCountPerRow(infoPtr);
7829 if(nItem < 0) nItem = 0;
7833 if (uView == LVS_REPORT)
7834 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr);
7836 nItem = infoPtr->nFocusedItem + LISTVIEW_GetCountPerColumn(infoPtr)
7837 * LISTVIEW_GetCountPerRow(infoPtr);
7838 if(nItem >= infoPtr->nItemCount) nItem = infoPtr->nItemCount - 1;
7842 if ((nItem != -1) && (nItem != infoPtr->nFocusedItem))
7843 LISTVIEW_KeySelection(infoPtr, nItem);
7853 * [I] infoPtr : valid pointer to the listview structure
7858 static LRESULT LISTVIEW_KillFocus(LISTVIEW_INFO *infoPtr)
7862 /* if we did not have the focus, there's nothing to do */
7863 if (!infoPtr->bFocus) return 0;
7865 /* send NM_KILLFOCUS notification */
7866 notify(infoPtr, NM_KILLFOCUS);
7868 /* if we have a focus rectagle, get rid of it */
7869 LISTVIEW_ShowFocusRect(infoPtr, FALSE);
7871 /* set window focus flag */
7872 infoPtr->bFocus = FALSE;
7874 /* invalidate the selected items before reseting focus flag */
7875 LISTVIEW_InvalidateSelectedItems(infoPtr);
7883 * Track mouse/dragging
7886 * [I] infoPtr : valid pointer to the listview structure
7887 * [I] pt : mouse coordinate
7892 static LRESULT LISTVIEW_TrackMouse(LISTVIEW_INFO *infoPtr, POINT pt)
7894 INT cxDrag = GetSystemMetrics(SM_CXDRAG);
7895 INT cyDrag = GetSystemMetrics(SM_CYDRAG);
7901 r.top = pt.y - cyDrag;
7902 r.left = pt.x - cxDrag;
7903 r.bottom = pt.y + cyDrag;
7904 r.right = pt.x + cxDrag;
7906 SetCapture(infoPtr->hwndSelf);
7910 if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD))
7912 if (msg.message == WM_MOUSEMOVE)
7914 pt.x = (short)LOWORD(msg.lParam);
7915 pt.y = (short)HIWORD(msg.lParam);
7916 if (PtInRect(&r, pt))
7924 else if (msg.message >= WM_LBUTTONDOWN &&
7925 msg.message <= WM_RBUTTONDBLCLK)
7930 DispatchMessageW(&msg);
7933 if (GetCapture() != infoPtr->hwndSelf)
7944 * Processes double click messages (left mouse button).
7947 * [I] infoPtr : valid pointer to the listview structure
7948 * [I] wKey : key flag
7949 * [I] pts : mouse coordinate
7954 static LRESULT LISTVIEW_LButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7956 LVHITTESTINFO htInfo;
7958 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7960 /* send NM_RELEASEDCAPTURE notification */
7961 notify(infoPtr, NM_RELEASEDCAPTURE);
7963 htInfo.pt.x = pts.x;
7964 htInfo.pt.y = pts.y;
7966 /* send NM_DBLCLK notification */
7967 LISTVIEW_HitTest(infoPtr, &htInfo, TRUE, FALSE);
7968 notify_click(infoPtr, NM_DBLCLK, &htInfo);
7970 /* To send the LVN_ITEMACTIVATE, it must be on an Item */
7971 if(htInfo.iItem != -1) notify_itemactivate(infoPtr,&htInfo);
7978 * Processes mouse down messages (left mouse button).
7981 * [I] infoPtr : valid pointer to the listview structure
7982 * [I] wKey : key flag
7983 * [I] pts : mouse coordinate
7988 static LRESULT LISTVIEW_LButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
7990 LVHITTESTINFO lvHitTestInfo;
7991 static BOOL bGroupSelect = TRUE;
7992 POINT pt = { pts.x, pts.y };
7995 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
7997 /* send NM_RELEASEDCAPTURE notification */
7998 notify(infoPtr, NM_RELEASEDCAPTURE);
8000 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8002 /* set left button down flag */
8003 infoPtr->bLButtonDown = TRUE;
8005 lvHitTestInfo.pt.x = pts.x;
8006 lvHitTestInfo.pt.y = pts.y;
8008 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8009 TRACE("at %s, nItem=%d\n", debugpoint(&pt), nItem);
8010 infoPtr->nEditLabelItem = -1;
8011 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8013 if ((infoPtr->dwLvExStyle & LVS_EX_CHECKBOXES) && (lvHitTestInfo.flags & LVHT_ONITEMSTATEICON))
8015 DWORD state = LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK) >> 12;
8016 if(state == 1 || state == 2)
8020 lvitem.state = state << 12;
8021 lvitem.stateMask = LVIS_STATEIMAGEMASK;
8022 LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
8026 if (LISTVIEW_TrackMouse(infoPtr, lvHitTestInfo.pt))
8030 ZeroMemory(&nmlv, sizeof(nmlv));
8032 nmlv.ptAction.x = lvHitTestInfo.pt.x;
8033 nmlv.ptAction.y = lvHitTestInfo.pt.y;
8035 notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
8040 if (infoPtr->dwStyle & LVS_SINGLESEL)
8042 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8043 infoPtr->nEditLabelItem = nItem;
8045 LISTVIEW_SetSelection(infoPtr, nItem);
8049 if ((wKey & MK_CONTROL) && (wKey & MK_SHIFT))
8053 LISTVIEW_AddGroupSelection(infoPtr, nItem);
8054 LISTVIEW_SetItemFocus(infoPtr, nItem);
8055 infoPtr->nSelectionMark = nItem;
8061 item.state = LVIS_SELECTED | LVIS_FOCUSED;
8062 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8064 LISTVIEW_SetItemState(infoPtr,nItem,&item);
8065 infoPtr->nSelectionMark = nItem;
8068 else if (wKey & MK_CONTROL)
8072 bGroupSelect = (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED) == 0);
8074 item.state = (bGroupSelect ? LVIS_SELECTED : 0) | LVIS_FOCUSED;
8075 item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;
8076 LISTVIEW_SetItemState(infoPtr, nItem, &item);
8077 infoPtr->nSelectionMark = nItem;
8079 else if (wKey & MK_SHIFT)
8081 LISTVIEW_SetGroupSelection(infoPtr, nItem);
8085 if (LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8086 infoPtr->nEditLabelItem = nItem;
8088 /* set selection (clears other pre-existing selections) */
8089 LISTVIEW_SetSelection(infoPtr, nItem);
8095 /* remove all selections */
8096 LISTVIEW_DeselectAll(infoPtr);
8105 * Processes mouse up messages (left mouse button).
8108 * [I] infoPtr : valid pointer to the listview structure
8109 * [I] wKey : key flag
8110 * [I] pts : mouse coordinate
8115 static LRESULT LISTVIEW_LButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8117 LVHITTESTINFO lvHitTestInfo;
8119 TRACE("(key=%hu, X=%hu, Y=%hu)\n", wKey, pts.x, pts.y);
8121 if (!infoPtr->bLButtonDown) return 0;
8123 lvHitTestInfo.pt.x = pts.x;
8124 lvHitTestInfo.pt.y = pts.y;
8126 /* send NM_CLICK notification */
8127 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8128 notify_click(infoPtr, NM_CLICK, &lvHitTestInfo);
8130 /* set left button flag */
8131 infoPtr->bLButtonDown = FALSE;
8133 /* if we clicked on a selected item, edit the label */
8134 if(lvHitTestInfo.iItem == infoPtr->nEditLabelItem && (lvHitTestInfo.flags & LVHT_ONITEMLABEL))
8135 LISTVIEW_EditLabelT(infoPtr, lvHitTestInfo.iItem, TRUE);
8142 * Destroys the listview control (called after WM_DESTROY).
8145 * [I] infoPtr : valid pointer to the listview structure
8150 static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
8154 /* delete all items */
8155 LISTVIEW_DeleteAllItems(infoPtr);
8157 /* destroy data structure */
8158 DPA_Destroy(infoPtr->hdpaItems);
8159 DPA_Destroy(infoPtr->hdpaPosX);
8160 DPA_Destroy(infoPtr->hdpaPosY);
8161 DPA_Destroy(infoPtr->hdpaColumns);
8162 ranges_destroy(infoPtr->selectionRanges);
8164 /* destroy image lists */
8165 if (!(infoPtr->dwStyle & LVS_SHAREIMAGELISTS))
8167 if (infoPtr->himlNormal)
8168 ImageList_Destroy(infoPtr->himlNormal);
8169 if (infoPtr->himlSmall)
8170 ImageList_Destroy(infoPtr->himlSmall);
8171 if (infoPtr->himlState)
8172 ImageList_Destroy(infoPtr->himlState);
8175 /* destroy font, bkgnd brush */
8177 if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
8178 if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
8180 SetWindowLongW(infoPtr->hwndSelf, 0, 0);
8182 /* free listview info pointer*/
8190 * Handles notifications from header.
8193 * [I] infoPtr : valid pointer to the listview structure
8194 * [I] nCtrlId : control identifier
8195 * [I] lpnmh : notification information
8200 static LRESULT LISTVIEW_HeaderNotification(LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
8202 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8204 TRACE("(lpnmh=%p)\n", lpnmh);
8206 if (!lpnmh || lpnmh->iItem < 0 || lpnmh->iItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return 0;
8208 switch (lpnmh->hdr.code)
8212 case HDN_ITEMCHANGEDW:
8213 case HDN_ITEMCHANGEDA:
8215 COLUMN_INFO *lpColumnInfo;
8218 if (!lpnmh->pitem || !(lpnmh->pitem->mask & HDI_WIDTH))
8222 hdi.mask = HDI_WIDTH;
8223 if (!Header_GetItemW(infoPtr->hwndHeader, lpnmh->iItem, (LPARAM)&hdi)) return 0;
8227 cxy = lpnmh->pitem->cxy;
8229 /* determine how much we change since the last know position */
8230 lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpnmh->iItem);
8231 dx = cxy - (lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left);
8234 RECT rcCol = lpColumnInfo->rcHeader;
8236 lpColumnInfo->rcHeader.right += dx;
8237 LISTVIEW_ScrollColumns(infoPtr, lpnmh->iItem + 1, dx);
8238 if (uView == LVS_REPORT && is_redrawing(infoPtr))
8240 /* this trick works for left aligned columns only */
8241 if ((lpColumnInfo->fmt & LVCFMT_JUSTIFYMASK) == LVCFMT_LEFT)
8243 rcCol.right = min (rcCol.right, lpColumnInfo->rcHeader.right);
8244 rcCol.left = max (rcCol.left, rcCol.right - 3 * infoPtr->ntmAveCharWidth);
8246 rcCol.top = infoPtr->rcList.top;
8247 rcCol.bottom = infoPtr->rcList.bottom;
8248 LISTVIEW_InvalidateRect(infoPtr, &rcCol);
8254 case HDN_ITEMCLICKW:
8255 case HDN_ITEMCLICKA:
8257 /* Handle sorting by Header Column */
8260 ZeroMemory(&nmlv, sizeof(NMLISTVIEW));
8262 nmlv.iSubItem = lpnmh->iItem;
8263 notify_listview(infoPtr, LVN_COLUMNCLICK, &nmlv);
8273 * Determines the type of structure to use.
8276 * [I] infoPtr : valid pointer to the listview structureof the sender
8277 * [I] hwndFrom : listview window handle
8278 * [I] nCommand : command specifying the nature of the WM_NOTIFYFORMAT
8283 static LRESULT LISTVIEW_NotifyFormat(LISTVIEW_INFO *infoPtr, HWND hwndFrom, INT nCommand)
8285 TRACE("(hwndFrom=%p, nCommand=%d)\n", hwndFrom, nCommand);
8287 if (nCommand != NF_REQUERY) return 0;
8289 infoPtr->notifyFormat = SendMessageW(hwndFrom, WM_NOTIFYFORMAT, (WPARAM)infoPtr->hwndSelf, NF_QUERY);
8296 * Paints/Repaints the listview control.
8299 * [I] infoPtr : valid pointer to the listview structure
8300 * [I] hdc : device context handle
8305 static LRESULT LISTVIEW_Paint(LISTVIEW_INFO *infoPtr, HDC hdc)
8307 TRACE("(hdc=%p)\n", hdc);
8309 if (infoPtr->bNoItemMetrics && infoPtr->nItemCount)
8311 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8313 infoPtr->bNoItemMetrics = FALSE;
8314 LISTVIEW_UpdateItemSize(infoPtr);
8315 if (uView == LVS_ICON || uView == LVS_SMALLICON)
8316 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8317 LISTVIEW_UpdateScroll(infoPtr);
8320 LISTVIEW_Refresh(infoPtr, hdc);
8325 hdc = BeginPaint(infoPtr->hwndSelf, &ps);
8327 if (ps.fErase) LISTVIEW_FillBkgnd(infoPtr, hdc, &ps.rcPaint);
8328 LISTVIEW_Refresh(infoPtr, hdc);
8329 EndPaint(infoPtr->hwndSelf, &ps);
8337 * Processes double click messages (right mouse button).
8340 * [I] infoPtr : valid pointer to the listview structure
8341 * [I] wKey : key flag
8342 * [I] pts : mouse coordinate
8347 static LRESULT LISTVIEW_RButtonDblClk(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8349 LVHITTESTINFO lvHitTestInfo;
8351 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8353 /* send NM_RELEASEDCAPTURE notification */
8354 notify(infoPtr, NM_RELEASEDCAPTURE);
8356 /* send NM_RDBLCLK notification */
8357 lvHitTestInfo.pt.x = pts.x;
8358 lvHitTestInfo.pt.y = pts.y;
8359 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8360 notify_click(infoPtr, NM_RDBLCLK, &lvHitTestInfo);
8367 * Processes mouse down messages (right mouse button).
8370 * [I] infoPtr : valid pointer to the listview structure
8371 * [I] wKey : key flag
8372 * [I] pts : mouse coordinate
8377 static LRESULT LISTVIEW_RButtonDown(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8379 LVHITTESTINFO lvHitTestInfo;
8382 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8384 /* send NM_RELEASEDCAPTURE notification */
8385 notify(infoPtr, NM_RELEASEDCAPTURE);
8387 /* make sure the listview control window has the focus */
8388 if (!infoPtr->bFocus) SetFocus(infoPtr->hwndSelf);
8390 /* set right button down flag */
8391 infoPtr->bRButtonDown = TRUE;
8393 /* determine the index of the selected item */
8394 lvHitTestInfo.pt.x = pts.x;
8395 lvHitTestInfo.pt.y = pts.y;
8396 nItem = LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, TRUE);
8398 if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
8400 LISTVIEW_SetItemFocus(infoPtr, nItem);
8401 if (!((wKey & MK_SHIFT) || (wKey & MK_CONTROL)) &&
8402 !LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED))
8403 LISTVIEW_SetSelection(infoPtr, nItem);
8407 LISTVIEW_DeselectAll(infoPtr);
8415 * Processes mouse up messages (right mouse button).
8418 * [I] infoPtr : valid pointer to the listview structure
8419 * [I] wKey : key flag
8420 * [I] pts : mouse coordinate
8425 static LRESULT LISTVIEW_RButtonUp(LISTVIEW_INFO *infoPtr, WORD wKey, POINTS pts)
8427 LVHITTESTINFO lvHitTestInfo;
8430 TRACE("(key=%hu,X=%hu,Y=%hu)\n", wKey, pts.x, pts.y);
8432 if (!infoPtr->bRButtonDown) return 0;
8434 /* set button flag */
8435 infoPtr->bRButtonDown = FALSE;
8437 /* Send NM_RClICK notification */
8438 lvHitTestInfo.pt.x = pts.x;
8439 lvHitTestInfo.pt.y = pts.y;
8440 LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
8441 notify_click(infoPtr, NM_RCLICK, &lvHitTestInfo);
8443 /* Change to screen coordinate for WM_CONTEXTMENU */
8444 pt = lvHitTestInfo.pt;
8445 ClientToScreen(infoPtr->hwndSelf, &pt);
8447 /* Send a WM_CONTEXTMENU message in response to the RBUTTONUP */
8448 SendMessageW(infoPtr->hwndSelf, WM_CONTEXTMENU,
8449 (WPARAM)infoPtr->hwndSelf, MAKELPARAM(pt.x, pt.y));
8460 * [I] infoPtr : valid pointer to the listview structure
8461 * [I] hwnd : window handle of window containing the cursor
8462 * [I] nHittest : hit-test code
8463 * [I] wMouseMsg : ideintifier of the mouse message
8466 * TRUE if cursor is set
8469 static BOOL LISTVIEW_SetCursor(LISTVIEW_INFO *infoPtr, HWND hwnd, UINT nHittest, UINT wMouseMsg)
8471 LVHITTESTINFO lvHitTestInfo;
8473 if(!(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)) return FALSE;
8475 if(!infoPtr->hHotCursor) return FALSE;
8477 GetCursorPos(&lvHitTestInfo.pt);
8478 if (LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, FALSE, FALSE) < 0) return FALSE;
8480 SetCursor(infoPtr->hHotCursor);
8490 * [I] infoPtr : valid pointer to the listview structure
8491 * [I] hwndLoseFocus : handle of previously focused window
8496 static LRESULT LISTVIEW_SetFocus(LISTVIEW_INFO *infoPtr, HWND hwndLoseFocus)
8498 TRACE("(hwndLoseFocus=%p)\n", hwndLoseFocus);
8500 /* if we have the focus already, there's nothing to do */
8501 if (infoPtr->bFocus) return 0;
8503 /* send NM_SETFOCUS notification */
8504 notify(infoPtr, NM_SETFOCUS);
8506 /* set window focus flag */
8507 infoPtr->bFocus = TRUE;
8509 /* put the focus rect back on */
8510 LISTVIEW_ShowFocusRect(infoPtr, TRUE);
8512 /* redraw all visible selected items */
8513 LISTVIEW_InvalidateSelectedItems(infoPtr);
8523 * [I] infoPtr : valid pointer to the listview structure
8524 * [I] fRedraw : font handle
8525 * [I] fRedraw : redraw flag
8530 static LRESULT LISTVIEW_SetFont(LISTVIEW_INFO *infoPtr, HFONT hFont, WORD fRedraw)
8532 HFONT oldFont = infoPtr->hFont;
8534 TRACE("(hfont=%p,redraw=%hu)\n", hFont, fRedraw);
8536 infoPtr->hFont = hFont ? hFont : infoPtr->hDefaultFont;
8537 if (infoPtr->hFont == oldFont) return 0;
8539 LISTVIEW_SaveTextMetrics(infoPtr);
8541 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_REPORT)
8542 SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(fRedraw, 0));
8544 if (fRedraw) LISTVIEW_InvalidateList(infoPtr);
8551 * Message handling for WM_SETREDRAW.
8552 * For the Listview, it invalidates the entire window (the doc specifies otherwise)
8555 * [I] infoPtr : valid pointer to the listview structure
8556 * [I] bRedraw: state of redraw flag
8559 * DefWinProc return value
8561 static LRESULT LISTVIEW_SetRedraw(LISTVIEW_INFO *infoPtr, BOOL bRedraw)
8563 TRACE("infoPtr->bRedraw=%d, bRedraw=%d\n", infoPtr->bRedraw, bRedraw);
8565 /* we can not use straight equality here because _any_ non-zero value is TRUE */
8566 if ((infoPtr->bRedraw && bRedraw) || (!infoPtr->bRedraw && !bRedraw)) return 0;
8568 infoPtr->bRedraw = bRedraw;
8570 if(!bRedraw) return 0;
8572 if (is_autoarrange(infoPtr))
8573 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8574 LISTVIEW_UpdateScroll(infoPtr);
8576 /* despite what the WM_SETREDRAW docs says, apps expect us
8577 * to invalidate the listview here... stupid! */
8578 LISTVIEW_InvalidateList(infoPtr);
8585 * Resizes the listview control. This function processes WM_SIZE
8586 * messages. At this time, the width and height are not used.
8589 * [I] infoPtr : valid pointer to the listview structure
8590 * [I] Width : new width
8591 * [I] Height : new height
8596 static LRESULT LISTVIEW_Size(LISTVIEW_INFO *infoPtr, int Width, int Height)
8598 RECT rcOld = infoPtr->rcList;
8600 TRACE("(width=%d, height=%d)\n", Width, Height);
8602 LISTVIEW_UpdateSize(infoPtr);
8603 if (EqualRect(&rcOld, &infoPtr->rcList)) return 0;
8605 /* do not bother with display related stuff if we're not redrawing */
8606 if (!is_redrawing(infoPtr)) return 0;
8608 if (is_autoarrange(infoPtr))
8609 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8611 LISTVIEW_UpdateScroll(infoPtr);
8613 /* refresh all only for lists whose height changed significantly */
8614 if ((infoPtr->dwStyle & LVS_TYPEMASK) == LVS_LIST &&
8615 (rcOld.bottom - rcOld.top) / infoPtr->nItemHeight !=
8616 (infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight)
8617 LISTVIEW_InvalidateList(infoPtr);
8624 * Sets the size information.
8627 * [I] infoPtr : valid pointer to the listview structure
8632 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *infoPtr)
8634 UINT uView = infoPtr->dwStyle & LVS_TYPEMASK;
8636 TRACE("uView=%d, rcList(old)=%s\n", uView, debugrect(&infoPtr->rcList));
8638 GetClientRect(infoPtr->hwndSelf, &infoPtr->rcList);
8640 if (uView == LVS_LIST)
8642 /* Apparently the "LIST" style is supposed to have the same
8643 * number of items in a column even if there is no scroll bar.
8644 * Since if a scroll bar already exists then the bottom is already
8645 * reduced, only reduce if the scroll bar does not currently exist.
8646 * The "2" is there to mimic the native control. I think it may be
8647 * related to either padding or edges. (GLA 7/2002)
8649 if (!(infoPtr->dwStyle & WS_HSCROLL))
8650 infoPtr->rcList.bottom -= GetSystemMetrics(SM_CYHSCROLL);
8651 infoPtr->rcList.bottom = max (infoPtr->rcList.bottom - 2, 0);
8653 else if (uView == LVS_REPORT && !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER))
8658 hl.prc = &infoPtr->rcList;
8660 Header_Layout(infoPtr->hwndHeader, &hl);
8662 SetWindowPos(wp.hwnd, wp.hwndInsertAfter, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8664 infoPtr->rcList.top = max(wp.cy, 0);
8667 TRACE(" rcList=%s\n", debugrect(&infoPtr->rcList));
8672 * Processes WM_STYLECHANGED messages.
8675 * [I] infoPtr : valid pointer to the listview structure
8676 * [I] wStyleType : window style type (normal or extended)
8677 * [I] lpss : window style information
8682 static INT LISTVIEW_StyleChanged(LISTVIEW_INFO *infoPtr, WPARAM wStyleType,
8683 const STYLESTRUCT *lpss)
8685 UINT uNewView = lpss->styleNew & LVS_TYPEMASK;
8686 UINT uOldView = lpss->styleOld & LVS_TYPEMASK;
8688 TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
8689 wStyleType, lpss->styleOld, lpss->styleNew);
8691 if (wStyleType != GWL_STYLE) return 0;
8693 /* FIXME: if LVS_NOSORTHEADER changed, update header */
8694 /* what if LVS_OWNERDATA changed? */
8695 /* or LVS_SINGLESEL */
8696 /* or LVS_SORT{AS,DES}CENDING */
8698 infoPtr->dwStyle = lpss->styleNew;
8700 if (((lpss->styleOld & WS_HSCROLL) != 0)&&
8701 ((lpss->styleNew & WS_HSCROLL) == 0))
8702 ShowScrollBar(infoPtr->hwndSelf, SB_HORZ, FALSE);
8704 if (((lpss->styleOld & WS_VSCROLL) != 0)&&
8705 ((lpss->styleNew & WS_VSCROLL) == 0))
8706 ShowScrollBar(infoPtr->hwndSelf, SB_VERT, FALSE);
8708 if (uNewView != uOldView)
8710 SIZE oldIconSize = infoPtr->iconSize;
8713 SendMessageW(infoPtr->hwndEdit, WM_KILLFOCUS, 0, 0);
8714 ShowWindow(infoPtr->hwndHeader, SW_HIDE);
8716 ShowScrollBar(infoPtr->hwndSelf, SB_BOTH, FALSE);
8717 SetRectEmpty(&infoPtr->rcFocus);
8719 himl = (uNewView == LVS_ICON ? infoPtr->himlNormal : infoPtr->himlSmall);
8720 set_icon_size(&infoPtr->iconSize, himl, uNewView != LVS_ICON);
8722 if (uNewView == LVS_ICON)
8724 if ((infoPtr->iconSize.cx != oldIconSize.cx) || (infoPtr->iconSize.cy != oldIconSize.cy))
8726 TRACE("icon old size=(%ld,%ld), new size=(%ld,%ld)\n",
8727 oldIconSize.cx, oldIconSize.cy, infoPtr->iconSize.cx, infoPtr->iconSize.cy);
8728 LISTVIEW_SetIconSpacing(infoPtr, 0, 0);
8731 else if (uNewView == LVS_REPORT)
8736 hl.prc = &infoPtr->rcList;
8738 Header_Layout(infoPtr->hwndHeader, &hl);
8739 SetWindowPos(infoPtr->hwndHeader, infoPtr->hwndSelf, wp.x, wp.y, wp.cx, wp.cy, wp.flags);
8742 LISTVIEW_UpdateItemSize(infoPtr);
8745 if (uNewView == LVS_REPORT)
8746 ShowWindow(infoPtr->hwndHeader, (lpss->styleNew & LVS_NOCOLUMNHEADER) ? SW_HIDE : SW_SHOWNORMAL);
8748 if ( (uNewView == LVS_ICON || uNewView == LVS_SMALLICON) &&
8749 (uNewView != uOldView || ((lpss->styleNew ^ lpss->styleOld) & LVS_ALIGNMASK)) )
8750 LISTVIEW_Arrange(infoPtr, LVA_DEFAULT);
8752 /* update the size of the client area */
8753 LISTVIEW_UpdateSize(infoPtr);
8755 /* add scrollbars if needed */
8756 LISTVIEW_UpdateScroll(infoPtr);
8758 /* invalidate client area + erase background */
8759 LISTVIEW_InvalidateList(infoPtr);
8766 * Window procedure of the listview control.
8769 static LRESULT WINAPI
8770 LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
8772 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(hwnd, 0);
8774 TRACE("(uMsg=%x wParam=%x lParam=%lx)\n", uMsg, wParam, lParam);
8776 if (!infoPtr && (uMsg != WM_CREATE))
8777 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
8781 infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
8786 case LVM_APPROXIMATEVIEWRECT:
8787 return LISTVIEW_ApproximateViewRect(infoPtr, (INT)wParam,
8788 LOWORD(lParam), HIWORD(lParam));
8790 return LISTVIEW_Arrange(infoPtr, (INT)wParam);
8792 /* case LVM_CANCELEDITLABEL: */
8794 case LVM_CREATEDRAGIMAGE:
8795 return (LRESULT)LISTVIEW_CreateDragImage(infoPtr, (INT)wParam, (LPPOINT)lParam);
8797 case LVM_DELETEALLITEMS:
8798 return LISTVIEW_DeleteAllItems(infoPtr);
8800 case LVM_DELETECOLUMN:
8801 return LISTVIEW_DeleteColumn(infoPtr, (INT)wParam);
8803 case LVM_DELETEITEM:
8804 return LISTVIEW_DeleteItem(infoPtr, (INT)wParam);
8806 case LVM_EDITLABELW:
8807 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, TRUE);
8809 case LVM_EDITLABELA:
8810 return (LRESULT)LISTVIEW_EditLabelT(infoPtr, (INT)wParam, FALSE);
8812 /* case LVM_ENABLEGROUPVIEW: */
8814 case LVM_ENSUREVISIBLE:
8815 return LISTVIEW_EnsureVisible(infoPtr, (INT)wParam, (BOOL)lParam);
8818 return LISTVIEW_FindItemW(infoPtr, (INT)wParam, (LPLVFINDINFOW)lParam);
8821 return LISTVIEW_FindItemA(infoPtr, (INT)wParam, (LPLVFINDINFOA)lParam);
8823 case LVM_GETBKCOLOR:
8824 return infoPtr->clrBk;
8826 /* case LVM_GETBKIMAGE: */
8828 case LVM_GETCALLBACKMASK:
8829 return infoPtr->uCallbackMask;
8831 case LVM_GETCOLUMNA:
8832 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8834 case LVM_GETCOLUMNW:
8835 return LISTVIEW_GetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8837 case LVM_GETCOLUMNORDERARRAY:
8838 return LISTVIEW_GetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
8840 case LVM_GETCOLUMNWIDTH:
8841 return LISTVIEW_GetColumnWidth(infoPtr, (INT)wParam);
8843 case LVM_GETCOUNTPERPAGE:
8844 return LISTVIEW_GetCountPerPage(infoPtr);
8846 case LVM_GETEDITCONTROL:
8847 return (LRESULT)infoPtr->hwndEdit;
8849 case LVM_GETEXTENDEDLISTVIEWSTYLE:
8850 return infoPtr->dwLvExStyle;
8852 /* case LVM_GETGROUPINFO: */
8854 /* case LVM_GETGROUPMETRICS: */
8857 return (LRESULT)infoPtr->hwndHeader;
8859 case LVM_GETHOTCURSOR:
8860 return (LRESULT)infoPtr->hHotCursor;
8862 case LVM_GETHOTITEM:
8863 return infoPtr->nHotItem;
8865 case LVM_GETHOVERTIME:
8866 return infoPtr->dwHoverTime;
8868 case LVM_GETIMAGELIST:
8869 return (LRESULT)LISTVIEW_GetImageList(infoPtr, (INT)wParam);
8871 /* case LVM_GETINSERTMARK: */
8873 /* case LVM_GETINSERTMARKCOLOR: */
8875 /* case LVM_GETINSERTMARKRECT: */
8877 case LVM_GETISEARCHSTRINGA:
8878 case LVM_GETISEARCHSTRINGW:
8879 FIXME("LVM_GETISEARCHSTRING: unimplemented\n");
8883 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, FALSE);
8886 return LISTVIEW_GetItemExtT(infoPtr, (LPLVITEMW)lParam, TRUE);
8888 case LVM_GETITEMCOUNT:
8889 return infoPtr->nItemCount;
8891 case LVM_GETITEMPOSITION:
8892 return LISTVIEW_GetItemPosition(infoPtr, (INT)wParam, (LPPOINT)lParam);
8894 case LVM_GETITEMRECT:
8895 return LISTVIEW_GetItemRect(infoPtr, (INT)wParam, (LPRECT)lParam);
8897 case LVM_GETITEMSPACING:
8898 return LISTVIEW_GetItemSpacing(infoPtr, (BOOL)wParam);
8900 case LVM_GETITEMSTATE:
8901 return LISTVIEW_GetItemState(infoPtr, (INT)wParam, (UINT)lParam);
8903 case LVM_GETITEMTEXTA:
8904 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
8906 case LVM_GETITEMTEXTW:
8907 return LISTVIEW_GetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
8909 case LVM_GETNEXTITEM:
8910 return LISTVIEW_GetNextItem(infoPtr, (INT)wParam, LOWORD(lParam));
8912 case LVM_GETNUMBEROFWORKAREAS:
8913 FIXME("LVM_GETNUMBEROFWORKAREAS: unimplemented\n");
8917 if (!lParam) return FALSE;
8918 LISTVIEW_GetOrigin(infoPtr, (LPPOINT)lParam);
8921 /* case LVM_GETOUTLINECOLOR: */
8923 /* case LVM_GETSELECTEDCOLUMN: */
8925 case LVM_GETSELECTEDCOUNT:
8926 return LISTVIEW_GetSelectedCount(infoPtr);
8928 case LVM_GETSELECTIONMARK:
8929 return infoPtr->nSelectionMark;
8931 case LVM_GETSTRINGWIDTHA:
8932 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, FALSE);
8934 case LVM_GETSTRINGWIDTHW:
8935 return LISTVIEW_GetStringWidthT(infoPtr, (LPCWSTR)lParam, TRUE);
8937 case LVM_GETSUBITEMRECT:
8938 return LISTVIEW_GetSubItemRect(infoPtr, (UINT)wParam, (LPRECT)lParam);
8940 case LVM_GETTEXTBKCOLOR:
8941 return infoPtr->clrTextBk;
8943 case LVM_GETTEXTCOLOR:
8944 return infoPtr->clrText;
8946 /* case LVM_GETTILEINFO: */
8948 /* case LVM_GETTILEVIEWINFO: */
8950 case LVM_GETTOOLTIPS:
8951 return (LRESULT)infoPtr->hwndToolTip;
8953 case LVM_GETTOPINDEX:
8954 return LISTVIEW_GetTopIndex(infoPtr);
8956 /*case LVM_GETUNICODEFORMAT:
8957 FIXME("LVM_GETUNICODEFORMAT: unimplemented\n");
8960 /* case LVM_GETVIEW: */
8962 case LVM_GETVIEWRECT:
8963 return LISTVIEW_GetViewRect(infoPtr, (LPRECT)lParam);
8965 case LVM_GETWORKAREAS:
8966 FIXME("LVM_GETWORKAREAS: unimplemented\n");
8969 /* case LVM_HASGROUP: */
8972 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, FALSE, FALSE);
8974 case LVM_INSERTCOLUMNA:
8975 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
8977 case LVM_INSERTCOLUMNW:
8978 return LISTVIEW_InsertColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
8980 /* case LVM_INSERTGROUP: */
8982 /* case LVM_INSERTGROUPSORTED: */
8984 case LVM_INSERTITEMA:
8985 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
8987 case LVM_INSERTITEMW:
8988 return LISTVIEW_InsertItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
8990 /* case LVM_INSERTMARKHITTEST: */
8992 /* case LVM_ISGROUPVIEWENABLED: */
8994 /* case LVM_MAPIDTOINDEX: */
8996 /* case LVM_MAPINDEXTOID: */
8998 /* case LVM_MOVEGROUP: */
9000 /* case LVM_MOVEITEMTOGROUP: */
9002 case LVM_REDRAWITEMS:
9003 return LISTVIEW_RedrawItems(infoPtr, (INT)wParam, (INT)lParam);
9005 /* case LVM_REMOVEALLGROUPS: */
9007 /* case LVM_REMOVEGROUP: */
9010 return LISTVIEW_Scroll(infoPtr, (INT)wParam, (INT)lParam);
9012 case LVM_SETBKCOLOR:
9013 return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
9015 /* case LVM_SETBKIMAGE: */
9017 case LVM_SETCALLBACKMASK:
9018 infoPtr->uCallbackMask = (UINT)wParam;
9021 case LVM_SETCOLUMNA:
9022 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, FALSE);
9024 case LVM_SETCOLUMNW:
9025 return LISTVIEW_SetColumnT(infoPtr, (INT)wParam, (LPLVCOLUMNW)lParam, TRUE);
9027 case LVM_SETCOLUMNORDERARRAY:
9028 return LISTVIEW_SetColumnOrderArray(infoPtr, (INT)wParam, (LPINT)lParam);
9030 case LVM_SETCOLUMNWIDTH:
9031 return LISTVIEW_SetColumnWidth(infoPtr, (INT)wParam, (short)LOWORD(lParam));
9033 case LVM_SETEXTENDEDLISTVIEWSTYLE:
9034 return LISTVIEW_SetExtendedListViewStyle(infoPtr, (DWORD)wParam, (DWORD)lParam);
9036 /* case LVM_SETGROUPINFO: */
9038 /* case LVM_SETGROUPMETRICS: */
9040 case LVM_SETHOTCURSOR:
9041 return (LRESULT)LISTVIEW_SetHotCursor(infoPtr, (HCURSOR)lParam);
9043 case LVM_SETHOTITEM:
9044 return LISTVIEW_SetHotItem(infoPtr, (INT)wParam);
9046 case LVM_SETHOVERTIME:
9047 return LISTVIEW_SetHoverTime(infoPtr, (DWORD)wParam);
9049 case LVM_SETICONSPACING:
9050 return LISTVIEW_SetIconSpacing(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9052 case LVM_SETIMAGELIST:
9053 return (LRESULT)LISTVIEW_SetImageList(infoPtr, (INT)wParam, (HIMAGELIST)lParam);
9055 /* case LVM_SETINFOTIP: */
9057 /* case LVM_SETINSERTMARK: */
9059 /* case LVM_SETINSERTMARKCOLOR: */
9062 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, FALSE);
9065 return LISTVIEW_SetItemT(infoPtr, (LPLVITEMW)lParam, TRUE);
9067 case LVM_SETITEMCOUNT:
9068 return LISTVIEW_SetItemCount(infoPtr, (INT)wParam, (DWORD)lParam);
9070 case LVM_SETITEMPOSITION:
9073 pt.x = (short)LOWORD(lParam);
9074 pt.y = (short)HIWORD(lParam);
9075 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, pt);
9078 case LVM_SETITEMPOSITION32:
9079 if (lParam == 0) return FALSE;
9080 return LISTVIEW_SetItemPosition(infoPtr, (INT)wParam, *((POINT*)lParam));
9082 case LVM_SETITEMSTATE:
9083 return LISTVIEW_SetItemState(infoPtr, (INT)wParam, (LPLVITEMW)lParam);
9085 case LVM_SETITEMTEXTA:
9086 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, FALSE);
9088 case LVM_SETITEMTEXTW:
9089 return LISTVIEW_SetItemTextT(infoPtr, (INT)wParam, (LPLVITEMW)lParam, TRUE);
9091 /* case LVM_SETOUTLINECOLOR: */
9093 /* case LVM_SETSELECTEDCOLUMN: */
9095 case LVM_SETSELECTIONMARK:
9096 return LISTVIEW_SetSelectionMark(infoPtr, (INT)lParam);
9098 case LVM_SETTEXTBKCOLOR:
9099 return LISTVIEW_SetTextBkColor(infoPtr, (COLORREF)lParam);
9101 case LVM_SETTEXTCOLOR:
9102 return LISTVIEW_SetTextColor(infoPtr, (COLORREF)lParam);
9104 /* case LVM_SETTILEINFO: */
9106 /* case LVM_SETTILEVIEWINFO: */
9108 /* case LVM_SETTILEWIDTH: */
9110 case LVM_SETTOOLTIPS:
9111 return (LRESULT)LISTVIEW_SetToolTips(infoPtr, (HWND)lParam);
9113 /* case LVM_SETUNICODEFORMAT: */
9115 /* case LVM_SETVIEW: */
9117 /* case LVM_SETWORKAREAS: */
9119 /* case LVM_SORTGROUPS: */
9122 return LISTVIEW_SortItems(infoPtr, (PFNLVCOMPARE)lParam, (LPARAM)wParam);
9124 /* LVM_SORTITEMSEX: */
9126 case LVM_SUBITEMHITTEST:
9127 return LISTVIEW_HitTest(infoPtr, (LPLVHITTESTINFO)lParam, TRUE, FALSE);
9130 return LISTVIEW_Update(infoPtr, (INT)wParam);
9133 return LISTVIEW_ProcessLetterKeys( infoPtr, wParam, lParam );
9136 return LISTVIEW_Command(infoPtr, wParam, lParam);
9139 return LISTVIEW_Create(hwnd, (LPCREATESTRUCTW)lParam);
9142 return LISTVIEW_EraseBkgnd(infoPtr, (HDC)wParam);
9145 return DLGC_WANTCHARS | DLGC_WANTARROWS;
9148 return (LRESULT)infoPtr->hFont;
9151 return LISTVIEW_HScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9154 return LISTVIEW_KeyDown(infoPtr, (INT)wParam, (LONG)lParam);
9157 return LISTVIEW_KillFocus(infoPtr);
9159 case WM_LBUTTONDBLCLK:
9160 return LISTVIEW_LButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9162 case WM_LBUTTONDOWN:
9163 return LISTVIEW_LButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9166 return LISTVIEW_LButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9169 return LISTVIEW_MouseMove (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9172 return LISTVIEW_MouseHover(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9175 return LISTVIEW_NCDestroy(infoPtr);
9178 if (lParam && ((LPNMHDR)lParam)->hwndFrom == infoPtr->hwndHeader)
9179 return LISTVIEW_HeaderNotification(infoPtr, (LPNMHEADERW)lParam);
9182 case WM_NOTIFYFORMAT:
9183 return LISTVIEW_NotifyFormat(infoPtr, (HWND)wParam, (INT)lParam);
9186 return LISTVIEW_Paint(infoPtr, (HDC)wParam);
9188 case WM_RBUTTONDBLCLK:
9189 return LISTVIEW_RButtonDblClk(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9191 case WM_RBUTTONDOWN:
9192 return LISTVIEW_RButtonDown(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9195 return LISTVIEW_RButtonUp(infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
9198 if(LISTVIEW_SetCursor(infoPtr, (HWND)wParam, LOWORD(lParam), HIWORD(lParam)))
9203 return LISTVIEW_SetFocus(infoPtr, (HWND)wParam);
9206 return LISTVIEW_SetFont(infoPtr, (HFONT)wParam, (WORD)lParam);
9209 return LISTVIEW_SetRedraw(infoPtr, (BOOL)wParam);
9212 return LISTVIEW_Size(infoPtr, (short)LOWORD(lParam), (short)HIWORD(lParam));
9214 case WM_STYLECHANGED:
9215 return LISTVIEW_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
9217 case WM_SYSCOLORCHANGE:
9218 COMCTL32_RefreshSysColors();
9221 /* case WM_TIMER: */
9224 return LISTVIEW_VScroll(infoPtr, (INT)LOWORD(wParam), 0, (HWND)lParam);
9227 if (wParam & (MK_SHIFT | MK_CONTROL))
9228 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9229 return LISTVIEW_MouseWheel(infoPtr, (short int)HIWORD(wParam));
9231 case WM_WINDOWPOSCHANGED:
9232 if (!(((WINDOWPOS *)lParam)->flags & SWP_NOSIZE))
9234 SetWindowPos(infoPtr->hwndSelf, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
9235 SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);
9236 LISTVIEW_UpdateSize(infoPtr);
9237 LISTVIEW_UpdateScroll(infoPtr);
9239 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9241 /* case WM_WININICHANGE: */
9244 if ((uMsg >= WM_USER) && (uMsg < WM_APP))
9245 ERR("unknown msg %04x wp=%08x lp=%08lx\n", uMsg, wParam, lParam);
9248 /* call default window procedure */
9249 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
9257 * Registers the window class.
9265 void LISTVIEW_Register(void)
9269 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
9270 wndClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
9271 wndClass.lpfnWndProc = (WNDPROC)LISTVIEW_WindowProc;
9272 wndClass.cbClsExtra = 0;
9273 wndClass.cbWndExtra = sizeof(LISTVIEW_INFO *);
9274 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
9275 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
9276 wndClass.lpszClassName = WC_LISTVIEWW;
9277 RegisterClassW(&wndClass);
9282 * Unregisters the window class.
9290 void LISTVIEW_Unregister(void)
9292 UnregisterClassW(WC_LISTVIEWW, NULL);
9297 * Handle any WM_COMMAND messages
9300 * [I] infoPtr : valid pointer to the listview structure
9301 * [I] wParam : the first message parameter
9302 * [I] lParam : the second message parameter
9307 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
9309 switch (HIWORD(wParam))
9314 * Adjust the edit window size
9317 HDC hdc = GetDC(infoPtr->hwndEdit);
9318 HFONT hFont, hOldFont = 0;
9323 if (!infoPtr->hwndEdit || !hdc) return 0;
9324 len = GetWindowTextW(infoPtr->hwndEdit, buffer, sizeof(buffer)/sizeof(buffer[0]));
9325 GetWindowRect(infoPtr->hwndEdit, &rect);
9327 /* Select font to get the right dimension of the string */
9328 hFont = (HFONT)SendMessageW(infoPtr->hwndEdit, WM_GETFONT, 0, 0);
9331 hOldFont = SelectObject(hdc, hFont);
9334 if (GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &sz))
9336 TEXTMETRICW textMetric;
9338 /* Add Extra spacing for the next character */
9339 GetTextMetricsW(hdc, &textMetric);
9340 sz.cx += (textMetric.tmMaxCharWidth * 2);
9348 rect.bottom - rect.top,
9349 SWP_DRAWFRAME|SWP_NOMOVE);
9352 SelectObject(hdc, hOldFont);
9354 ReleaseDC(infoPtr->hwndSelf, hdc);
9360 return SendMessageW (infoPtr->hwndNotify, WM_COMMAND, wParam, lParam);
9369 * Subclassed edit control windproc function
9372 * [I] hwnd : the edit window handle
9373 * [I] uMsg : the message that is to be processed
9374 * [I] wParam : first message parameter
9375 * [I] lParam : second message parameter
9376 * [I] isW : TRUE if input is Unicode
9381 static LRESULT EditLblWndProcT(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL isW)
9383 LISTVIEW_INFO *infoPtr = (LISTVIEW_INFO *)GetWindowLongW(GetParent(hwnd), 0);
9384 BOOL cancel = FALSE;
9386 TRACE("(hwnd=%p, uMsg=%x, wParam=%x, lParam=%lx, isW=%d)\n",
9387 hwnd, uMsg, wParam, lParam, isW);
9392 return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
9399 WNDPROC editProc = infoPtr->EditWndProc;
9400 infoPtr->EditWndProc = 0;
9401 SetWindowLongW(hwnd, GWL_WNDPROC, (LONG)editProc);
9402 return CallWindowProcT(editProc, hwnd, uMsg, wParam, lParam, isW);
9406 if (VK_ESCAPE == (INT)wParam)
9411 else if (VK_RETURN == (INT)wParam)
9415 return CallWindowProcT(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam, isW);
9419 if (infoPtr->hwndEdit)
9421 LPWSTR buffer = NULL;
9423 infoPtr->hwndEdit = 0;
9426 DWORD len = isW ? GetWindowTextLengthW(hwnd) : GetWindowTextLengthA(hwnd);
9430 if ( (buffer = Alloc((len+1) * (isW ? sizeof(WCHAR) : sizeof(CHAR)))) )
9432 if (isW) GetWindowTextW(hwnd, buffer, len+1);
9433 else GetWindowTextA(hwnd, (CHAR*)buffer, len+1);
9437 LISTVIEW_EndEditLabelT(infoPtr, buffer, isW);
9439 if (buffer) Free(buffer);
9443 SendMessageW(hwnd, WM_CLOSE, 0, 0);
9449 * Subclassed edit control Unicode windproc function
9452 * [I] hwnd : the edit window handle
9453 * [I] uMsg : the message that is to be processed
9454 * [I] wParam : first message parameter
9455 * [I] lParam : second message parameter
9459 LRESULT CALLBACK EditLblWndProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9461 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, TRUE);
9466 * Subclassed edit control ANSI windproc function
9469 * [I] hwnd : the edit window handle
9470 * [I] uMsg : the message that is to be processed
9471 * [I] wParam : first message parameter
9472 * [I] lParam : second message parameter
9476 LRESULT CALLBACK EditLblWndProcA(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
9478 return EditLblWndProcT(hwnd, uMsg, wParam, lParam, FALSE);
9483 * Creates a subclassed edit cotrol
9486 * [I] infoPtr : valid pointer to the listview structure
9487 * [I] text : initial text for the edit
9488 * [I] style : the window style
9489 * [I] isW : TRUE if input is Unicode
9493 static HWND CreateEditLabelT(LISTVIEW_INFO *infoPtr, LPCWSTR text, DWORD style,
9494 INT x, INT y, INT width, INT height, BOOL isW)
9496 WCHAR editName[5] = { 'E', 'd', 'i', 't', '\0' };
9501 TEXTMETRICW textMetric;
9502 HINSTANCE hinst = (HINSTANCE)GetWindowLongW(infoPtr->hwndSelf, GWL_HINSTANCE);
9504 TRACE("(text=%s, ..., isW=%d)\n", debugtext_t(text, isW), isW);
9506 style |= WS_CHILDWINDOW|WS_CLIPSIBLINGS|ES_LEFT|WS_BORDER;
9507 hdc = GetDC(infoPtr->hwndSelf);
9509 /* Select the font to get appropriate metric dimensions */
9510 if(infoPtr->hFont != 0)
9511 hOldFont = SelectObject(hdc, infoPtr->hFont);
9513 /*Get String Length in pixels */
9514 GetTextExtentPoint32W(hdc, text, lstrlenW(text), &sz);
9516 /*Add Extra spacing for the next character */
9517 GetTextMetricsW(hdc, &textMetric);
9518 sz.cx += (textMetric.tmMaxCharWidth * 2);
9520 if(infoPtr->hFont != 0)
9521 SelectObject(hdc, hOldFont);
9523 ReleaseDC(infoPtr->hwndSelf, hdc);
9525 hedit = CreateWindowW(editName, text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9527 hedit = CreateWindowA("Edit", (LPCSTR)text, style, x, y, sz.cx, height, infoPtr->hwndSelf, 0, hinst, 0);
9529 if (!hedit) return 0;
9531 infoPtr->EditWndProc = (WNDPROC)
9532 (isW ? SetWindowLongW(hedit, GWL_WNDPROC, (LONG)EditLblWndProcW) :
9533 SetWindowLongA(hedit, GWL_WNDPROC, (LONG)EditLblWndProcA) );
9535 SendMessageW(hedit, WM_SETFONT, (WPARAM)infoPtr->hFont, FALSE);