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