ReactOS  r76032
listview.c
Go to the documentation of this file.
1 /*
2  * Listview control
3  *
4  * Copyright 1998, 1999 Eric Kohl
5  * Copyright 1999 Luc Tourangeau
6  * Copyright 2000 Jason Mawdsley
7  * Copyright 2001 CodeWeavers Inc.
8  * Copyright 2002 Dimitrie O. Paun
9  * Copyright 2009-2015 Nikolay Sivov
10  * Copyright 2009 Owen Rudge for CodeWeavers
11  * Copyright 2012-2013 Daniel Jelinski
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2.1 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26  *
27  * NOTES
28  *
29  * This code was audited for completeness against the documented features
30  * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
31  *
32  * Unless otherwise noted, we believe this code to be complete, as per
33  * the specification mentioned above.
34  * If you discover missing features, or bugs, please note them below.
35  *
36  * TODO:
37  *
38  * Default Message Processing
39  * -- WM_CREATE: create the icon and small icon image lists at this point only if
40  * the LVS_SHAREIMAGELISTS style is not specified.
41  * -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
42  * or small icon and the LVS_AUTOARRANGE style is specified.
43  * -- WM_TIMER
44  * -- WM_WININICHANGE
45  *
46  * Features
47  * -- Hot item handling, mouse hovering
48  * -- Workareas support
49  * -- Tilemode support
50  * -- Groups support
51  *
52  * Bugs
53  * -- Expand large item in ICON mode when the cursor is flying over the icon or text.
54  * -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
55  * -- LVA_SNAPTOGRID not implemented
56  * -- LISTVIEW_ApproximateViewRect partially implemented
57  * -- LISTVIEW_StyleChanged doesn't handle some changes too well
58  *
59  * Speedups
60  * -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
61  * linear in the number of items in the list, and this is
62  * unacceptable for large lists.
63  * -- if list is sorted by item text LISTVIEW_InsertItemT could use
64  * binary search to calculate item index (e.g. DPA_Search()).
65  * This requires sorted state to be reliably tracked in item modifiers.
66  * -- we should keep an ordered array of coordinates in iconic mode.
67  * This would allow framing items (iterator_frameditems),
68  * and finding the nearest item (LVFI_NEARESTXY) a lot more efficiently.
69  *
70  * Flags
71  * -- LVIF_COLUMNS
72  * -- LVIF_GROUPID
73  *
74  * States
75  * -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
76  * -- LVIS_DROPHILITED
77  *
78  * Styles
79  * -- LVS_NOLABELWRAP
80  * -- LVS_NOSCROLL (see Q137520)
81  * -- LVS_ALIGNTOP
82  *
83  * Extended Styles
84  * -- LVS_EX_BORDERSELECT
85  * -- LVS_EX_FLATSB
86  * -- LVS_EX_INFOTIP
87  * -- LVS_EX_LABELTIP
88  * -- LVS_EX_MULTIWORKAREAS
89  * -- LVS_EX_REGIONAL
90  * -- LVS_EX_SIMPLESELECT
91  * -- LVS_EX_TWOCLICKACTIVATE
92  * -- LVS_EX_UNDERLINECOLD
93  * -- LVS_EX_UNDERLINEHOT
94  *
95  * Notifications:
96  * -- LVN_BEGINSCROLL, LVN_ENDSCROLL
97  * -- LVN_GETINFOTIP
98  * -- LVN_HOTTRACK
99  * -- LVN_SETDISPINFO
100  *
101  * Messages:
102  * -- LVM_ENABLEGROUPVIEW
103  * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
104  * -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
105  * -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
106  * -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
107  * -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
108  * -- LVM_GETINSERTMARKRECT
109  * -- LVM_GETNUMBEROFWORKAREAS
110  * -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
111  * -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
112  * -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
113  * -- LVM_GETTILEINFO, LVM_SETTILEINFO
114  * -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
115  * -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
116  * -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
117  * -- LVM_INSERTGROUPSORTED
118  * -- LVM_INSERTMARKHITTEST
119  * -- LVM_ISGROUPVIEWENABLED
120  * -- LVM_MOVEGROUP
121  * -- LVM_MOVEITEMTOGROUP
122  * -- LVM_SETINFOTIP
123  * -- LVM_SETTILEWIDTH
124  * -- LVM_SORTGROUPS
125  *
126  * Macros:
127  * -- ListView_GetHoverTime, ListView_SetHoverTime
128  * -- ListView_GetISearchString
129  * -- ListView_GetNumberOfWorkAreas
130  * -- ListView_GetWorkAreas, ListView_SetWorkAreas
131  *
132  * Functions:
133  * -- LVGroupComparE
134  */
135 
136 #include "comctl32.h"
137 
138 #include <stdio.h>
139 
141 
142 typedef struct tagCOLUMN_INFO
143 {
144  RECT rcHeader; /* tracks the header's rectangle */
145  INT fmt; /* same as LVCOLUMN.fmt */
147 } COLUMN_INFO;
148 
149 typedef struct tagITEMHDR
150 {
153 } ITEMHDR, *LPITEMHDR;
154 
155 typedef struct tagSUBITEM_INFO
156 {
159 } SUBITEM_INFO;
160 
161 typedef struct tagITEM_ID ITEM_ID;
162 
163 typedef struct tagITEM_INFO
164 {
170 } ITEM_INFO;
171 
173 {
174  UINT id; /* item id */
175  HDPA item; /* link to item data */
176 };
177 
178 typedef struct tagRANGE
179 {
182 } RANGE;
183 
184 typedef struct tagRANGES
185 {
187 } *RANGES;
188 
189 typedef struct tagITERATOR
190 {
196 } ITERATOR;
197 
198 typedef struct tagDELAYED_ITEM_EDIT
199 {
203 
204 typedef struct tagLISTVIEW_INFO
205 {
206  /* control window */
208  RECT rcList; /* This rectangle is really the window
209  * client rectangle possibly reduced by the
210  * horizontal scroll bar and/or header - see
211  * LISTVIEW_UpdateSize. This rectangle offset
212  * by the LISTVIEW_GetOrigin value is in
213  * client coordinates */
214 
215  /* notification window */
218  BOOL bDoChangeNotify; /* send change notification messages? */
220 
221  /* tooltips */
223 
224  /* items */
225  INT nItemCount; /* the number of items in the list */
226  HDPA hdpaItems; /* array ITEM_INFO pointers */
227  HDPA hdpaItemIds; /* array of ITEM_ID pointers */
228  HDPA hdpaPosX; /* maintains the (X, Y) coordinates of the */
229  HDPA hdpaPosY; /* items in LVS_ICON, and LVS_SMALLICON modes */
231  INT nSelectionMark; /* item to start next multiselection from */
233 
234  /* columns */
235  HDPA hdpaColumns; /* array of COLUMN_INFO pointers */
236  BOOL colRectsDirty; /* trigger column rectangles requery from header */
237 
238  /* item metrics */
239  BOOL bNoItemMetrics; /* flags if item metrics are not yet computed */
242 
243  /* sorting */
244  PFNLVCOMPARE pfnCompare; /* sorting callback pointer */
246 
247  /* style */
248  DWORD dwStyle; /* the cached window GWL_STYLE */
249  DWORD dwLvExStyle; /* extended listview style */
250  DWORD uView; /* current view available through LVM_[G,S]ETVIEW */
251 
252  /* edit item */
256  DELAYED_ITEM_EDIT itemEdit; /* Pointer to this structure will be the timer ID */
257 
258  /* icons */
266  POINT currIconPos; /* this is the position next icon will be placed */
267 
268  /* header */
270  INT xTrackLine; /* The x coefficient of the track line or -1 if none */
271 
272  /* marquee selection */
273  BOOL bMarqueeSelect; /* marquee selection/highlight underway */
275  RECT marqueeRect; /* absolute coordinates of marquee selection */
276  RECT marqueeDrawRect; /* relative coordinates for drawing marquee */
277  POINT marqueeOrigin; /* absolute coordinates of marquee click origin */
278 
279  /* focus drawing */
280  BOOL bFocus; /* control has focus */
282  RECT rcFocus; /* focus bounds */
283 
284  /* colors */
285  HBRUSH hBkBrush;
289 #ifdef __REACTOS__
290  BOOL bDefaultBkColor;
291 #endif
292 
293  /* font */
296  INT ntmHeight; /* Some cached metrics of the font used */
297  INT ntmMaxCharWidth; /* by the listview to draw items */
299 
300  /* mouse operation */
303  POINT ptClickPos; /* point where the user clicked */
304  INT nLButtonDownItem; /* tracks item to reset multiselection on WM_LBUTTONUP */
308 
309  /* keyboard operation */
314 
315  /* painting */
316  BOOL bIsDrawing; /* Drawing in progress */
317  INT nMeasureItemHeight; /* WM_MEASUREITEM result */
318  BOOL redraw; /* WM_SETREDRAW switch */
319 
320  /* misc */
321  DWORD iVersion; /* CCM_[G,S]ETVERSION */
322 } LISTVIEW_INFO;
323 
324 /*
325  * constants
326  */
327 /* How many we debug buffer to allocate */
328 #define DEBUG_BUFFERS 20
329 /* The size of a single debug buffer */
330 #define DEBUG_BUFFER_SIZE 256
331 
332 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
333 #define SB_INTERNAL -1
334 
335 /* maximum size of a label */
336 #define DISP_TEXT_SIZE 260
337 
338 /* padding for items in list and small icon display modes */
339 #define WIDTH_PADDING 12
340 
341 /* padding for items in list, report and small icon display modes */
342 #define HEIGHT_PADDING 1
343 
344 /* offset of items in report display mode */
345 #define REPORT_MARGINX 2
346 
347 /* padding for icon in large icon display mode
348  * ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
349  * that HITTEST will see.
350  * ICON_TOP_PADDING_HITABLE - spacing between above and icon.
351  * ICON_TOP_PADDING - sum of the two above.
352  * ICON_BOTTOM_PADDING - between bottom of icon and top of text
353  * LABEL_HOR_PADDING - between text and sides of box
354  * LABEL_VERT_PADDING - between bottom of text and end of box
355  *
356  * ICON_LR_PADDING - additional width above icon size.
357  * ICON_LR_HALF - half of the above value
358  */
359 #define ICON_TOP_PADDING_NOTHITABLE 2
360 #define ICON_TOP_PADDING_HITABLE 2
361 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
362 #define ICON_BOTTOM_PADDING 4
363 #define LABEL_HOR_PADDING 5
364 #define LABEL_VERT_PADDING 7
365 #define ICON_LR_PADDING 16
366 #define ICON_LR_HALF (ICON_LR_PADDING/2)
367 
368 /* default label width for items in list and small icon display modes */
369 #define DEFAULT_LABEL_WIDTH 40
370 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
371 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
372 
373 /* default column width for items in list display mode */
374 #define DEFAULT_COLUMN_WIDTH 128
375 
376 /* Size of "line" scroll for V & H scrolls */
377 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
378 
379 /* Padding between image and label */
380 #define IMAGE_PADDING 2
381 
382 /* Padding behind the label */
383 #define TRAILING_LABEL_PADDING 12
384 #define TRAILING_HEADER_PADDING 11
385 
386 /* Border for the icon caption */
387 #define CAPTION_BORDER 2
388 
389 /* Standard DrawText flags */
390 #define LV_ML_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
391 #define LV_FL_DT_FLAGS (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
392 #define LV_SL_DT_FLAGS (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
393 
394 /* Image index from state */
395 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
396 
397 /* The time in milliseconds to reset the search in the list */
398 #define KEY_DELAY 450
399 
400 /* Dump the LISTVIEW_INFO structure to the debug channel */
401 #define LISTVIEW_DUMP(iP) do { \
402  TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
403  iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
404  iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
405  TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
406  iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
407  iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
408  TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
409  iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
410  iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
411  TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
412 } while(0)
413 
414 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
415 
416 /*
417  * forward declarations
418  */
420 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
421 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
424 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
426 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
439 
440 /******** Text handling functions *************************************/
441 
442 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
443  * text string. The string may be ANSI or Unicode, in which case
444  * the boolean isW tells us the type of the string.
445  *
446  * The name of the function tell what type of strings it expects:
447  * W: Unicode, T: ANSI/Unicode - function of isW
448  */
449 
450 static inline BOOL is_text(LPCWSTR text)
451 {
452  return text != NULL && text != LPSTR_TEXTCALLBACKW;
453 }
454 
455 static inline int textlenT(LPCWSTR text, BOOL isW)
456 {
457  return !is_text(text) ? 0 :
458  isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
459 }
460 
461 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
462 {
463  if (isDestW)
464  if (isSrcW) lstrcpynW(dest, src, max);
465  else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
466  else
467  if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
468  else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
469 }
470 
472 {
473  LPWSTR wstr = (LPWSTR)text;
474 
475  if (!isW && is_text(text))
476  {
477  INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
478  wstr = Alloc(len * sizeof(WCHAR));
479  if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
480  }
481  TRACE(" wstr=%s\n", text == LPSTR_TEXTCALLBACKW ? "(callback)" : debugstr_w(wstr));
482  return wstr;
483 }
484 
485 static inline void textfreeT(LPWSTR wstr, BOOL isW)
486 {
487  if (!isW && is_text(wstr)) Free (wstr);
488 }
489 
490 /*
491  * dest is a pointer to a Unicode string
492  * src is a pointer to a string (Unicode if isW, ANSI if !isW)
493  */
495 {
496  BOOL bResult = TRUE;
497 
498  if (src == LPSTR_TEXTCALLBACKW)
499  {
500  if (is_text(*dest)) Free(*dest);
501  *dest = LPSTR_TEXTCALLBACKW;
502  }
503  else
504  {
505  LPWSTR pszText = textdupTtoW(src, isW);
506  if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
507  bResult = Str_SetPtrW(dest, pszText);
508  textfreeT(pszText, isW);
509  }
510  return bResult;
511 }
512 
513 /*
514  * compares a Unicode to a Unicode/ANSI text string
515  */
516 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
517 {
518  if (!aw) return bt ? -1 : 0;
519  if (!bt) return 1;
520  if (aw == LPSTR_TEXTCALLBACKW)
521  return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
522  if (bt != LPSTR_TEXTCALLBACKW)
523  {
524  LPWSTR bw = textdupTtoW(bt, isW);
525  int r = bw ? lstrcmpW(aw, bw) : 1;
526  textfreeT(bw, isW);
527  return r;
528  }
529 
530  return 1;
531 }
532 
533 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
534 {
535  n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
537 }
538 
539 /******** Debugging functions *****************************************/
540 
542 {
543  if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
544  return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
545 }
546 
548 {
549  if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
550  n = min(textlenT(text, isW), n);
551  return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
552 }
553 
554 static char* debug_getbuf(void)
555 {
556  static int index = 0;
558  return buffers[index++ % DEBUG_BUFFERS];
559 }
560 
561 static inline const char* debugrange(const RANGE *lprng)
562 {
563  if (!lprng) return "(null)";
564  return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
565 }
566 
567 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
568 {
569  char* buf = debug_getbuf(), *text = buf;
570  int len, size = DEBUG_BUFFER_SIZE;
571 
572  if (pScrollInfo == NULL) return "(null)";
573  len = snprintf(buf, size, "{cbSize=%u, ", pScrollInfo->cbSize);
574  if (len == -1) goto end;
575  buf += len; size -= len;
576  if (pScrollInfo->fMask & SIF_RANGE)
577  len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
578  else len = 0;
579  if (len == -1) goto end;
580  buf += len; size -= len;
581  if (pScrollInfo->fMask & SIF_PAGE)
582  len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
583  else len = 0;
584  if (len == -1) goto end;
585  buf += len; size -= len;
586  if (pScrollInfo->fMask & SIF_POS)
587  len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
588  else len = 0;
589  if (len == -1) goto end;
590  buf += len; size -= len;
591  if (pScrollInfo->fMask & SIF_TRACKPOS)
592  len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
593  else len = 0;
594  if (len == -1) goto end;
595  buf += len;
596  goto undo;
597 end:
598  buf = text + strlen(text);
599 undo:
600  if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
601  return text;
602 }
603 
604 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
605 {
606  if (!plvnm) return "(null)";
607  return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
608  " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
609  plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
610  plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
611 }
612 
613 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
614 {
615  char* buf = debug_getbuf(), *text = buf;
616  int len, size = DEBUG_BUFFER_SIZE;
617 
618  if (lpLVItem == NULL) return "(null)";
619  len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
620  if (len == -1) goto end;
621  buf += len; size -= len;
622  if (lpLVItem->mask & LVIF_STATE)
623  len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
624  else len = 0;
625  if (len == -1) goto end;
626  buf += len; size -= len;
627  if (lpLVItem->mask & LVIF_TEXT)
628  len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
629  else len = 0;
630  if (len == -1) goto end;
631  buf += len; size -= len;
632  if (lpLVItem->mask & LVIF_IMAGE)
633  len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
634  else len = 0;
635  if (len == -1) goto end;
636  buf += len; size -= len;
637  if (lpLVItem->mask & LVIF_PARAM)
638  len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
639  else len = 0;
640  if (len == -1) goto end;
641  buf += len; size -= len;
642  if (lpLVItem->mask & LVIF_INDENT)
643  len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
644  else len = 0;
645  if (len == -1) goto end;
646  buf += len;
647  goto undo;
648 end:
649  buf = text + strlen(text);
650 undo:
651  if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
652  return text;
653 }
654 
655 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
656 {
657  char* buf = debug_getbuf(), *text = buf;
658  int len, size = DEBUG_BUFFER_SIZE;
659 
660  if (lpColumn == NULL) return "(null)";
661  len = snprintf(buf, size, "{");
662  if (len == -1) goto end;
663  buf += len; size -= len;
664  if (lpColumn->mask & LVCF_SUBITEM)
665  len = snprintf(buf, size, "iSubItem=%d, ", lpColumn->iSubItem);
666  else len = 0;
667  if (len == -1) goto end;
668  buf += len; size -= len;
669  if (lpColumn->mask & LVCF_FMT)
670  len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
671  else len = 0;
672  if (len == -1) goto end;
673  buf += len; size -= len;
674  if (lpColumn->mask & LVCF_WIDTH)
675  len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
676  else len = 0;
677  if (len == -1) goto end;
678  buf += len; size -= len;
679  if (lpColumn->mask & LVCF_TEXT)
680  len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
681  else len = 0;
682  if (len == -1) goto end;
683  buf += len; size -= len;
684  if (lpColumn->mask & LVCF_IMAGE)
685  len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
686  else len = 0;
687  if (len == -1) goto end;
688  buf += len; size -= len;
689  if (lpColumn->mask & LVCF_ORDER)
690  len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
691  else len = 0;
692  if (len == -1) goto end;
693  buf += len;
694  goto undo;
695 end:
696  buf = text + strlen(text);
697 undo:
698  if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
699  return text;
700 }
701 
702 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
703 {
704  if (!lpht) return "(null)";
705 
706  return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
707  wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
708 }
709 
710 /* Return the corresponding text for a given scroll value */
711 static inline LPCSTR debugscrollcode(int nScrollCode)
712 {
713  switch(nScrollCode)
714  {
715  case SB_LINELEFT: return "SB_LINELEFT";
716  case SB_LINERIGHT: return "SB_LINERIGHT";
717  case SB_PAGELEFT: return "SB_PAGELEFT";
718  case SB_PAGERIGHT: return "SB_PAGERIGHT";
719  case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
720  case SB_THUMBTRACK: return "SB_THUMBTRACK";
721  case SB_ENDSCROLL: return "SB_ENDSCROLL";
722  case SB_INTERNAL: return "SB_INTERNAL";
723  default: return "unknown";
724  }
725 }
726 
727 
728 /******** Notification functions ************************************/
729 
730 static int get_ansi_notification(UINT unicodeNotificationCode)
731 {
732  switch (unicodeNotificationCode)
733  {
734  case LVN_BEGINLABELEDITA:
736  case LVN_ENDLABELEDITA:
738  case LVN_GETDISPINFOA:
739  case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
740  case LVN_SETDISPINFOA:
741  case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
742  case LVN_ODFINDITEMA:
743  case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
744  case LVN_GETINFOTIPA:
745  case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
746  /* header forwards */
747  case HDN_TRACKA:
748  case HDN_TRACKW: return HDN_TRACKA;
749  case HDN_ENDTRACKA:
750  case HDN_ENDTRACKW: return HDN_ENDTRACKA;
751  case HDN_BEGINDRAG: return HDN_BEGINDRAG;
752  case HDN_ENDDRAG: return HDN_ENDDRAG;
753  case HDN_ITEMCHANGINGA:
755  case HDN_ITEMCHANGEDA:
756  case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
757  case HDN_ITEMCLICKA:
758  case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
761  default: break;
762  }
763  FIXME("unknown notification %x\n", unicodeNotificationCode);
764  return unicodeNotificationCode;
765 }
766 
767 /* forwards header notifications to listview parent */
768 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, NMHEADERW *lpnmhW)
769 {
770  LPCWSTR text = NULL, filter = NULL;
771  LRESULT ret;
772  NMHEADERA *lpnmh = (NMHEADERA*) lpnmhW;
773 
774  /* on unicode format exit earlier */
775  if (infoPtr->notifyFormat == NFR_UNICODE)
776  return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
777  (LPARAM)lpnmh);
778 
779  /* header always supplies unicode notifications,
780  all we have to do is to convert strings to ANSI */
781  if (lpnmh->pitem)
782  {
783  /* convert item text */
784  if (lpnmh->pitem->mask & HDI_TEXT)
785  {
786  text = (LPCWSTR)lpnmh->pitem->pszText;
787  lpnmh->pitem->pszText = NULL;
788  Str_SetPtrWtoA(&lpnmh->pitem->pszText, text);
789  }
790  /* convert filter text */
791  if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
792  lpnmh->pitem->pvFilter)
793  {
794  filter = (LPCWSTR)((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText;
795  ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = NULL;
796  Str_SetPtrWtoA(&((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText, filter);
797  }
798  }
799  lpnmh->hdr.code = get_ansi_notification(lpnmh->hdr.code);
800 
801  ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, lpnmh->hdr.idFrom,
802  (LPARAM)lpnmh);
803 
804  /* cleanup */
805  if(text)
806  {
807  Free(lpnmh->pitem->pszText);
808  lpnmh->pitem->pszText = (LPSTR)text;
809  }
810  if(filter)
811  {
812  Free(((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText);
813  ((HD_TEXTFILTERA*)lpnmh->pitem->pvFilter)->pszText = (LPSTR)filter;
814  }
815 
816  return ret;
817 }
818 
819 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
820 {
821  LRESULT result;
822 
823  TRACE("(code=%d)\n", code);
824 
825  pnmh->hwndFrom = infoPtr->hwndSelf;
826  pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
827  pnmh->code = code;
828  result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
829 
830  TRACE(" <= %ld\n", result);
831 
832  return result;
833 }
834 
835 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
836 {
837  NMHDR nmh;
838  HWND hwnd = infoPtr->hwndSelf;
839  notify_hdr(infoPtr, code, &nmh);
840  return IsWindow(hwnd);
841 }
842 
843 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
844 {
845  NMITEMACTIVATE nmia;
846  LVITEMW item;
847 
848  nmia.uNewState = 0;
849  nmia.uOldState = 0;
850  nmia.uChanged = 0;
851  nmia.uKeyFlags = 0;
852 
853  item.mask = LVIF_PARAM|LVIF_STATE;
854  item.iItem = htInfo->iItem;
855  item.iSubItem = 0;
856  item.stateMask = (UINT)-1;
857  if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
858  nmia.lParam = item.lParam;
859  nmia.uOldState = item.state;
860  nmia.uNewState = item.state | LVIS_ACTIVATING;
861  nmia.uChanged = LVIF_STATE;
862  }
863 
864  nmia.iItem = htInfo->iItem;
865  nmia.iSubItem = htInfo->iSubItem;
866  nmia.ptAction = htInfo->pt;
867 
868  if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
869  if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
870  if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
871 
872  notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
873 }
874 
875 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
876 {
877  TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
878  return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
879 }
880 
881 /* Handles NM_DBLCLK, NM_CLICK, NM_RDBLCLK, NM_RCLICK. Only NM_RCLICK return value is used. */
882 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
883 {
884  NMITEMACTIVATE nmia;
885  LVITEMW item;
886  HWND hwnd = infoPtr->hwndSelf;
887  LRESULT ret;
888 
889  TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht));
890  ZeroMemory(&nmia, sizeof(nmia));
891  nmia.iItem = lvht->iItem;
892  nmia.iSubItem = lvht->iSubItem;
893  nmia.ptAction = lvht->pt;
894  item.mask = LVIF_PARAM;
895  item.iItem = lvht->iItem;
896  item.iSubItem = 0;
897  if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
898  ret = notify_hdr(infoPtr, code, (NMHDR*)&nmia);
899  return IsWindow(hwnd) && (code == NM_RCLICK ? !ret : TRUE);
900 }
901 
902 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
903 {
904  NMLISTVIEW nmlv;
905  LVITEMW item;
906  HWND hwnd = infoPtr->hwndSelf;
907 
908  ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
909  nmlv.iItem = nItem;
910  item.mask = LVIF_PARAM;
911  item.iItem = nItem;
912  item.iSubItem = 0;
913  if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
914  notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
915  return IsWindow(hwnd);
916 }
917 
918 /*
919  Send notification. depends on dispinfoW having same
920  structure as dispinfoA.
921  infoPtr : listview struct
922  code : *Unicode* notification code
923  pdi : dispinfo structure (can be unicode or ansi)
924  isW : TRUE if dispinfo is Unicode
925 */
927 {
928  INT length = 0, ret_length;
929  LPWSTR buffer = NULL, ret_text;
930  BOOL return_ansi = FALSE;
931  BOOL return_unicode = FALSE;
932  BOOL ret;
933 
934  if ((pdi->item.mask & LVIF_TEXT) && is_text(pdi->item.pszText))
935  {
936  return_unicode = ( isW && infoPtr->notifyFormat == NFR_ANSI);
937  return_ansi = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
938  }
939 
940  ret_length = pdi->item.cchTextMax;
941  ret_text = pdi->item.pszText;
942 
943  if (return_unicode || return_ansi)
944  {
945  if (code != LVN_GETDISPINFOW)
946  {
947  length = return_ansi ?
948  MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
949  WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
950  }
951  else
952  {
953  length = pdi->item.cchTextMax;
954  *pdi->item.pszText = 0; /* make sure we don't process garbage */
955  }
956 
957  buffer = Alloc( (return_ansi ? sizeof(WCHAR) : sizeof(CHAR)) * length);
958  if (!buffer) return FALSE;
959 
960  if (return_ansi)
962  buffer, length);
963  else
964  WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
965  length, NULL, NULL);
966 
967  pdi->item.pszText = buffer;
968  pdi->item.cchTextMax = length;
969  }
970 
971  if (infoPtr->notifyFormat == NFR_ANSI)
972  code = get_ansi_notification(code);
973 
974  TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
975  ret = notify_hdr(infoPtr, code, &pdi->hdr);
976  TRACE(" resulting code=%d\n", pdi->hdr.code);
977 
978  if (return_ansi || return_unicode)
979  {
980  if (return_ansi && (pdi->hdr.code == LVN_GETDISPINFOA))
981  {
982  strcpy((char*)ret_text, (char*)pdi->item.pszText);
983  }
984  else if (return_unicode && (pdi->hdr.code == LVN_GETDISPINFOW))
985  {
986  strcpyW(ret_text, pdi->item.pszText);
987  }
988  else if (return_ansi) /* note : pointer can be changed by app ! */
989  {
990  WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) ret_text,
991  ret_length, NULL, NULL);
992  }
993  else
995  ret_text, ret_length);
996 
997  pdi->item.pszText = ret_text; /* restores our buffer */
998  pdi->item.cchTextMax = ret_length;
999 
1000  Free(buffer);
1001  return ret;
1002  }
1003 
1004  /* if dipsinfo holder changed notification code then convert */
1005  if (!isW && (pdi->hdr.code == LVN_GETDISPINFOW) && (pdi->item.mask & LVIF_TEXT))
1006  {
1007  length = WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
1008 
1009  buffer = Alloc(length * sizeof(CHAR));
1010  if (!buffer) return FALSE;
1011 
1012  WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) buffer,
1013  ret_length, NULL, NULL);
1014 
1015  strcpy((LPSTR)pdi->item.pszText, (LPSTR)buffer);
1016  Free(buffer);
1017  }
1018 
1019  return ret;
1020 }
1021 
1022 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
1023  const RECT *rcBounds, const LVITEMW *lplvItem)
1024 {
1025  ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
1026  lpnmlvcd->nmcd.hdc = hdc;
1027  lpnmlvcd->nmcd.rc = *rcBounds;
1028  lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
1029  lpnmlvcd->clrText = infoPtr->clrText;
1030  if (!lplvItem) return;
1031  lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
1032  lpnmlvcd->iSubItem = lplvItem->iSubItem;
1033  if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
1034  if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
1035  if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
1036  lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
1037 }
1038 
1039 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
1040 {
1041  BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
1042  DWORD result;
1043 
1044  lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
1045  if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM;
1046  if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
1047  if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
1048  result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
1049  if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
1050  return result;
1051 }
1052 
1053 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
1054 {
1055  COLORREF backcolor, textcolor;
1056 
1057  /* apparently, for selected items, we have to override the returned values */
1058  if (!SubItem || (infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT))
1059  {
1060  if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1061  {
1062  if (infoPtr->bFocus)
1063  {
1066  }
1067  else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1068  {
1069  lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1070  lpnmlvcd->clrText = comctl32_color.clrBtnText;
1071  }
1072  }
1073  }
1074 
1075  backcolor = lpnmlvcd->clrTextBk;
1076  textcolor = lpnmlvcd->clrText;
1077 
1078  if (backcolor == CLR_DEFAULT)
1079  backcolor = comctl32_color.clrWindow;
1080  if (textcolor == CLR_DEFAULT)
1081  textcolor = comctl32_color.clrWindowText;
1082 
1083  /* Set the text attributes */
1084  if (backcolor != CLR_NONE)
1085  {
1086  SetBkMode(hdc, OPAQUE);
1087  SetBkColor(hdc, backcolor);
1088  }
1089  else
1090  SetBkMode(hdc, TRANSPARENT);
1091  SetTextColor(hdc, textcolor);
1092 }
1093 
1094 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1095 {
1096  return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1097 }
1098 
1099 /* returns TRUE when repaint needed, FALSE otherwise */
1101 {
1102  MEASUREITEMSTRUCT mis;
1103  mis.CtlType = ODT_LISTVIEW;
1104  mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1105  mis.itemID = -1;
1106  mis.itemWidth = 0;
1107  mis.itemData = 0;
1108  mis.itemHeight= infoPtr->nItemHeight;
1109  SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1110  if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1111  {
1112  infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1113  return TRUE;
1114  }
1115  return FALSE;
1116 }
1117 
1118 /******** Item iterator functions **********************************/
1119 
1120 static RANGES ranges_create(int count);
1121 static void ranges_destroy(RANGES ranges);
1122 static BOOL ranges_add(RANGES ranges, RANGE range);
1123 static BOOL ranges_del(RANGES ranges, RANGE range);
1124 static void ranges_dump(RANGES ranges);
1125 
1126 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1127 {
1128  RANGE range = { nItem, nItem + 1 };
1129 
1130  return ranges_add(ranges, range);
1131 }
1132 
1133 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1134 {
1135  RANGE range = { nItem, nItem + 1 };
1136 
1137  return ranges_del(ranges, range);
1138 }
1139 
1140 /***
1141  * ITERATOR DOCUMENTATION
1142  *
1143  * The iterator functions allow for easy, and convenient iteration
1144  * over items of interest in the list. Typically, you create an
1145  * iterator, use it, and destroy it, as such:
1146  * ITERATOR i;
1147  *
1148  * iterator_xxxitems(&i, ...);
1149  * while (iterator_{prev,next}(&i)
1150  * {
1151  * //code which uses i.nItem
1152  * }
1153  * iterator_destroy(&i);
1154  *
1155  * where xxx is either: framed, or visible.
1156  * Note that it is important that the code destroys the iterator
1157  * after it's done with it, as the creation of the iterator may
1158  * allocate memory, which thus needs to be freed.
1159  *
1160  * You can iterate both forwards, and backwards through the list,
1161  * by using iterator_next or iterator_prev respectively.
1162  *
1163  * Lower numbered items are draw on top of higher number items in
1164  * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1165  * items may overlap). So, to test items, you should use
1166  * iterator_next
1167  * which lists the items top to bottom (in Z-order).
1168  * For drawing items, you should use
1169  * iterator_prev
1170  * which lists the items bottom to top (in Z-order).
1171  * If you keep iterating over the items after the end-of-items
1172  * marker (-1) is returned, the iterator will start from the
1173  * beginning. Typically, you don't need to test for -1,
1174  * because iterator_{next,prev} will return TRUE if more items
1175  * are to be iterated over, or FALSE otherwise.
1176  *
1177  * Note: the iterator is defined to be bidirectional. That is,
1178  * any number of prev followed by any number of next, or
1179  * five versa, should leave the iterator at the same item:
1180  * prev * n, next * n = next * n, prev * n
1181  *
1182  * The iterator has a notion of an out-of-order, special item,
1183  * which sits at the start of the list. This is used in
1184  * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1185  * which needs to be first, as it may overlap other items.
1186  *
1187  * The code is a bit messy because we have:
1188  * - a special item to deal with
1189  * - simple range, or composite range
1190  * - empty range.
1191  * If you find bugs, or want to add features, please make sure you
1192  * always check/modify *both* iterator_prev, and iterator_next.
1193  */
1194 
1195 /****
1196  * This function iterates through the items in increasing order,
1197  * but prefixed by the special item, then -1. That is:
1198  * special, 1, 2, 3, ..., n, -1.
1199  * Each item is listed only once.
1200  */
1201 static inline BOOL iterator_next(ITERATOR* i)
1202 {
1203  if (i->nItem == -1)
1204  {
1205  i->nItem = i->nSpecial;
1206  if (i->nItem != -1) return TRUE;
1207  }
1208  if (i->nItem == i->nSpecial)
1209  {
1210  if (i->ranges) i->index = 0;
1211  goto pickarange;
1212  }
1213 
1214  i->nItem++;
1215 testitem:
1216  if (i->nItem == i->nSpecial) i->nItem++;
1217  if (i->nItem < i->range.upper) return TRUE;
1218 
1219 pickarange:
1220  if (i->ranges)
1221  {
1222  if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1223  i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1224  else goto end;
1225  }
1226  else if (i->nItem >= i->range.upper) goto end;
1227 
1228  i->nItem = i->range.lower;
1229  if (i->nItem >= 0) goto testitem;
1230 end:
1231  i->nItem = -1;
1232  return FALSE;
1233 }
1234 
1235 /****
1236  * This function iterates through the items in decreasing order,
1237  * followed by the special item, then -1. That is:
1238  * n, n-1, ..., 3, 2, 1, special, -1.
1239  * Each item is listed only once.
1240  */
1241 static inline BOOL iterator_prev(ITERATOR* i)
1242 {
1243  BOOL start = FALSE;
1244 
1245  if (i->nItem == -1)
1246  {
1247  start = TRUE;
1248  if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1249  goto pickarange;
1250  }
1251  if (i->nItem == i->nSpecial)
1252  {
1253  i->nItem = -1;
1254  return FALSE;
1255  }
1256 
1257 testitem:
1258  i->nItem--;
1259  if (i->nItem == i->nSpecial) i->nItem--;
1260  if (i->nItem >= i->range.lower) return TRUE;
1261 
1262 pickarange:
1263  if (i->ranges)
1264  {
1265  if (i->index > 0)
1266  i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1267  else goto end;
1268  }
1269  else if (!start && i->nItem < i->range.lower) goto end;
1270 
1271  i->nItem = i->range.upper;
1272  if (i->nItem > 0) goto testitem;
1273 end:
1274  return (i->nItem = i->nSpecial) != -1;
1275 }
1276 
1278 {
1279  RANGE range;
1280 
1281  if (!i->ranges) return i->range;
1282 
1283  if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1284  {
1285  range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1286  range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1287  }
1288  else range.lower = range.upper = 0;
1289 
1290  return range;
1291 }
1292 
1293 /***
1294  * Releases resources associated with this iterator.
1295  */
1296 static inline void iterator_destroy(const ITERATOR *i)
1297 {
1298  ranges_destroy(i->ranges);
1299 }
1300 
1301 /***
1302  * Create an empty iterator.
1303  */
1305 {
1306  ZeroMemory(i, sizeof(*i));
1307  i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1308  return TRUE;
1309 }
1310 
1311 /***
1312  * Create an iterator over a range.
1313  */
1315 {
1316  iterator_empty(i);
1317  i->range = range;
1318  return TRUE;
1319 }
1320 
1321 /***
1322  * Create an iterator over a bunch of ranges.
1323  * Please note that the iterator will take ownership of the ranges,
1324  * and will free them upon destruction.
1325  */
1326 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1327 {
1328  iterator_empty(i);
1329  i->ranges = ranges;
1330  return TRUE;
1331 }
1332 
1333 /***
1334  * Creates an iterator over the items which intersect frame.
1335  * Uses absolute coordinates rather than compensating for the current offset.
1336  */
1337 static BOOL iterator_frameditems_absolute(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *frame)
1338 {
1339  RECT rcItem, rcTemp;
1340 
1341  /* in case we fail, we want to return an empty iterator */
1342  if (!iterator_empty(i)) return FALSE;
1343 
1344  TRACE("(frame=%s)\n", wine_dbgstr_rect(frame));
1345 
1346  if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1347  {
1348  INT nItem;
1349 
1350  if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1351  {
1352  LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1353  if (IntersectRect(&rcTemp, &rcItem, frame))
1354  i->nSpecial = infoPtr->nFocusedItem;
1355  }
1356  if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1357  /* to do better here, we need to have PosX, and PosY sorted */
1358  TRACE("building icon ranges:\n");
1359  for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1360  {
1361  rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1362  rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1363  rcItem.right = rcItem.left + infoPtr->nItemWidth;
1364  rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1365  if (IntersectRect(&rcTemp, &rcItem, frame))
1366  ranges_additem(i->ranges, nItem);
1367  }
1368  return TRUE;
1369  }
1370  else if (infoPtr->uView == LV_VIEW_DETAILS)
1371  {
1372  RANGE range;
1373 
1374  if (frame->left >= infoPtr->nItemWidth) return TRUE;
1375  if (frame->top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1376 
1377  range.lower = max(frame->top / infoPtr->nItemHeight, 0);
1378  range.upper = min((frame->bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1379  if (range.upper <= range.lower) return TRUE;
1380  if (!iterator_rangeitems(i, range)) return FALSE;
1381  TRACE(" report=%s\n", debugrange(&i->range));
1382  }
1383  else
1384  {
1385  INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1386  INT nFirstRow = max(frame->top / infoPtr->nItemHeight, 0);
1387  INT nLastRow = min((frame->bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1388  INT nFirstCol;
1389  INT nLastCol;
1390  INT lower;
1391  RANGE item_range;
1392  INT nCol;
1393 
1394  if (infoPtr->nItemWidth)
1395  {
1396  nFirstCol = max(frame->left / infoPtr->nItemWidth, 0);
1397  nLastCol = min((frame->right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1398  }
1399  else
1400  {
1401  nFirstCol = max(frame->left, 0);
1402  nLastCol = min(frame->right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1403  }
1404 
1405  lower = nFirstCol * nPerCol + nFirstRow;
1406 
1407  TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1408  nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1409 
1410  if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1411 
1412  if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1413  TRACE("building list ranges:\n");
1414  for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1415  {
1416  item_range.lower = nCol * nPerCol + nFirstRow;
1417  if(item_range.lower >= infoPtr->nItemCount) break;
1418  item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1419  TRACE(" list=%s\n", debugrange(&item_range));
1420  ranges_add(i->ranges, item_range);
1421  }
1422  }
1423 
1424  return TRUE;
1425 }
1426 
1427 /***
1428  * Creates an iterator over the items which intersect lprc.
1429  */
1430 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1431 {
1432  RECT frame = *lprc;
1433  POINT Origin;
1434 
1435  TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1436 
1437  LISTVIEW_GetOrigin(infoPtr, &Origin);
1438  OffsetRect(&frame, -Origin.x, -Origin.y);
1439 
1440  return iterator_frameditems_absolute(i, infoPtr, &frame);
1441 }
1442 
1443 /***
1444  * Creates an iterator over the items which intersect the visible region of hdc.
1445  */
1447 {
1448  POINT Origin, Position;
1449  RECT rcItem, rcClip;
1450  INT rgntype;
1451 
1452  rgntype = GetClipBox(hdc, &rcClip);
1453  if (rgntype == NULLREGION) return iterator_empty(i);
1454  if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1455  if (rgntype == SIMPLEREGION) return TRUE;
1456 
1457  /* first deal with the special item */
1458  if (i->nSpecial != -1)
1459  {
1460  LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1461  if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1462  }
1463 
1464  /* if we can't deal with the region, we'll just go with the simple range */
1465  LISTVIEW_GetOrigin(infoPtr, &Origin);
1466  TRACE("building visible range:\n");
1467  if (!i->ranges && i->range.lower < i->range.upper)
1468  {
1469  if (!(i->ranges = ranges_create(50))) return TRUE;
1470  if (!ranges_add(i->ranges, i->range))
1471  {
1472  ranges_destroy(i->ranges);
1473  i->ranges = 0;
1474  return TRUE;
1475  }
1476  }
1477 
1478  /* now delete the invisible items from the list */
1479  while(iterator_next(i))
1480  {
1481  LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1482  rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1483  rcItem.top = Position.y + Origin.y;
1484  rcItem.right = rcItem.left + infoPtr->nItemWidth;
1485  rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1486  if (!RectVisible(hdc, &rcItem))
1487  ranges_delitem(i->ranges, i->nItem);
1488  }
1489  /* the iterator should restart on the next iterator_next */
1490  TRACE("done\n");
1491 
1492  return TRUE;
1493 }
1494 
1495 /* Remove common elements from two iterators */
1496 /* Passed iterators have to point on the first elements */
1498 {
1499  if(!iter1->ranges || !iter2->ranges) {
1500  int lower, upper;
1501 
1502  if(iter1->ranges || iter2->ranges ||
1503  (iter1->range.lower<iter2->range.lower && iter1->range.upper>iter2->range.upper) ||
1504  (iter1->range.lower>iter2->range.lower && iter1->range.upper<iter2->range.upper)) {
1505  ERR("result is not a one range iterator\n");
1506  return FALSE;
1507  }
1508 
1509  if(iter1->range.lower==-1 || iter2->range.lower==-1)
1510  return TRUE;
1511 
1512  lower = iter1->range.lower;
1513  upper = iter1->range.upper;
1514 
1515  if(lower < iter2->range.lower)
1516  iter1->range.upper = iter2->range.lower;
1517  else if(upper > iter2->range.upper)
1518  iter1->range.lower = iter2->range.upper;
1519  else
1520  iter1->range.lower = iter1->range.upper = -1;
1521 
1522  if(iter2->range.lower < lower)
1523  iter2->range.upper = lower;
1524  else if(iter2->range.upper > upper)
1525  iter2->range.lower = upper;
1526  else
1527  iter2->range.lower = iter2->range.upper = -1;
1528 
1529  return TRUE;
1530  }
1531 
1532  iterator_next(iter1);
1533  iterator_next(iter2);
1534 
1535  while(1) {
1536  if(iter1->nItem==-1 || iter2->nItem==-1)
1537  break;
1538 
1539  if(iter1->nItem == iter2->nItem) {
1540  int delete = iter1->nItem;
1541 
1542  iterator_prev(iter1);
1543  iterator_prev(iter2);
1544  ranges_delitem(iter1->ranges, delete);
1545  ranges_delitem(iter2->ranges, delete);
1546  iterator_next(iter1);
1547  iterator_next(iter2);
1548  } else if(iter1->nItem > iter2->nItem)
1549  iterator_next(iter2);
1550  else
1551  iterator_next(iter1);
1552  }
1553 
1554  iter1->nItem = iter1->range.lower = iter1->range.upper = -1;
1555  iter2->nItem = iter2->range.lower = iter2->range.upper = -1;
1556  return TRUE;
1557 }
1558 
1559 /******** Misc helper functions ************************************/
1560 
1563 {
1564  if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1565  else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1566 }
1567 
1568 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1569 {
1570  return (infoPtr->dwStyle & LVS_AUTOARRANGE) &&
1571  (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1572 }
1573 
1574 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1575 {
1577  if(state == 1 || state == 2)
1578  {
1579  LVITEMW lvitem;
1580  state ^= 3;
1581  lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1582  lvitem.stateMask = LVIS_STATEIMAGEMASK;
1583  LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1584  }
1585 }
1586 
1587 /* this should be called after window style got updated,
1588  it used to reset view state to match current window style */
1589 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1590 {
1591  switch (infoPtr->dwStyle & LVS_TYPEMASK)
1592  {
1593  case LVS_ICON:
1594  infoPtr->uView = LV_VIEW_ICON;
1595  break;
1596  case LVS_REPORT:
1597  infoPtr->uView = LV_VIEW_DETAILS;
1598  break;
1599  case LVS_SMALLICON:
1600  infoPtr->uView = LV_VIEW_SMALLICON;
1601  break;
1602  case LVS_LIST:
1603  infoPtr->uView = LV_VIEW_LIST;
1604  }
1605 }
1606 
1607 /* computes next item id value */
1608 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1609 {
1610  INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1611 
1612  if (count > 0)
1613  {
1614  ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1615  return lpID->id + 1;
1616  }
1617  return 0;
1618 }
1619 
1620 /******** Internal API functions ************************************/
1621 
1622 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1623 {
1624  static COLUMN_INFO mainItem;
1625 
1626  if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1627  assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1628 
1629  /* update cached column rectangles */
1630  if (infoPtr->colRectsDirty)
1631  {
1632  COLUMN_INFO *info;
1633  LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1634  INT i;
1635 
1636  for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1637  info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1638  SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1639  }
1640  Ptr->colRectsDirty = FALSE;
1641  }
1642 
1643  return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1644 }
1645 
1647 {
1649  HINSTANCE hInst;
1650 
1651  if (infoPtr->hwndHeader) return 0;
1652 
1653  TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1654 
1655  /* setup creation flags */
1656  dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1657  dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1658 
1659  hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1660 
1661  /* create header */
1662  infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1663  0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1664  if (!infoPtr->hwndHeader) return -1;
1665 
1666  /* set header unicode format */
1668 
1669  /* set header font */
1670  SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, TRUE);
1671 
1672  /* set header image list */
1673  if (infoPtr->himlSmall)
1674  SendMessageW(infoPtr->hwndHeader, HDM_SETIMAGELIST, 0, (LPARAM)infoPtr->himlSmall);
1675 
1676  LISTVIEW_UpdateSize(infoPtr);
1677 
1678  return 0;
1679 }
1680 
1681 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1682 {
1683  *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1684 }
1685 
1686 static inline BOOL LISTVIEW_IsHeaderEnabled(const LISTVIEW_INFO *infoPtr)
1687 {
1688  return (infoPtr->uView == LV_VIEW_DETAILS ||
1689  infoPtr->dwLvExStyle & LVS_EX_HEADERINALLVIEWS) &&
1690  !(infoPtr->dwStyle & LVS_NOCOLUMNHEADER);
1691 }
1692 
1693 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1694 {
1695  return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1696 }
1697 
1698 /* used to handle collapse main item column case */
1699 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1700 {
1701 #ifdef __REACTOS__
1702  BOOL Ret = FALSE;
1703 
1704  if (infoPtr->rcFocus.left < infoPtr->rcFocus.right)
1705  {
1706  DWORD dwOldBkColor, dwOldTextColor;
1707 
1708  dwOldBkColor = SetBkColor(hdc, RGB(255, 255, 255));
1709  dwOldTextColor = SetBkColor(hdc, RGB(0, 0, 0));
1710  Ret = DrawFocusRect(hdc, &infoPtr->rcFocus);
1711  SetBkColor(hdc, dwOldBkColor);
1712  SetBkColor(hdc, dwOldTextColor);
1713  }
1714  return Ret;
1715 #else
1716  return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1717  DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1718 #endif
1719 }
1720 
1721 /* Listview invalidation functions: use _only_ these functions to invalidate */
1722 
1723 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1724 {
1725  return infoPtr->redraw;
1726 }
1727 
1728 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1729 {
1730  if(!is_redrawing(infoPtr)) return;
1731  TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1732  InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1733 }
1734 
1735 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1736 {
1737  RECT rcBox;
1738 
1739  if(!is_redrawing(infoPtr)) return;
1740  LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1741  LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1742 }
1743 
1744 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1745 {
1746  POINT Origin, Position;
1747  RECT rcBox;
1748 
1749  if(!is_redrawing(infoPtr)) return;
1750  assert (infoPtr->uView == LV_VIEW_DETAILS);
1751  LISTVIEW_GetOrigin(infoPtr, &Origin);
1752  LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1753  LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1754  rcBox.top = 0;
1755  rcBox.bottom = infoPtr->nItemHeight;
1756  OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1757  LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1758 }
1759 
1760 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1761 {
1762  LISTVIEW_InvalidateRect(infoPtr, NULL);
1763 }
1764 
1765 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1766 {
1767  RECT rcCol;
1768 
1769  if(!is_redrawing(infoPtr)) return;
1770  LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1771  rcCol.top = infoPtr->rcList.top;
1772  rcCol.bottom = infoPtr->rcList.bottom;
1773  LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1774 }
1775 
1776 /***
1777  * DESCRIPTION:
1778  * Retrieves the number of items that can fit vertically in the client area.
1779  *
1780  * PARAMETER(S):
1781  * [I] infoPtr : valid pointer to the listview structure
1782  *
1783  * RETURN:
1784  * Number of items per row.
1785  */
1786 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1787 {
1788  INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1789 
1790  return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1791 }
1792 
1793 /***
1794  * DESCRIPTION:
1795  * Retrieves the number of items that can fit horizontally in the client
1796  * area.
1797  *
1798  * PARAMETER(S):
1799  * [I] infoPtr : valid pointer to the listview structure
1800  *
1801  * RETURN:
1802  * Number of items per column.
1803  */
1804 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1805 {
1806  INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1807 
1808  return max(nListHeight / infoPtr->nItemHeight, 1);
1809 }
1810 
1811 
1812 /*************************************************************************
1813  * LISTVIEW_ProcessLetterKeys
1814  *
1815  * Processes keyboard messages generated by pressing the letter keys
1816  * on the keyboard.
1817  * What this does is perform a case insensitive search from the
1818  * current position with the following quirks:
1819  * - If two chars or more are pressed in quick succession we search
1820  * for the corresponding string (e.g. 'abc').
1821  * - If there is a delay we wipe away the current search string and
1822  * restart with just that char.
1823  * - If the user keeps pressing the same character, whether slowly or
1824  * fast, so that the search string is entirely composed of this
1825  * character ('aaaaa' for instance), then we search for first item
1826  * that starting with that character.
1827  * - If the user types the above character in quick succession, then
1828  * we must also search for the corresponding string ('aaaaa'), and
1829  * go to that string if there is a match.
1830  *
1831  * PARAMETERS
1832  * [I] hwnd : handle to the window
1833  * [I] charCode : the character code, the actual character
1834  * [I] keyData : key data
1835  *
1836  * RETURNS
1837  *
1838  * Zero.
1839  *
1840  * BUGS
1841  *
1842  * - The current implementation has a list of characters it will
1843  * accept and it ignores everything else. In particular it will
1844  * ignore accentuated characters which seems to match what
1845  * Windows does. But I'm not sure it makes sense to follow
1846  * Windows there.
1847  * - We don't sound a beep when the search fails.
1848  *
1849  * SEE ALSO
1850  *
1851  * TREEVIEW_ProcessLetterKeys
1852  */
1853 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1854 {
1856  DWORD prevTime;
1857  LVITEMW item;
1858  int startidx;
1859  INT nItem;
1860  INT diff;
1861 
1862  /* simple parameter checking */
1863  if (!charCode || !keyData || infoPtr->nItemCount == 0) return 0;
1864 
1865  /* only allow the valid WM_CHARs through */
1866  if (!isalnumW(charCode) &&
1867  charCode != '.' && charCode != '`' && charCode != '!' &&
1868  charCode != '@' && charCode != '#' && charCode != '$' &&
1869  charCode != '%' && charCode != '^' && charCode != '&' &&
1870  charCode != '*' && charCode != '(' && charCode != ')' &&
1871  charCode != '-' && charCode != '_' && charCode != '+' &&
1872  charCode != '=' && charCode != '\\'&& charCode != ']' &&
1873  charCode != '}' && charCode != '[' && charCode != '{' &&
1874  charCode != '/' && charCode != '?' && charCode != '>' &&
1875  charCode != '<' && charCode != ',' && charCode != '~')
1876  return 0;
1877 
1878  /* update the search parameters */
1879  prevTime = infoPtr->lastKeyPressTimestamp;
1880  infoPtr->lastKeyPressTimestamp = GetTickCount();
1881  diff = infoPtr->lastKeyPressTimestamp - prevTime;
1882 
1883  if (diff >= 0 && diff < KEY_DELAY)
1884  {
1885  if (infoPtr->nSearchParamLength < MAX_PATH - 1)
1886  infoPtr->szSearchParam[infoPtr->nSearchParamLength++] = charCode;
1887 
1888  if (infoPtr->charCode != charCode)
1889  infoPtr->charCode = charCode = 0;
1890  }
1891  else
1892  {
1893  infoPtr->charCode = charCode;
1894  infoPtr->szSearchParam[0] = charCode;
1895  infoPtr->nSearchParamLength = 1;
1896  }
1897 
1898  /* should start from next after focused item, so next item that matches
1899  will be selected, if there isn't any and focused matches it will be selected
1900  on second search stage from beginning of the list */
1901  if (infoPtr->nFocusedItem >= 0 && infoPtr->nItemCount > 1)
1902  {
1903  /* with some accumulated search data available start with current focus, otherwise
1904  it's excluded from search */
1905  startidx = infoPtr->nSearchParamLength > 1 ? infoPtr->nFocusedItem : infoPtr->nFocusedItem + 1;
1906  if (startidx == infoPtr->nItemCount) startidx = 0;
1907  }
1908  else
1909  startidx = 0;
1910 
1911  /* let application handle this for virtual listview */
1912  if (infoPtr->dwStyle & LVS_OWNERDATA)
1913  {
1914  NMLVFINDITEMW nmlv;
1915 
1916  memset(&nmlv.lvfi, 0, sizeof(nmlv.lvfi));
1917  nmlv.lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1918  nmlv.lvfi.psz = infoPtr->szSearchParam;
1919  nmlv.iStart = startidx;
1920 
1921  infoPtr->szSearchParam[infoPtr->nSearchParamLength] = 0;
1922 
1923  nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1924  }
1925  else
1926  {
1927  int i = startidx, endidx;
1928 
1929  /* and search from the current position */
1930  nItem = -1;
1931  endidx = infoPtr->nItemCount;
1932 
1933  /* first search in [startidx, endidx), on failure continue in [0, startidx) */
1934  while (1)
1935  {
1936  /* start from first item if not found with >= startidx */
1937  if (i == infoPtr->nItemCount && startidx > 0)
1938  {
1939  endidx = startidx;
1940  startidx = 0;
1941  }
1942 
1943  for (i = startidx; i < endidx; i++)
1944  {
1945  /* retrieve text */
1946  item.mask = LVIF_TEXT;
1947  item.iItem = i;
1948  item.iSubItem = 0;
1949  item.pszText = buffer;
1950  item.cchTextMax = MAX_PATH;
1951  if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1952 
1953  if (!lstrncmpiW(item.pszText, infoPtr->szSearchParam, infoPtr->nSearchParamLength))
1954  {
1955  nItem = i;
1956  break;
1957  }
1958  /* this is used to find first char match when search string is not available yet,
1959  otherwise every WM_CHAR will search to next item by first char, ignoring that we're
1960  already waiting for user to complete a string */
1961  else if (nItem == -1 && infoPtr->nSearchParamLength == 1 && !lstrncmpiW(item.pszText, infoPtr->szSearchParam, 1))
1962  {
1963  /* this would work but we must keep looking for a longer match */
1964  nItem = i;
1965  }
1966  }
1967 
1968  if ( nItem != -1 || /* found something */
1969  endidx != infoPtr->nItemCount || /* second search done */
1970  (startidx == 0 && endidx == infoPtr->nItemCount) /* full range for first search */ )
1971  break;
1972  };
1973  }
1974 
1975  if (nItem != -1)
1976  LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1977 
1978  return 0;
1979 }
1980 
1981 /*************************************************************************
1982  * LISTVIEW_UpdateHeaderSize [Internal]
1983  *
1984  * Function to resize the header control
1985  *
1986  * PARAMS
1987  * [I] hwnd : handle to a window
1988  * [I] nNewScrollPos : scroll pos to set
1989  *
1990  * RETURNS
1991  * None.
1992  */
1993 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1994 {
1995  RECT winRect;
1996  POINT point[2];
1997 
1998  TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1999 
2000  if (!infoPtr->hwndHeader) return;
2001 
2002  GetWindowRect(infoPtr->hwndHeader, &winRect);
2003  point[0].x = winRect.left;
2004  point[0].y = winRect.top;
2005  point[1].x = winRect.right;
2006  point[1].y = winRect.bottom;
2007 
2008  MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
2009  point[0].x = -nNewScrollPos;
2010  point[1].x += nNewScrollPos;
2011 
2012  SetWindowPos(infoPtr->hwndHeader,0,
2013  point[0].x,point[0].y,point[1].x,point[1].y,
2016 }
2017 
2019 {
2020  SCROLLINFO horzInfo;
2021  INT dx;
2022 
2023  ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
2024  horzInfo.cbSize = sizeof(SCROLLINFO);
2025  horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
2026 
2027  /* for now, we'll set info.nMax to the _count_, and adjust it later */
2028  if (infoPtr->uView == LV_VIEW_LIST)
2029  {
2030  INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
2031  horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
2032 
2033  /* scroll by at least one column per page */
2034  if(horzInfo.nPage < infoPtr->nItemWidth)
2035  horzInfo.nPage = infoPtr->nItemWidth;
2036 
2037  if (infoPtr->nItemWidth)
2038  horzInfo.nPage /= infoPtr->nItemWidth;
2039  }
2040  else if (infoPtr->uView == LV_VIEW_DETAILS)
2041  {
2042  horzInfo.nMax = infoPtr->nItemWidth;
2043  }
2044  else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2045  {
2046  RECT rcView;
2047 
2048  if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
2049  }
2050 
2051  if (LISTVIEW_IsHeaderEnabled(infoPtr))
2052  {
2053  if (DPA_GetPtrCount(infoPtr->hdpaColumns))
2054  {
2055  RECT rcHeader;
2056  INT index;
2057 
2058  index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2059  DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2060 
2061  LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2062  horzInfo.nMax = rcHeader.right;
2063  TRACE("horzInfo.nMax=%d\n", horzInfo.nMax);
2064  }
2065  }
2066 
2067  horzInfo.fMask = SIF_RANGE | SIF_PAGE;
2068  horzInfo.nMax = max(horzInfo.nMax - 1, 0);
2069  dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
2070  dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
2071  TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
2072 
2073  /* Update the Header Control */
2074  if (infoPtr->hwndHeader)
2075  {
2076  horzInfo.fMask = SIF_POS;
2077  GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
2078  LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
2079  }
2080 
2081  LISTVIEW_UpdateSize(infoPtr);
2082  return dx;
2083 }
2084 
2086 {
2087  SCROLLINFO vertInfo;
2088  INT dy;
2089 
2090  ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
2091  vertInfo.cbSize = sizeof(SCROLLINFO);
2092  vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
2093 
2094  if (infoPtr->uView == LV_VIEW_DETAILS)
2095  {
2096  vertInfo.nMax = infoPtr->nItemCount;
2097 
2098  /* scroll by at least one page */
2099  if(vertInfo.nPage < infoPtr->nItemHeight)
2100  vertInfo.nPage = infoPtr->nItemHeight;
2101 
2102  if (infoPtr->nItemHeight > 0)
2103  vertInfo.nPage /= infoPtr->nItemHeight;
2104  }
2105  else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
2106  {
2107  RECT rcView;
2108 
2109  if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
2110  }
2111 
2112  vertInfo.fMask = SIF_RANGE | SIF_PAGE;
2113  vertInfo.nMax = max(vertInfo.nMax - 1, 0);
2114  dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
2115  dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
2116  TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
2117 
2118  LISTVIEW_UpdateSize(infoPtr);
2119  return dy;
2120 }
2121 
2122 /***
2123  * DESCRIPTION:
2124  * Update the scrollbars. This function should be called whenever
2125  * the content, size or view changes.
2126  *
2127  * PARAMETER(S):
2128  * [I] infoPtr : valid pointer to the listview structure
2129  *
2130  * RETURN:
2131  * None
2132  */
2134 {
2135  INT dx, dy, pass;
2136 
2137  if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
2138 
2139  /* Setting the horizontal scroll can change the listview size
2140  * (and potentially everything else) so we need to recompute
2141  * everything again for the vertical scroll and vice-versa
2142  */
2143  for (dx = 0, dy = 0, pass = 0; pass <= 1; pass++)
2144  {
2145  dx += LISTVIEW_UpdateHScroll(infoPtr);
2146  dy += LISTVIEW_UpdateVScroll(infoPtr);
2147  }
2148 
2149  /* Change of the range may have changed the scroll pos. If so move the content */
2150  if (dx != 0 || dy != 0)
2151  {
2152  RECT listRect;
2153  listRect = infoPtr->rcList;
2154  ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
2156  }
2157 }
2158 
2159 
2160 /***
2161  * DESCRIPTION:
2162  * Shows/hides the focus rectangle.
2163  *
2164  * PARAMETER(S):
2165  * [I] infoPtr : valid pointer to the listview structure
2166  * [I] fShow : TRUE to show the focus, FALSE to hide it.
2167  *
2168  * RETURN:
2169  * None
2170  */
2171 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
2172 {
2173  HDC hdc;
2174 
2175  TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
2176 
2177  if (infoPtr->nFocusedItem < 0) return;
2178 
2179  /* we need some gymnastics in ICON mode to handle large items */
2180  if (infoPtr->uView == LV_VIEW_ICON)
2181  {
2182  RECT rcBox;
2183 
2184  LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox);
2185  if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
2186  {
2187  LISTVIEW_InvalidateRect(infoPtr, &rcBox);
2188  return;
2189  }
2190  }
2191 
2192  if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
2193 
2194  /* for some reason, owner draw should work only in report mode */
2195  if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
2196  {
2197  DRAWITEMSTRUCT dis;
2198  LVITEMW item;
2199 
2200  HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2201  HFONT hOldFont = SelectObject(hdc, hFont);
2202 
2203  item.iItem = infoPtr->nFocusedItem;
2204  item.iSubItem = 0;
2205  item.mask = LVIF_PARAM;
2206  if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
2207 
2208  ZeroMemory(&dis, sizeof(dis));
2209  dis.CtlType = ODT_LISTVIEW;
2210  dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
2211  dis.itemID = item.iItem;
2212  dis.itemAction = ODA_FOCUS;
2213  if (fShow) dis.itemState |= ODS_FOCUS;
2214  dis.hwndItem = infoPtr->hwndSelf;
2215  dis.hDC = hdc;
2216  LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
2217  dis.itemData = item.lParam;
2218 
2219  SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
2220 
2221  SelectObject(hdc, hOldFont);
2222  }
2223  else
2224  LISTVIEW_InvalidateItem(infoPtr, infoPtr->nFocusedItem);
2225 
2226 done:
2227  ReleaseDC(infoPtr->hwndSelf, hdc);
2228 }
2229 
2230 /***
2231  * Invalidates all visible selected items.
2232  */
2234 {
2235  ITERATOR i;
2236 
2237  iterator_frameditems(&i, infoPtr, &infoPtr->rcList);
2238  while(iterator_next(&i))
2239  {
2240  if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2241  LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2242  }
2243  iterator_destroy(&i);
2244 }
2245 
2246 
2247 /***
2248  * DESCRIPTION: [INTERNAL]
2249  * Computes an item's (left,top) corner, relative to rcView.
2250  * That is, the position has NOT been made relative to the Origin.
2251  * This is deliberate, to avoid computing the Origin over, and
2252  * over again, when this function is called in a loop. Instead,
2253  * one can factor the computation of the Origin before the loop,
2254  * and offset the value returned by this function, on every iteration.
2255  *
2256  * PARAMETER(S):
2257  * [I] infoPtr : valid pointer to the listview structure
2258  * [I] nItem : item number
2259  * [O] lpptOrig : item top, left corner
2260  *
2261  * RETURN:
2262  * None.
2263  */
2264 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2265 {
2266  assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2267 
2268  if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2269  {
2270  lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2271  lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2272  }
2273  else if (infoPtr->uView == LV_VIEW_LIST)
2274  {
2275  INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2276  lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2277  lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2278  }
2279  else /* LV_VIEW_DETAILS */
2280  {
2281  lpptPosition->x = REPORT_MARGINX;
2282  /* item is always at zero indexed column */
2283  if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2284  lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2285  lpptPosition->y = nItem * infoPtr->nItemHeight;
2286  }
2287 }
2288 
2289 /***
2290  * DESCRIPTION: [INTERNAL]
2291  * Compute the rectangles of an item. This is to localize all
2292  * the computations in one place. If you are not interested in some
2293  * of these values, simply pass in a NULL -- the function is smart
2294  * enough to compute only what's necessary. The function computes
2295  * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2296  * one, the BOX rectangle. This rectangle is very cheap to compute,
2297  * and is guaranteed to contain all the other rectangles. Computing
2298  * the ICON rect is also cheap, but all the others are potentially
2299  * expensive. This gives an easy and effective optimization when
2300  * searching (like point inclusion, or rectangle intersection):
2301  * first test against the BOX, and if TRUE, test against the desired
2302  * rectangle.
2303  * If the function does not have all the necessary information
2304  * to computed the requested rectangles, will crash with a
2305  * failed assertion. This is done so we catch all programming
2306  * errors, given that the function is called only from our code.
2307  *
2308  * We have the following 'special' meanings for a few fields:
2309  * * If LVIS_FOCUSED is set, we assume the item has the focus
2310  * This is important in ICON mode, where it might get a larger
2311  * then usual rectangle
2312  *
2313  * Please note that subitem support works only in REPORT mode.
2314  *
2315  * PARAMETER(S):
2316  * [I] infoPtr : valid pointer to the listview structure
2317  * [I] lpLVItem : item to compute the measures for
2318  * [O] lprcBox : ptr to Box rectangle
2319  * Same as LVM_GETITEMRECT with LVIR_BOUNDS
2320  * [0] lprcSelectBox : ptr to select box rectangle
2321  * Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2322  * [O] lprcIcon : ptr to Icon rectangle
2323  * Same as LVM_GETITEMRECT with LVIR_ICON
2324  * [O] lprcStateIcon: ptr to State Icon rectangle
2325  * [O] lprcLabel : ptr to Label rectangle
2326  * Same as LVM_GETITEMRECT with LVIR_LABEL
2327  *
2328  * RETURN:
2329  * None.
2330  */
2331 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2332  LPRECT lprcBox, LPRECT lprcSelectBox,
2333  LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2334 {
2335  BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2336  RECT Box, SelectBox, Icon, Label;
2337  COLUMN_INFO *lpColumnInfo = NULL;
2338  SIZE labelSize = { 0, 0 };
2339 
2340  TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2341 
2342  /* Be smart and try to figure out the minimum we have to do */
2343  if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2344  if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2345  {
2346  assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2347  if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2348  }
2349  if (lprcSelectBox) doSelectBox = TRUE;
2350  if (lprcLabel) doLabel = TRUE;
2351  if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2352  if (doSelectBox)
2353  {
2354  doIcon = TRUE;
2355  doLabel = TRUE;
2356  }
2357 
2358  /************************************************************/
2359  /* compute the box rectangle (it should be cheap to do) */
2360  /************************************************************/
2361  if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2362  lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2363 
2364  if (lpLVItem->iSubItem)
2365  {
2366  Box = lpColumnInfo->rcHeader;
2367  }
2368  else
2369  {
2370  Box.left = 0;
2371  Box.right = infoPtr->nItemWidth;
2372  }
2373  Box.top = 0;
2374  Box.bottom = infoPtr->nItemHeight;
2375 
2376  /******************************************************************/
2377  /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON */
2378  /******************************************************************/
2379  if (doIcon)
2380  {
2381  LONG state_width = 0;
2382 
2383  if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2384  state_width = infoPtr->iconStateSize.cx;
2385 
2386  if (infoPtr->uView == LV_VIEW_ICON)
2387  {
2388  Icon.left = Box.left + state_width;
2389  if (infoPtr->himlNormal)
2390  Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2391  Icon.top = Box.top + ICON_TOP_PADDING;
2392  Icon.right = Icon.left;
2393  Icon.bottom = Icon.top;
2394  if (infoPtr->himlNormal)
2395  {
2396  Icon.right += infoPtr->iconSize.cx;
2397  Icon.bottom += infoPtr->iconSize.cy;
2398  }
2399  }
2400  else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2401  {
2402  Icon.left = Box.left + state_width;
2403 
2404  if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2405  {
2406  /* we need the indent in report mode */
2407  assert(lpLVItem->mask & LVIF_INDENT);
2408  Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2409  }
2410 
2411  Icon.top = Box.top;
2412  Icon.right = Icon.left;
2413  if (infoPtr->himlSmall &&
2414  (!lpColumnInfo || lpLVItem->iSubItem == 0 ||
2415  ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2416  Icon.right += infoPtr->iconSize.cx;
2417  Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2418  }
2419  if(lprcIcon) *lprcIcon = Icon;
2420  TRACE(" - icon=%s\n", wine_dbgstr_rect(&Icon));
2421 
2422  /* TODO: is this correct? */
2423  if (lprcStateIcon)
2424  {
2425  lprcStateIcon->left = Icon.left - state_width;
2426  lprcStateIcon->right = Icon.left;
2427  lprcStateIcon->top = Icon.top;
2428  lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2429  TRACE(" - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2430  }
2431  }
2432  else Icon.right = 0;
2433 
2434  /************************************************************/
2435  /* compute LABEL bounding box (ala LVM_GETITEMRECT) */
2436  /************************************************************/
2437  if (doLabel)
2438  {
2439  /* calculate how far to the right can the label stretch */
2440  Label.right = Box.right;
2441  if (infoPtr->uView == LV_VIEW_DETAILS)
2442  {
2443  if (lpLVItem->iSubItem == 0)
2444  {
2445  /* we need a zero based rect here */
2446  Label = lpColumnInfo->rcHeader;
2447  OffsetRect(&Label, -Label.left, 0);
2448  }
2449  }
2450 
2451  if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2452  {
2453  labelSize.cx = infoPtr->nItemWidth;
2454  labelSize.cy = infoPtr->nItemHeight;
2455  goto calc_label;
2456  }
2457 
2458  /* we need the text in non owner draw mode */
2459  assert(lpLVItem->mask & LVIF_TEXT);
2460  if (is_text(lpLVItem->pszText))
2461  {
2462  HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2463  HDC hdc = GetDC(infoPtr->hwndSelf);
2464  HFONT hOldFont = SelectObject(hdc, hFont);
2465  UINT uFormat;
2466  RECT rcText;
2467 
2468  /* compute rough rectangle where the label will go */
2469  SetRectEmpty(&rcText);
2470  rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2471  rcText.bottom = infoPtr->nItemHeight;
2472  if (infoPtr->uView == LV_VIEW_ICON)
2473  rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2474 
2475  /* now figure out the flags */
2476  if (infoPtr->uView == LV_VIEW_ICON)
2477  uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2478  else
2479  uFormat = LV_SL_DT_FLAGS;
2480 
2481  DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2482 
2483  if (rcText.right != rcText.left)
2484  labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2485 
2486  labelSize.cy = rcText.bottom - rcText.top;
2487 
2488  SelectObject(hdc, hOldFont);
2489  ReleaseDC(infoPtr->hwndSelf, hdc);
2490  }
2491 
2492 calc_label:
2493  if (infoPtr->uView == LV_VIEW_ICON)
2494  {
2495  Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2496  Label.top = Box.top + ICON_TOP_PADDING_HITABLE +
2497  infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2498  Label.right = Label.left + labelSize.cx;
2499  Label.bottom = Label.top + infoPtr->nItemHeight;
2500  if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2501  {
2502  labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2503  labelSize.cy /= infoPtr->ntmHeight;
2504  labelSize.cy = max(labelSize.cy, 1);
2505  labelSize.cy *= infoPtr->ntmHeight;
2506  }
2507  Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2508  }
2509  else if (infoPtr->uView == LV_VIEW_DETAILS)
2510  {
2511  Label.left = Icon.right;
2512  Label.top = Box.top;
2513  Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2514  lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2515  Label.bottom = Label.top + infoPtr->nItemHeight;
2516  }
2517  else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2518  {
2519  Label.left = Icon.right;
2520  Label.top = Box.top;
2521  Label.right = min(Label.left + labelSize.cx, Label.right);
2522  Label.bottom = Label.top + infoPtr->nItemHeight;
2523  }
2524 
2525  if (lprcLabel) *lprcLabel = Label;
2526  TRACE(" - label=%s\n", wine_dbgstr_rect(&Label));
2527  }
2528 
2529  /************************************************************/
2530  /* compute SELECT bounding box */
2531  /************************************************************/
2532  if (doSelectBox)
2533  {
2534  if (infoPtr->uView == LV_VIEW_DETAILS)
2535  {
2536  SelectBox.left = Icon.left;
2537  SelectBox.top = Box.top;
2538  SelectBox.bottom = Box.bottom;
2539 
2540  if (labelSize.cx)
2541  SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2542  else
2543  SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2544  }
2545  else
2546  {
2547  UnionRect(&SelectBox, &Icon, &Label);
2548  }
2549  if (lprcSelectBox) *lprcSelectBox = SelectBox;
2550  TRACE(" - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2551  }
2552 
2553  /* Fix the Box if necessary */
2554  if (lprcBox)
2555  {
2556  if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2557  else *lprcBox = Box;
2558  }
2559  TRACE(" - box=%s\n", wine_dbgstr_rect(&Box));
2560 }
2561 
2562 /***
2563  * DESCRIPTION: [INTERNAL]
2564  *
2565  * PARAMETER(S):
2566  * [I] infoPtr : valid pointer to the listview structure
2567  * [I] nItem : item number
2568  * [O] lprcBox : ptr to Box rectangle
2569  *
2570  * RETURN:
2571  * None.
2572  */
2573 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2574 {
2575  WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2576  POINT Position, Origin;
2577  LVITEMW lvItem;
2578 
2579  LISTVIEW_GetOrigin(infoPtr, &Origin);
2580  LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2581 
2582  /* Be smart and try to figure out the minimum we have to do */
2583  lvItem.mask = 0;
2584  if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2585  lvItem.mask |= LVIF_TEXT;
2586  lvItem.iItem = nItem;
2587  lvItem.iSubItem = 0;
2588  lvItem.pszText = szDispText;
2589  lvItem.cchTextMax = DISP_TEXT_SIZE;
2590  if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2591  if (infoPtr->uView == LV_VIEW_ICON)
2592  {
2593  lvItem.mask |= LVIF_STATE;
2594  lvItem.stateMask = LVIS_FOCUSED;
2595  lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2596  }
2597  LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2598 
2599  if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2600  SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2601  {
2602  OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2603  }
2604  else
2605  OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2606 }
2607 
2608 /* LISTVIEW_MapIdToIndex helper */
2610 {
2611  ITEM_ID *id1 = (ITEM_ID*)p1;
2612  ITEM_ID *id2 = (ITEM_ID*)p2;
2613 
2614  if (id1->id == id2->id) return 0;
2615 
2616  return (id1->id < id2->id) ? -1 : 1;
2617 }
2618 
2619 /***
2620  * DESCRIPTION:
2621  * Returns the item index for id specified.
2622  *
2623  * PARAMETER(S):
2624  * [I] infoPtr : valid pointer to the listview structure
2625  * [I] iID : item id to get index for
2626  *
2627  * RETURN:
2628  * Item index, or -1 on failure.
2629  */
2630 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2631 {
2632  ITEM_ID ID;
2633  INT index;
2634 
2635  TRACE("iID=%d\n", iID);
2636 
2637  if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2638  if (infoPtr->nItemCount == 0) return -1;
2639 
2640  ID.id = iID;
2641  index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, MapIdSearchCompare, 0, DPAS_SORTED);
2642 
2643  if (index != -1)
2644  {
2645  ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2646  return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2647  }
2648 
2649  return -1;
2650 }
2651 
2652 /***
2653  * DESCRIPTION:
2654  * Returns the item id for index given.
2655  *
2656  * PARAMETER(S):
2657  * [I] infoPtr : valid pointer to the listview structure
2658  * [I] iItem : item index to get id for
2659  *
2660  * RETURN:
2661  * Item id.
2662  */
2663 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2664 {
2665  ITEM_INFO *lpItem;
2666  HDPA hdpaSubItems;
2667 
2668  TRACE("iItem=%d\n", iItem);
2669 
2670  if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2671  if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2672 
2673  hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2674  lpItem = DPA_GetPtr(hdpaSubItems, 0);
2675 
2676  return lpItem->id->id;
2677 }
2678 
2679 /***
2680  * DESCRIPTION:
2681  * Returns the current icon position, and advances it along the top.
2682  * The returned position is not offset by Origin.
2683  *
2684  * PARAMETER(S):
2685  * [I] infoPtr : valid pointer to the listview structure
2686  * [O] lpPos : will get the current icon position
2687  *
2688  * RETURN:
2689  * None
2690  */
2691 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2692 {
2693  INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2694 
2695  *lpPos = infoPtr->currIconPos;
2696 
2697  infoPtr->currIconPos.x += infoPtr->nItemWidth;
2698  if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2699 
2700  infoPtr->currIconPos.x = 0;
2701  infoPtr->currIconPos.y += infoPtr->nItemHeight;
2702 }
2703 
2704 
2705 /***
2706  * DESCRIPTION:
2707  * Returns the current icon position, and advances it down the left edge.
2708  * The returned position is not offset by Origin.
2709  *
2710  * PARAMETER(S):
2711  * [I] infoPtr : valid pointer to the listview structure
2712  * [O] lpPos : will get the current icon position
2713  *
2714  * RETURN:
2715  * None
2716  */
2717 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2718 {
2719  INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2720 
2721  *lpPos = infoPtr->currIconPos;
2722 
2723  infoPtr->currIconPos.y += infoPtr->nItemHeight;
2724  if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2725 
2726  infoPtr->currIconPos.x += infoPtr->nItemWidth;
2727  infoPtr->currIconPos.y = 0;
2728 }
2729 
2730 
2731 /***
2732  * DESCRIPTION:
2733  * Moves an icon to the specified position.
2734  * It takes care of invalidating the item, etc.
2735  *
2736  * PARAMETER(S):
2737  * [I] infoPtr : valid pointer to the listview structure
2738  * [I] nItem : the item to move
2739  * [I] lpPos : the new icon position
2740  * [I] isNew : flags the item as being new
2741  *
2742  * RETURN:
2743  * Success: TRUE
2744  * Failure: FALSE
2745  */
2746 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2747 {
2748  POINT old;
2749 
2750  if (!isNew)
2751  {
2752  old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2753  old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2754 
2755  if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2756  LISTVIEW_InvalidateItem(infoPtr, nItem);
2757  }
2758 
2759  /* Allocating a POINTER for every item is too resource intensive,
2760  * so we'll keep the (x,y) in different arrays */
2761  if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2762  if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2763 
2764  LISTVIEW_InvalidateItem(infoPtr, nItem);
2765 
2766  return TRUE;
2767 }
2768 
2769 /***
2770  * DESCRIPTION:
2771  * Arranges listview items in icon display mode.
2772  *
2773  * PARAMETER(S):
2774  * [I] infoPtr : valid pointer to the listview structure
2775  * [I] nAlignCode : alignment code
2776  *
2777  * RETURN:
2778  * SUCCESS : TRUE
2779  * FAILURE : FALSE
2780  */
2781 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2782 {
2783  void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2784  POINT pos;
2785  INT i;
2786 
2787  if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2788 
2789  TRACE("nAlignCode=%d\n", nAlignCode);
2790 
2791  if (nAlignCode == LVA_DEFAULT)
2792  {
2793  if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2794  else nAlignCode = LVA_ALIGNTOP;
2795  }
2796 
2797  switch (nAlignCode)
2798  {
2799  case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2800  case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2801  case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2802  default: return FALSE;
2803  }
2804 
2805  infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2806  for (i = 0; i < infoPtr->nItemCount; i++)
2807  {
2808  next_pos(infoPtr, &pos);
2809  LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2810  }
2811 
2812  return TRUE;
2813 }
2814 
2815 /***
2816  * DESCRIPTION:
2817  * Retrieves the bounding rectangle of all the items, not offset by Origin.
2818  * For LVS_REPORT always returns empty rectangle.
2819  *
2820  * PARAMETER(S):
2821  * [I] infoPtr : valid pointer to the listview structure
2822  * [O] lprcView : bounding rectangle
2823  *
2824  * RETURN:
2825  * SUCCESS : TRUE
2826  * FAILURE : FALSE
2827  */
2828 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2829 {
2830  INT i, x, y;
2831 
2832  SetRectEmpty(lprcView);
2833 
2834  switch (infoPtr->uView)
2835  {
2836  case LV_VIEW_ICON:
2837  case LV_VIEW_SMALLICON:
2838  for (i = 0; i < infoPtr->nItemCount; i++)
2839  {
2840  x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2841  y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2842  lprcView->right = max(lprcView->right, x);
2843  lprcView->bottom = max(lprcView->bottom, y);
2844  }
2845  if (infoPtr->nItemCount > 0)
2846  {
2847  lprcView->right += infoPtr->nItemWidth;
2848  lprcView->bottom += infoPtr->nItemHeight;
2849  }
2850  break;
2851 
2852  case LV_VIEW_LIST:
2853  y = LISTVIEW_GetCountPerColumn(infoPtr);
2854  x = infoPtr->nItemCount / y;
2855  if (infoPtr->nItemCount % y) x++;
2856  lprcView->right = x * infoPtr->nItemWidth;
2857  lprcView->bottom = y * infoPtr->nItemHeight;
2858  break;
2859  }
2860 }
2861 
2862 /***
2863  * DESCRIPTION:
2864  * Retrieves the bounding rectangle of all the items.
2865  *
2866  * PARAMETER(S):
2867  * [I] infoPtr : valid pointer to the listview structure
2868  * [O] lprcView : bounding rectangle
2869  *
2870  * RETURN:
2871  * SUCCESS : TRUE
2872  * FAILURE : FALSE
2873  */
2874 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2875 {
2876  POINT ptOrigin;
2877 
2878  TRACE("(lprcView=%p)\n", lprcView);
2879 
2880  if (!lprcView) return FALSE;
2881 
2882  LISTVIEW_GetAreaRect(infoPtr, lprcView);
2883 
2884  if (infoPtr->uView != LV_VIEW_DETAILS)
2885  {
2886  LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2887  OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2888  }
2889 
2890  TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2891 
2892  return TRUE;
2893 }
2894 
2895 /***
2896  * DESCRIPTION:
2897  * Retrieves the subitem pointer associated with the subitem index.
2898  *
2899  * PARAMETER(S):
2900  * [I] hdpaSubItems : DPA handle for a specific item
2901  * [I] nSubItem : index of subitem
2902  *
2903  * RETURN:
2904  * SUCCESS : subitem pointer
2905  * FAILURE : NULL
2906  */
2907 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2908 {
2909  SUBITEM_INFO *lpSubItem;
2910  INT i;
2911 
2912  /* we should binary search here if need be */
2913  for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2914  {
2915  lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2916  if (lpSubItem->iSubItem == nSubItem)
2917  return lpSubItem;
2918  }
2919 
2920  return NULL;
2921 }
2922 
2923 
2924 /***
2925  * DESCRIPTION:
2926  * Calculates the desired item width.
2927  *
2928  * PARAMETER(S):
2929  * [I] infoPtr : valid pointer to the listview structure
2930  *
2931  * RETURN:
2932  * The desired item width.
2933  */
2935 {
2936  INT nItemWidth = 0;
2937 
2938  TRACE("uView=%d\n", infoPtr->uView);
2939 
2940  if (infoPtr->uView == LV_VIEW_ICON)
2941  nItemWidth = infoPtr->iconSpacing.cx;
2942  else if (infoPtr->uView == LV_VIEW_DETAILS)
2943  {
2944  if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2945  {
2946  RECT rcHeader;
2947  INT index;
2948 
2949  index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2950  DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2951 
2952  LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2953  nItemWidth = rcHeader.right;
2954  }
2955  }
2956  else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2957  {
2958  WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2959  LVITEMW lvItem;
2960  INT i;
2961 
2962  lvItem.mask = LVIF_TEXT;
2963  lvItem.iSubItem = 0;
2964 
2965  for (i = 0; i < infoPtr->nItemCount; i++)
2966  {
2967  lvItem.iItem = i;
2968  lvItem.pszText = szDispText;
2969  lvItem.cchTextMax = DISP_TEXT_SIZE;
2970  if (LISTVIEW_GetItemW(infoPtr, &lvItem))
2971  nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
2972  nItemWidth);
2973  }
2974 
2975  if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
2976  if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2977 
2978  nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2979  }
2980 
2981  return nItemWidth;
2982 }
2983 
2984 /***
2985  * DESCRIPTION:
2986  * Calculates the desired item height.
2987  *
2988  * PARAMETER(S):
2989  * [I] infoPtr : valid pointer to the listview structure
2990  *
2991  * RETURN:
2992  * The desired item height.
2993  */
2995 {
2996  INT nItemHeight;
2997 
2998  TRACE("uView=%d\n", infoPtr->uView);
2999 
3000  if (infoPtr->uView == LV_VIEW_ICON)
3001  nItemHeight = infoPtr->iconSpacing.cy;
3002  else
3003  {
3004  nItemHeight = infoPtr->ntmHeight;
3005  if (infoPtr->himlState)
3006  nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
3007  if (infoPtr->himlSmall)
3008  nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
3009  nItemHeight += HEIGHT_PADDING;
3010  if (infoPtr->nMeasureItemHeight > 0)
3011  nItemHeight = infoPtr->nMeasureItemHeight;
3012  }
3013 
3014  return max(nItemHeight, 1);
3015 }
3016 
3017 /***
3018  * DESCRIPTION:
3019  * Updates the width, and height of an item.
3020  *
3021  * PARAMETER(S):
3022  * [I] infoPtr : valid pointer to the listview structure
3023  *
3024  * RETURN:
3025  * None.
3026  */
3027 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
3028 {
3029  infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
3030  infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
3031 }
3032 
3033 
3034 /***
3035  * DESCRIPTION:
3036  * Retrieves and saves important text metrics info for the current
3037  * Listview font.
3038  *
3039  * PARAMETER(S):
3040  * [I] infoPtr : valid pointer to the listview structure
3041  *
3042  */
3044 {
3045  HDC hdc = GetDC(infoPtr->hwndSelf);
3046  HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3047  HFONT hOldFont = SelectObject(hdc, hFont);
3048  TEXTMETRICW tm;
3049  SIZE sz;
3050 
3051  if (GetTextMetricsW(hdc, &tm))
3052  {
3053  infoPtr->ntmHeight = tm.tmHeight;
3054  infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3055  }
3056 
3057  if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3058  infoPtr->nEllipsisWidth = sz.cx;
3059 
3060  SelectObject(hdc, hOldFont);
3061  ReleaseDC(infoPtr->hwndSelf, hdc);
3062 
3063  TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3064 }
3065 
3066 /***
3067  * DESCRIPTION:
3068  * A compare function for ranges
3069  *
3070  * PARAMETER(S)
3071  * [I] range1 : pointer to range 1;
3072  * [I] range2 : pointer to range 2;
3073  * [I] flags : flags
3074  *
3075  * RETURNS:
3076  * > 0 : if range 1 > range 2
3077  * < 0 : if range 2 > range 1
3078  * = 0 : if range intersects range 2
3079  */
3081 {
3082  INT cmp;
3083 
3084  if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3085  cmp = -1;
3086  else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3087  cmp = 1;
3088  else
3089  cmp = 0;
3090 
3091  TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3092 
3093  return cmp;
3094 }
3095 
3096 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3097 
3098 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3099 {
3100  INT i;
3101  RANGE *prev, *curr;
3102 
3103  TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3104  assert (ranges);
3105  assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3106  ranges_dump(ranges);
3107  if (DPA_GetPtrCount(ranges->hdpa) > 0)
3108  {
3109  prev = DPA_GetPtr(ranges->hdpa, 0);
3110  assert (prev->lower >= 0 && prev->lower < prev->upper);
3111  for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3112  {
3113  curr = DPA_GetPtr(ranges->hdpa, i);
3114  assert (prev->upper <= curr->lower);
3115  assert (curr->lower < curr->upper);
3116  prev = curr;
3117  }
3118  }
3119  TRACE("--- Done checking---\n");
3120 }
3121 
3123 {
3124  RANGES ranges = Alloc(sizeof(struct tagRANGES));
3125  if (!ranges) return NULL;
3126  ranges->hdpa = DPA_Create(count);
3127  if (ranges->hdpa) return ranges;
3128  Free(ranges);
3129  return NULL;
3130 }
3131 
3132 static void ranges_clear(RANGES ranges)
3133 {
3134  INT i;
3135 
3136  for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3137  Free(DPA_GetPtr(ranges->hdpa, i));
3138  DPA_DeleteAllPtrs(ranges->hdpa);
3139 }
3140 
3141 
3142 static void ranges_destroy(RANGES ranges)
3143 {
3144  if (!ranges) return;
3145  ranges_clear(ranges);
3146  DPA_Destroy(ranges->hdpa);
3147  Free(ranges);
3148 }
3149 
3151 {
3152  RANGES clone;
3153  INT i;
3154 
3155  if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3156 
3157  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3158  {
3159  RANGE *newrng = Alloc(sizeof(RANGE));
3160  if (!newrng) goto fail;
3161  *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3162  if (!DPA_SetPtr(clone->hdpa, i, newrng))
3163  {
3164  Free(newrng);
3165  goto fail;
3166  }
3167  }
3168  return clone;
3169 
3170 fail:
3171  TRACE ("clone failed\n");
3172  ranges_destroy(clone);
3173  return NULL;
3174 }
3175 
3176 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3177 {
3178  INT i;
3179 
3180  for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3181  ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3182 
3183  return ranges;
3184 }
3185 
3186 static void ranges_dump(RANGES ranges)
3187 {
3188  INT i;
3189 
3190  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3191  TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3192 }
3193 
3194 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3195 {
3196  RANGE srchrng = { nItem, nItem + 1 };
3197 
3198  TRACE("(nItem=%d)\n", nItem);
3199  ranges_check(ranges, "before contain");
3200  return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3201 }
3202 
3204 {
3205  INT i, count = 0;
3206 
3207  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3208  {
3209  RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3210  count += sel->upper - sel->lower;
3211  }
3212 
3213  return count;
3214 }
3215 
3216 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3217 {
3218  RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3219  INT index;
3220 
3221  index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3222  if (index == -1) return TRUE;
3223 
3224  for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3225  {
3226  chkrng = DPA_GetPtr(ranges->hdpa, index);
3227  if (chkrng->lower >= nItem)
3228  chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3229  if (chkrng->upper > nItem)
3230  chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3231  }
3232  return TRUE;
3233 }
3234 
3236 {
3237  RANGE srchrgn;
3238  INT index;
3239 
3240  TRACE("(%s)\n", debugrange(&range));
3241  ranges_check(ranges, "before add");
3242 
3243  /* try find overlapping regions first */
3244  srchrgn.lower = range.lower - 1;
3245  srchrgn.upper = range.upper + 1;
3246  index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3247 
3248  if (index == -1)
3249  {
3250  RANGE *newrgn;
3251 
3252  TRACE("Adding new range\n");
3253 
3254  /* create the brand new range to insert */
3255  newrgn = Alloc(sizeof(RANGE));
3256  if(!newrgn) goto fail;
3257  *newrgn = range;
3258 
3259  /* figure out where to insert it */
3260  index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3261  TRACE("index=%d\n", index);
3262  if (index == -1) index = 0;
3263 
3264  /* and get it over with */
3265  if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3266  {
3267  Free(newrgn);
3268  goto fail;
3269  }
3270  }
3271  else
3272  {
3273  RANGE *chkrgn, *mrgrgn;
3274  INT fromindex, mergeindex;
3275 
3276  chkrgn = DPA_GetPtr(ranges->hdpa, index);
3277  TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3278 
3279  chkrgn->lower = min(range.lower, chkrgn->lower);
3280  chkrgn->upper = max(range.upper, chkrgn->upper);
3281 
3282  TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3283 
3284  /* merge now common ranges */
3285  fromindex = 0;
3286  srchrgn.lower = chkrgn->lower - 1;
3287  srchrgn.upper = chkrgn->upper + 1;
3288 
3289  do
3290  {
3291  mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3292  if (mergeindex == -1) break;
3293  if (mergeindex == index)
3294  {
3295  fromindex = index + 1;
3296  continue;
3297  }
3298 
3299  TRACE("Merge with index %i\n", mergeindex);
3300 
3301  mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3302  chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3303  chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3304  Free(mrgrgn);
3305  DPA_DeletePtr(ranges->hdpa, mergeindex);
3306  if (mergeindex < index) index --;
3307  } while(1);
3308  }
3309 
3310  ranges_check(ranges, "after add");
3311  return TRUE;
3312 
3313 fail:
3314  ranges_check(ranges, "failed add");
3315  return FALSE;
3316 }
3317 
3319 {
3320  RANGE *chkrgn;
3321  INT index;
3322 
3323  TRACE("(%s)\n", debugrange(&range));
3324  ranges_check(ranges, "before del");
3325 
3326  /* we don't use DPAS_SORTED here, since we need *
3327  * to find the first overlapping range */
3328  index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3329  while(index != -1)
3330  {
3331  chkrgn = DPA_GetPtr(ranges->hdpa, index);
3332 
3333  TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3334 
3335  /* case 1: Same range */
3336  if ( (chkrgn->upper == range.upper) &&
3337  (chkrgn->lower == range.lower) )
3338  {
3339  DPA_DeletePtr(ranges->hdpa, index);
3340  Free(chkrgn);
3341  break;
3342  }
3343  /* case 2: engulf */
3344  else if ( (chkrgn->upper <= range.upper) &&
3345  (chkrgn->lower >= range.lower) )
3346  {
3347  DPA_DeletePtr(ranges->hdpa, index);
3348  Free(chkrgn);
3349  }
3350  /* case 3: overlap upper */
3351  else if ( (chkrgn->upper <= range.upper) &&
3352  (chkrgn->lower < range.lower) )
3353  {
3354  chkrgn->upper = range.lower;
3355  }
3356  /* case 4: overlap lower */
3357  else if ( (chkrgn->upper > range.upper) &&
3358  (chkrgn->lower >= range.lower) )
3359  {
3360  chkrgn->lower = range.upper;
3361  break;
3362  }
3363  /* case 5: fully internal */
3364  else
3365  {
3366  RANGE *newrgn;
3367 
3368  if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3369  newrgn->lower = chkrgn->lower;
3370  newrgn->upper = range.lower;
3371  chkrgn->lower = range.upper;
3372  if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3373  {
3374  Free(newrgn);
3375  goto fail;
3376  }
3377  break;
3378  }
3379 
3380  index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3381  }
3382 
3383  ranges_check(ranges, "after del");
3384  return TRUE;
3385 
3386 fail:
3387  ranges_check(ranges, "failed del");
3388  return FALSE;
3389 }
3390 
3391 /***
3392 * DESCRIPTION:
3393 * Removes all selection ranges
3394 *
3395 * Parameters(s):
3396 * [I] infoPtr : valid pointer to the listview structure
3397 * [I] toSkip : item range to skip removing the selection
3398 *
3399 * RETURNS:
3400 * SUCCESS : TRUE
3401 * FAILURE : FALSE
3402 */
3404 {
3405  LVITEMW lvItem;
3406  ITERATOR i;
3407  RANGES clone;
3408 
3409  TRACE("()\n");
3410 
3411  lvItem.state = 0;
3412  lvItem.stateMask = LVIS_SELECTED;
3413 
3414  /* need to clone the DPA because callbacks can change it */
3415  if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3416  iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3417  while(iterator_next(&i))
3418  LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3419  /* note that the iterator destructor will free the cloned range */
3420  iterator_destroy(&i);
3421 
3422  return TRUE;
3423 }
3424 
3425 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3426 {
3427  RANGES toSkip;
3428 
3429  if (!(toSkip = ranges_create(1))) return FALSE;
3430  if (nItem != -1) ranges_additem(toSkip, nItem);
3431  LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3432  ranges_destroy(toSkip);
3433  return TRUE;
3434 }
3435 
3436 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3437 {
3438  return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3439 }
3440 
3441 /***
3442  * DESCRIPTION:
3443  * Retrieves the number of items that are marked as selected.
3444  *
3445  * PARAMETER(S):
3446  * [I] infoPtr : valid pointer to the listview structure
3447  *
3448  * RETURN:
3449  * Number of items selected.
3450  */
3452 {
3453  INT nSelectedCount = 0;
3454 
3455  if (infoPtr->uCallbackMask & LVIS_SELECTED)
3456  {
3457  INT i;
3458  for (i = 0; i < infoPtr->nItemCount; i++)
3459  {
3460  if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3461  nSelectedCount++;
3462  }
3463  }
3464  else
3465  nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3466 
3467  TRACE("nSelectedCount=%d\n", nSelectedCount);
3468  return nSelectedCount;
3469 }
3470 
3471 /***
3472  * DESCRIPTION:
3473  * Manages the item focus.
3474  *
3475  * PARAMETER(S):
3476  * [I] infoPtr : valid pointer to the listview structure
3477  * [I] nItem : item index
3478  *
3479  * RETURN:
3480  * TRUE : focused item changed
3481  * FALSE : focused item has NOT changed
3482  */
3483 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3484 {
3485  INT oldFocus = infoPtr->nFocusedItem;
3486  LVITEMW lvItem;
3487 
3488  if (nItem == infoPtr->nFocusedItem) return FALSE;
3489 
3490  lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3491  lvItem.stateMask = LVIS_FOCUSED;
3492  LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3493 
3494  return oldFocus != infoPtr->nFocusedItem;
3495 }
3496 
3497 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3498 {
3499  if (nShiftItem < nItem) return nShiftItem;
3500 
3501  if (nShiftItem > nItem) return nShiftItem + direction;
3502 
3503  if (direction > 0) return nShiftItem + direction;
3504 
3505  return min(nShiftItem, infoPtr->nItemCount - 1);
3506 }
3507 
3508 /* This function updates focus index.
3509 
3510 Parameters:
3511  focus : current focus index
3512  item : index of item to be added/removed
3513  direction : add/remove flag
3514 */
3515 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3516 {
3517  BOOL old_change = infoPtr->bDoChangeNotify;
3518 
3519  infoPtr->bDoChangeNotify = FALSE;
3520  focus = shift_item(infoPtr, focus, item, direction);
3521  if (focus != infoPtr->nFocusedItem)
3522  LISTVIEW_SetItemFocus(infoPtr, focus);
3523  infoPtr->bDoChangeNotify = old_change;
3524 }
3525 
3538 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3539 {
3540  TRACE("Shifting %i, %i steps\n", nItem, direction);
3541 
3542  ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3543  assert(abs(direction) == 1);
3544  infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3545 
3546  /* But we are not supposed to modify nHotItem! */
3547 }
3548 
3561 {
3562  INT nFirst = min(infoPtr->nSelectionMark, nItem);
3563  INT nLast = max(infoPtr->nSelectionMark, nItem);
3564  HWND hwndSelf = infoPtr->hwndSelf;
3565  NMLVODSTATECHANGE nmlv;
3566  LVITEMW item;
3567  BOOL bOldChange;
3568  INT i;
3569 
3570  /* Temporarily disable change notification
3571  * If the control is LVS_OWNERDATA, we need to send
3572  * only one LVN_ODSTATECHANGED notification.
3573  * See MSDN documentation for LVN_ITEMCHANGED.
3574  */
3575  bOldChange = infoPtr->bDoChangeNotify;
3576  if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3577 
3578  if (nFirst == -1) nFirst = nItem;
3579 
3580  item.state = LVIS_SELECTED;
3581  item.stateMask = LVIS_SELECTED;
3582 
3583  for (i = nFirst; i <= nLast; i++)
3584  LISTVIEW_SetItemState(infoPtr,i,&item);
3585 
3586  ZeroMemory(&nmlv, sizeof(nmlv));
3587  nmlv.iFrom = nFirst;
3588  nmlv.iTo = nLast;
3589  nmlv.uOldState = 0;
3590  nmlv.uNewState = item.state;
3591 
3592  notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3593  if (!IsWindow(hwndSelf))
3594  return FALSE;
3595  infoPtr->bDoChangeNotify = bOldChange;
3596  return TRUE;
3597 }
3598 
3599 
3600 /***
3601  * DESCRIPTION:
3602  * Sets a single group selection.
3603  *
3604  * PARAMETER(S):
3605  * [I] infoPtr : valid pointer to the listview structure
3606  * [I] nItem : item index
3607  *
3608  * RETURN:
3609  * None
3610  */
3611 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3612 {
3613  RANGES selection;
3614  LVITEMW item;
3615  ITERATOR i;
3616  BOOL bOldChange;
3617 
3618  if (!(selection = ranges_create(100))) return;
3619 
3620  item.state = LVIS_SELECTED;
3621  item.stateMask = LVIS_SELECTED;
3622 
3623  if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3624  {
3625  if (infoPtr->nSelectionMark == -1)
3626  {
3627  infoPtr->nSelectionMark = nItem;
3628  ranges_additem(selection, nItem);
3629  }
3630  else
3631  {
3632  RANGE sel;
3633 
3634  sel.lower = min(infoPtr->nSelectionMark, nItem);
3635  sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3636  ranges_add(selection, sel);
3637  }
3638  }
3639  else
3640  {
3641  RECT rcItem, rcSel, rcSelMark;
3642  POINT ptItem;
3643 
3644  rcItem.left = LVIR_BOUNDS;
3645  if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3646  ranges_destroy (selection);
3647  return;
3648  }
3649  rcSelMark.left = LVIR_BOUNDS;
3650  if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3651  ranges_destroy (selection);
3652  return;
3653  }
3654  UnionRect(&rcSel, &rcItem, &rcSelMark);
3655  iterator_frameditems(&i, infoPtr, &rcSel);
3656  while(iterator_next(&i))
3657  {
3658  LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3659  if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3660  }
3661  iterator_destroy(&i);
3662  }
3663 
3664  /* disable per item notifications on LVS_OWNERDATA style
3665  FIXME: single LVN_ODSTATECHANGED should be used */
3666  bOldChange = infoPtr->bDoChangeNotify;
3667  if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3668 
3669  LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
3670 
3671 
3672  iterator_rangesitems(&i, selection);
3673  while(iterator_next(&i))
3674  LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3675  /* this will also destroy the selection */
3676  iterator_destroy(&i);
3677 
3678  infoPtr->bDoChangeNotify = bOldChange;
3679 
3680  LISTVIEW_SetItemFocus(infoPtr, nItem);
3681 }
3682 
3683 /***
3684  * DESCRIPTION:
3685  * Sets a single selection.
3686  *
3687  * PARAMETER(S):
3688  * [I] infoPtr : valid pointer to the listview structure
3689  * [I] nItem : item index
3690  *
3691  * RETURN:
3692  * None
3693  */
3694 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3695 {
3696  LVITEMW lvItem;
3697 
3698  TRACE("nItem=%d\n", nItem);
3699 
3700  LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3701 
3702  lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3704  LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3705 
3706  infoPtr->nSelectionMark = nItem;
3707 }
3708 
3709 /***
3710  * DESCRIPTION:
3711  * Set selection(s) with keyboard.
3712  *
3713  * PARAMETER(S):
3714  * [I] infoPtr : valid pointer to the listview structure
3715  * [I] nItem : item index
3716  * [I] space : VK_SPACE code sent
3717  *
3718  * RETURN:
3719  * SUCCESS : TRUE (needs to be repainted)
3720  * FAILURE : FALSE (nothing has changed)
3721  */
3723 {
3724  /* FIXME: pass in the state */
3725  WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3726  WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3727  BOOL bResult = FALSE;
3728 
3729  TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3730  if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3731  {
3732  bResult = TRUE;
3733 
3734  if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3735  LISTVIEW_SetSelection(infoPtr, nItem);
3736  else
3737  {
3738  if (wShift)
3739  LISTVIEW_SetGroupSelection(infoPtr, nItem);
3740  else if (wCtrl)
3741  {
3742  LVITEMW lvItem;
3743  lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3744  lvItem.stateMask = LVIS_SELECTED;
3745  if (space)
3746  {
3747  LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3748  if (lvItem.state & LVIS_SELECTED)
3749  infoPtr->nSelectionMark = nItem;
3750  }
3751  bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3752  }
3753  }
3754  LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3755  }
3756 
3757  UpdateWindow(infoPtr->hwndSelf); /* update client area */
3758  return bResult;
3759 }
3760 
3761 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3762 {
3763  LVHITTESTINFO lvHitTestInfo;
3764 
3765  ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3766  lvHitTestInfo.pt.x = pt.x;
3767  lvHitTestInfo.pt.y = pt.y;
3768 
3769  LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3770 
3771  lpLVItem->mask = LVIF_PARAM;
3772  lpLVItem->iItem = lvHitTestInfo.iItem;
3773  lpLVItem->iSubItem = 0;
3774 
3775  return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3776 }
3777 
3778 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3779 {
3780  return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3781  (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3782  (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3783 }
3784 
3785 /***
3786  * DESCRIPTION:
3787  * Called when the mouse is being actively tracked and has hovered for a specified
3788  * amount of time
3789  *
3790  * PARAMETER(S):
3791  * [I] infoPtr : valid pointer to the listview structure
3792  * [I] fwKeys : key indicator
3793  * [I] x,y : mouse position
3794  *
3795  * RETURN:
3796  * 0 if the message was processed, non-zero if there was an error
3797  *
3798  * INFO:
3799  * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3800  * over the item for a certain period of time.
3801  *
3802  */
3804 {
3805  NMHDR hdr;
3806 
3807  if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3808 
3809  if (LISTVIEW_IsHotTracking(infoPtr))
3810  {
3811  LVITEMW item;
3812  POINT pt;
3813 
3814  pt.x = x;
3815  pt.y = y;
3816 
3817  if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3818  LISTVIEW_SetSelection(infoPtr, item.iItem);
3819 
3820  SetFocus(infoPtr->hwndSelf);
3821  }
3822 
3823  return 0;
3824 }
3825 
3826 #define SCROLL_LEFT 0x1
3827 #define SCROLL_RIGHT 0x2
3828 #define SCROLL_UP 0x4
3829 #define SCROLL_DOWN 0x8
3830 
3831 /***
3832  * DESCRIPTION:
3833  * Utility routine to draw and highlight items within a marquee selection rectangle.
3834  *
3835  * PARAMETER(S):
3836  * [I] infoPtr : valid pointer to the listview structure
3837  * [I] coords_orig : original co-ordinates of the cursor
3838  * [I] coords_offs : offsetted coordinates of the cursor
3839  * [I] offset : offset amount
3840  * [I] scroll : Bitmask of which directions we should scroll, if at all
3841  *
3842  * RETURN:
3843  * None.
3844  */
3845 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3846  const POINT *coords_offs, const POINT *offset,
3847  INT scroll)
3848 {
3849  BOOL controlDown = FALSE;
3850  LVITEMW item;
3851  ITERATOR old_elems, new_elems;
3852  RECT rect;
3853 
3854  if (coords_offs->x > infoPtr->marqueeOrigin.x)
3855  {
3856  rect.left = infoPtr->marqueeOrigin.x;
3857  rect.right = coords_offs->x;
3858  }
3859  else
3860  {
3861  rect.left = coords_offs->x;
3862  rect.right = infoPtr->marqueeOrigin.x;
3863  }
3864 
3865  if (coords_offs->y > infoPtr->marqueeOrigin.y)
3866  {
3867  rect.top = infoPtr->marqueeOrigin.y;
3868  rect.bottom = coords_offs->y;
3869  }
3870  else
3871  {
3872  rect.top = coords_offs->y;
3873  rect.bottom = infoPtr->marqueeOrigin.y;
3874  }
3875 
3876  /* Cancel out the old marquee rectangle and draw the new one */
3877  LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3878 
3879  /* Scroll by the appropriate distance if applicable - speed up scrolling as
3880  the cursor is further away */
3881 
3882  if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3883  LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3884 
3885  if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3886  LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3887 
3888  if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3889  LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3890 
3891  if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3892  LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3893 
3894  iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3895 
3896  infoPtr->marqueeRect = rect;
3897  infoPtr->marqueeDrawRect = rect;
3898  OffsetRect(&infoPtr->marqueeDrawRect, offset->x, offset->y);
3899 
3900  iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3901  iterator_remove_common_items(&old_elems, &new_elems);
3902 
3903  /* Iterate over no longer selected items */
3904  while (iterator_next(&old_elems))
3905  {
3906  if (old_elems.nItem > -1)
3907  {
3908  if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3909  item.state = 0;
3910  else
3911  item.state = LVIS_SELECTED;
3912 
3913  item.stateMask = LVIS_SELECTED;
3914 
3915  LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3916  }
3917  }
3918  iterator_destroy(&old_elems);
3919 
3920 
3921  /* Iterate over newly selected items */
3922  if (GetKeyState(VK_CONTROL) & 0x8000)
3923  controlDown = TRUE;
3924 
3925  while (iterator_next(&new_elems))
3926  {
3927  if (new_elems.nItem > -1)
3928  {
3929  /* If CTRL is pressed, invert. If not, always select the item. */
3930  if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3931  item.state = 0;
3932  else
3933  item.state = LVIS_SELECTED;
3934 
3935  item.stateMask = LVIS_SELECTED;
3936 
3937  LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3938  }
3939  }
3940  iterator_destroy(&new_elems);
3941 
3942  LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3943 }
3944 
3945 /***
3946  * DESCRIPTION:
3947  * Called when we are in a marquee selection that involves scrolling the listview (ie,
3948  * the cursor is outside the bounds of the client area). This is a TIMERPROC.
3949  *
3950  * PARAMETER(S):
3951  * [I] hwnd : Handle to the listview
3952  * [I] uMsg : WM_TIMER (ignored)
3953  * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
3954  * [I] dwTimer : The elapsed time (ignored)
3955  *
3956  * RETURN:
3957  * None.
3958  */
3960 {
3961  LISTVIEW_INFO *infoPtr;
3962  SCROLLINFO scrollInfo;
3963  POINT coords_orig;
3964  POINT coords_offs;
3965  POINT offset;
3966  INT scroll = 0;
3967 
3968  infoPtr = (LISTVIEW_INFO *) idEvent;
3969 
3970  if (!infoPtr)
3971  return;
3972 
3973  /* Get the current cursor position and convert to client coordinates */
3974  GetCursorPos(&coords_orig);
3975  ScreenToClient(hWnd, &coords_orig);
3976 
3977  /* Ensure coordinates are within client bounds */
3978  coords_offs.x = max(min(coords_orig.x, infoPtr->rcList.right), 0);
3979  coords_offs.y = max(min(coords_orig.y, infoPtr->rcList.bottom), 0);
3980 
3981  /* Get offset */
3982  LISTVIEW_GetOrigin(infoPtr, &offset);
3983 
3984  /* Offset coordinates by the appropriate amount */
3985  coords_offs.x -= offset.x;
3986  coords_offs.y -= offset.y;
3987 
3988  scrollInfo.cbSize = sizeof(SCROLLINFO);
3989  scrollInfo.fMask = SIF_ALL;
3990 
3991  /* Work out in which directions we can scroll */
3992  if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
3993  {
3994  if (scrollInfo.nPos != scrollInfo.nMin)
3995  scroll |= SCROLL_UP;
3996 
3997  if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
3998  scroll |= SCROLL_DOWN;
3999  }
4000 
4001  if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4002  {
4003  if (scrollInfo.nPos != scrollInfo.nMin)
4004  scroll |= SCROLL_LEFT;
4005 
4006  if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
4007  scroll |= SCROLL_RIGHT;
4008  }
4009 
4010  if (((coords_orig.x <= 0) && (scroll & SCROLL_LEFT)) ||
4011  ((coords_orig.y <= 0) && (scroll & SCROLL_UP)) ||
4012  ((coords_orig.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
4013  ((coords_orig.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
4014  {
4015  LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, scroll);
4016  }
4017 }
4018 
4019 /***
4020  * DESCRIPTION:
4021  * Called whenever WM_MOUSEMOVE is received.
4022  *
4023  * PARAMETER(S):
4024  * [I] infoPtr : valid pointer to the listview structure
4025  * [I] fwKeys : key indicator
4026  * [I] x,y : mouse position
4027  *
4028  * RETURN:
4029  * 0 if the message is processed, non-zero if there was an error
4030  */
4032 {
4033  LVHITTESTINFO ht;
4034  RECT rect;
4035  POINT pt;
4036 
4037  if (!(fwKeys & MK_LBUTTON))
4038  infoPtr->bLButtonDown = FALSE;
4039 
4040  if (infoPtr->bLButtonDown)
4041  {
4042  rect.left = rect.right = infoPtr->ptClickPos.x;
4043  rect.top = rect.bottom = infoPtr->ptClickPos.y;
4044 
4046 
4047  if (infoPtr->bMarqueeSelect)
4048  {
4049  POINT coords_orig;
4050  POINT coords_offs;
4051  POINT offset;
4052 
4053  coords_orig.x = x;
4054  coords_orig.y = y;
4055 
4056  /* Get offset */
4057  LISTVIEW_GetOrigin(infoPtr, &offset);
4058 
4059  /* Ensure coordinates are within client bounds */
4060  coords_offs.x = max(min(x, infoPtr->rcList.right), 0);
4061  coords_offs.y = max(min(y, infoPtr->rcList.bottom), 0);
4062 
4063  /* Offset coordinates by the appropriate amount */
4064  coords_offs.x -= offset.x;
4065  coords_offs.y -= offset.y;
4066 
4067  /* Enable the timer if we're going outside our bounds, in case the user doesn't
4068  move the mouse again */
4069 
4070  if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4071  (y >= infoPtr->rcList.bottom))
4072  {
4073  if (!infoPtr->bScrolling)
4074  {
4075  infoPtr->bScrolling = TRUE;
4076  SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4077  }
4078  }
4079  else
4080  {
4081  infoPtr->bScrolling = FALSE;
4082  KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4083  }
4084 
4085  LISTVIEW_MarqueeHighlight(infoPtr, &coords_orig, &coords_offs, &offset, 0);
4086  return 0;
4087  }
4088 
4089  pt.x = x;
4090  pt.y = y;
4091 
4092  ht.pt = pt;
4093  LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4094 
4095  /* reset item marker */
4096  if (infoPtr->nLButtonDownItem != ht.iItem)
4097  infoPtr->nLButtonDownItem = -1;
4098 
4099  if (!PtInRect(&rect, pt))
4100  {
4101  /* this path covers the following:
4102  1. WM_LBUTTONDOWN over selected item (sets focus on it)
4103  2. change focus with keys
4104  3. move mouse over item from step 1 selects it and moves focus on it */
4105  if (infoPtr->nLButtonDownItem != -1 &&
4107  {
4108  LVITEMW lvItem;
4109 
4110  lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4112 
4113  LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4114  infoPtr->nLButtonDownItem = -1;
4115  }
4116 
4117  if (!infoPtr->bDragging)
4118  {
4119  ht.pt = infoPtr->ptClickPos;
4120  LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4121 
4122  /* If the click is outside the range of an item, begin a
4123  highlight. If not, begin an item drag. */
4124  if (ht.iItem == -1)
4125  {
4126  NMHDR hdr;
4127 
4128  /* If we're allowing multiple selections, send notification.
4129  If return value is non-zero, cancel. */
4130  if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4131  {
4132  /* Store the absolute coordinates of the click */
4133  POINT offset;
4134  LISTVIEW_GetOrigin(infoPtr, &offset);
4135 
4136  infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4137  infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4138 
4139  /* Begin selection and capture mouse */
4140  infoPtr->bMarqueeSelect = TRUE;
4141  SetCapture(infoPtr->hwndSelf);
4142  }
4143  }
4144  else
4145  {
4146  NMLISTVIEW nmlv;
4147 
4148  ZeroMemory(&nmlv, sizeof(nmlv));
4149  nmlv.iItem = ht.iItem;
4150  nmlv.ptAction = infoPtr->ptClickPos;
4151 
4152  notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4153  infoPtr->bDragging = TRUE;
4154  }
4155  }
4156 
4157  return 0;
4158  }
4159  }
4160 
4161  /* see if we are supposed to be tracking mouse hovering */
4162  if (LISTVIEW_IsHotTracking(infoPtr)) {
4163  TRACKMOUSEEVENT trackinfo;
4164  DWORD flags;
4165 
4166  trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4167  trackinfo.dwFlags = TME_QUERY;
4168 
4169  /* see if we are already tracking this hwnd */
4170  _TrackMouseEvent(&trackinfo);
4171 
4172  flags = TME_LEAVE;
4173  if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4174  flags |= TME_HOVER;
4175 
4176  if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4177  trackinfo.dwFlags = flags;
4178  trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4179  trackinfo.hwndTrack = infoPtr->hwndSelf;
4180 
4181  /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4182  _TrackMouseEvent(&trackinfo);
4183  }
4184  }
4185 
4186  return 0;
4187 }
4188 
4189 
4190 /***
4191  * Tests whether the item is assignable to a list with style lStyle
4192  */
4193 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4194 {
4195  if ( (lpLVItem->mask & LVIF_TEXT) &&
4196  (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4197  (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4198 
4199  return TRUE;
4200 }
4201 
4202 
4203 /***
4204  * DESCRIPTION:
4205  * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4206  *
4207  * PARAMETER(S):
4208  * [I] infoPtr : valid pointer to the listview structure
4209  * [I] lpLVItem : valid pointer to new item attributes
4210  * [I] isNew : the item being set is being inserted
4211  * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4212  * [O] bChanged : will be set to TRUE if the item really changed
4213  *
4214  * RETURN:
4215  * SUCCESS : TRUE
4216  * FAILURE : FALSE
4217  */
4218 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4219 {
4220  ITEM_INFO *lpItem;
4221  NMLISTVIEW nmlv;
4222  UINT uChanged = 0;
4223  LVITEMW item;
4224  /* stateMask is ignored for LVM_INSERTITEM */
4225  UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4226 
4227  TRACE("()\n");
4228 
4229  assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4230 
4231  if (lpLVItem->mask == 0) return TRUE;
4232 
4233  if (infoPtr->dwStyle & LVS_OWNERDATA)
4234  {
4235  /* a virtual listview only stores selection and focus */
4236  if (lpLVItem->mask & ~LVIF_STATE)
4237  return FALSE;
4238  lpItem = NULL;
4239  }
4240  else
4241  {
4242  HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4243  lpItem = DPA_GetPtr(hdpaSubItems, 0);
4244  assert (lpItem);
4245  }
4246 
4247  /* we need to get the lParam and state of the item */
4248  item.iItem = lpLVItem->iItem;
4249  item.iSubItem = lpLVItem->iSubItem;
4250  item.mask = LVIF_STATE | LVIF_PARAM;
4251  item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4252 
4253  item.state = 0;
4254  item.lParam = 0;
4255  if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4256 
4257  TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4258  /* determine what fields will change */
4259  if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4260  uChanged |= LVIF_STATE;
4261 
4262  if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4263  uChanged |= LVIF_IMAGE;
4264 
4265  if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4266  uChanged |= LVIF_PARAM;
4267 
4268  if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4269  uChanged |= LVIF_INDENT;
4270 
4271  if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4272  uChanged |= LVIF_TEXT;
4273 
4274  TRACE("change mask=0x%x\n", uChanged);
4275 
4276  memset(&nmlv, 0, sizeof(NMLISTVIEW));
4277  nmlv.iItem = lpLVItem->iItem;
4278  nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4279  nmlv.uOldState = item.state;
4280  nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4281  nmlv.lParam = item.lParam;
4282 
4283  /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4284  and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4285  are enabled. Even nothing really changed we still need to send this,
4286  in this case uChanged mask is just set to passed item mask. */
4287  if(lpItem && !isNew && infoPtr->bDoChangeNotify)
4288  {
4289  HWND hwndSelf = infoPtr->hwndSelf;
4290 
4291  if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4292  return FALSE;
4293  if (!IsWindow(hwndSelf))
4294  return FALSE;
4295  }
4296 
4297  /* When item is inserted we need to shift existing focus index if new item has lower index. */
4298  if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4299  /* this means we won't hit a focus change path later */
4300  ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4301  {
4302  if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4303  infoPtr->nFocusedItem++;
4304  }
4305 
4306  if (!uChanged) return TRUE;
4307  *bChanged = TRUE;
4308 
4309  /* copy information */
4310  if (lpLVItem->mask & LVIF_TEXT)
4311  textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4312 
4313  if (lpLVItem->mask & LVIF_IMAGE)
4314  lpItem->hdr.iImage = lpLVItem->iImage;
4315 
4316  if (lpLVItem->mask & LVIF_PARAM)
4317  lpItem->lParam = lpLVItem->lParam;
4318 
4319  if (lpLVItem->mask & LVIF_INDENT)
4320  lpItem->iIndent = lpLVItem->iIndent;
4321 
4322  if (uChanged & LVIF_STATE)
4323  {
4324  if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4325  {
4326  lpItem->state &= ~stateMask;
4327  lpItem->state |= (lpLVItem->state & stateMask);
4328  }
4329  if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4330  {
4331  if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4332  ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4333  }
4334  else if (stateMask & LVIS_SELECTED)
4335  {
4336  ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4337  }
4338  /* If we are asked to change focus, and we manage it, do it.
4339  It's important to have all new item data stored at this point,
4340  because changing existing focus could result in a redrawing operation,
4341  which in turn could ask for disp data, application should see all data
4342  for inserted item when processing LVN_GETDISPINFO.
4343 
4344  The way this works application will see nested item change notifications -
4345  changed item notifications interrupted by ones from item losing focus. */
4346  if (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED)
4347  {
4348  if (lpLVItem->state & LVIS_FOCUSED)
4349  {
4350  /* update selection mark */
4351  if (infoPtr->nFocusedItem == -1 && infoPtr->nSelectionMark == -1)
4352  infoPtr->nSelectionMark = lpLVItem->iItem;
4353 
4354  if (infoPtr->nFocusedItem != -1)
4355  {
4356  /* remove current focus */
4357  item.mask = LVIF_STATE;
4358  item.state = 0;
4359  item.stateMask = LVIS_FOCUSED;
4360 
4361  /* recurse with redrawing an item */
4362  LISTVIEW_SetItemState(infoPtr, infoPtr->nFocusedItem, &item);
4363  }
4364 
4365  infoPtr->nFocusedItem = lpLVItem->iItem;
4366  LISTVIEW_EnsureVisible(infoPtr, lpLVItem->iItem, infoPtr->uView == LV_VIEW_LIST);
4367  }
4368  else if (infoPtr->nFocusedItem == lpLVItem->iItem)
4369  {
4370  infoPtr->nFocusedItem = -1;
4371  }
4372  }
4373  }
4374 
4375  /* if we're inserting the item, we're done */
4376  if (isNew) return TRUE;
4377 
4378  /* send LVN_ITEMCHANGED notification */
4379  if (lpLVItem->mask & LVIF_PARAM) nmlv.lParam = lpLVItem->lParam;
4380  if (infoPtr->bDoChangeNotify) notify_listview(infoPtr, LVN_ITEMCHANGED, &nmlv);
4381 
4382  return TRUE;
4383 }
4384 
4385 /***
4386  * DESCRIPTION:
4387  * Helper for LISTVIEW_{Set,Insert}ItemT *only*: sets subitem attributes.
4388  *
4389  * PARAMETER(S):
4390  * [I] infoPtr : valid pointer to the listview structure
4391  * [I] lpLVItem : valid pointer to new subitem attributes
4392  * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4393  * [O] bChanged : will be set to TRUE if the item really changed
4394  *
4395  * RETURN:
4396  * SUCCESS : TRUE
4397  * FAILURE : FALSE
4398  */
4399 static BOOL set_sub_item(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isW, BOOL *bChanged)
4400 {
4401  HDPA hdpaSubItems;
4402  SUBITEM_INFO *lpSubItem;
4403 
4404  /* we do not support subitems for virtual listviews */
4405  if (infoPtr->dwStyle & LVS_OWNERDATA) return FALSE;
4406 
4407  /* set subitem only if column is present */
4408  if (lpLVItem->iSubItem >= DPA_GetPtrCount(infoPtr->hdpaColumns)) return FALSE;
4409 
4410  /* First do some sanity checks */
4411  /* The LVIF_STATE flag is valid for subitems, but does not appear to be
4412  particularly useful. We currently do not actually do anything with
4413  the flag on subitems.
4414  */
4415  if (lpLVItem->mask & ~(LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_DI_SETITEM)) return FALSE;
4416  if (!(lpLVItem->mask & (LVIF_TEXT | LVIF_IMAGE | LVIF_STATE))) return TRUE;
4417 
4418  /* get the subitem structure, and create it if not there */
4419  hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4420  assert (hdpaSubItems);
4421 
4422  lpSubItem = LISTVIEW_GetSubItemPtr(hdpaSubItems, lpLVItem->iSubItem);
4423  if (!lpSubItem)
4424  {
4425  SUBITEM_INFO *tmpSubItem;
4426  INT i;
4427 
4428  lpSubItem = Alloc(sizeof(SUBITEM_INFO));
4429  if (!lpSubItem) return FALSE;
4430  /* we could binary search here, if need be...*/
4431  for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
4432  {
4433  tmpSubItem = DPA_GetPtr(hdpaSubItems, i);
4434  if (tmpSubItem->iSubItem > lpLVItem->iSubItem) break;
4435  }
4436  if (DPA_InsertPtr(hdpaSubItems, i, lpSubItem) == -1)
4437  {
4438  Free(lpSubItem);
4439  return FALSE;
4440  }
4441  lpSubItem->iSubItem = lpLVItem->iSubItem;
4442  lpSubItem->hdr.iImage = I_IMAGECALLBACK;
4443  *bChanged = TRUE;
4444  }
4445 
4446  if ((lpLVItem->mask & LVIF_IMAGE) && (lpSubItem->hdr.iImage != lpLVItem->iImage))
4447  {
4448  lpSubItem->hdr.iImage = lpLVItem->iImage;
4449  *bChanged = TRUE;
4450  }
4451 
4452  if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpSubItem->hdr.pszText, lpLVItem->pszText, isW))
4453  {
4454  textsetptrT(&lpSubItem->hdr.pszText, lpLVItem->pszText, isW);
4455  *bChanged = TRUE;
4456  }
4457 
4458  return TRUE;
4459 }
4460 
4461 /***
4462  * DESCRIPTION:
4463  * Sets item attributes.
4464  *
4465  * PARAMETER(S):
4466  * [I] infoPtr : valid pointer to the listview structure
4467  * [I] lpLVItem : new item attributes
4468  * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4469  *
4470  * RETURN:
4471  * SUCCESS : TRUE
4472  * FAILURE : FALSE
4473  */
4474 static BOOL LISTVIEW_SetItemT(LISTVIEW_INFO *infoPtr, LVITEMW *lpLVItem, BOOL isW)
4475 {
4476  HWND hwndSelf = infoPtr->hwndSelf;
4477  LPWSTR pszText = NULL;
4478  BOOL bResult, bChanged = FALSE;
4479  RECT oldItemArea;
4480 
4481  TRACE("(lpLVItem=%s, isW=%d)\n", debuglvitem_t(lpLVItem, isW), isW);
4482 
4483  if (!lpLVItem || lpLVItem->iItem < 0 || lpLVItem->iItem >= infoPtr->nItemCount)
4484  return FALSE;
4485 
4486  /* Store old item area */
4487  LISTVIEW_GetItemBox(infoPtr, lpLVItem->iItem, &oldItemArea);
4488 
4489  /* For efficiency, we transform the lpLVItem->pszText to Unicode here */
4490  if ((lpLVItem->mask & LVIF_TEXT) && is_text(lpLVItem->pszText))
4491  {
4492  pszText = lpLVItem->pszText;
4493  lpLVItem->pszText = textdupTtoW(lpLVItem->pszText, isW);
4494  }
4495 
4496  /* actually set the fields */
4497  if (!is_assignable_item(lpLVItem, infoPtr->dwStyle)) return FALSE;
4498 
4499  if (lpLVItem->iSubItem)
4500  bResult = set_sub_item(infoPtr, lpLVItem, TRUE, &bChanged);
4501  else
4502  bResult = set_main_item(infoPtr, lpLVItem, FALSE, TRUE, &bChanged);
4503  if (!IsWindow(hwndSelf))
4504  return FALSE;
4505 
4506  /* redraw item, if necessary */
4507  if (bChanged && !infoPtr->bIsDrawing)
4508  {
4509  /* this little optimization eliminates some nasty flicker */
4510  if ( infoPtr->uView == LV_VIEW_DETAILS && !(infoPtr->dwStyle & LVS_OWNERDRAWFIXED) &&
4511  !(infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT) &&
4512  lpLVItem->iSubItem > 0 && lpLVItem->iSubItem <= DPA_GetPtrCount(infoPtr->hdpaColumns) )
4513  LISTVIEW_InvalidateSubItem(infoPtr, lpLVItem->iItem, lpLVItem->iSubItem);
4514  else
4515  {
4516  LISTVIEW_InvalidateRect(infoPtr, &oldItemArea);
4517  LISTVIEW_InvalidateItem(infoPtr, lpLVItem->iItem);
4518  }
4519  }
4520  /* restore text */
4521  if (pszText)
4522  {
4523  textfreeT(lpLVItem->pszText, isW);
4524  lpLVItem->pszText = pszText;
4525  }
4526 
4527  return bResult;
4528 }
4529 
4530 /***
4531  * DESCRIPTION:
4532  * Retrieves the index of the item at coordinate (0, 0) of the client area.
4533  *
4534  * PARAMETER(S):
4535  * [I] infoPtr : valid pointer to the listview structure
4536  *
4537  * RETURN:
4538  * item index
4539  */
4541 {
4542