ReactOS  0.4.14-dev-604-gcfdd483
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  *
2727  * RETURN:
2728  * None
2729  */
2730 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2731 {
2732  INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2733 
2734  *lpPos = infoPtr->currIconPos;
2735 
2736  infoPtr->currIconPos.x += infoPtr->nItemWidth;
2737  if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2738 
2739  infoPtr->currIconPos.x = 0;
2740  infoPtr->currIconPos.y += infoPtr->nItemHeight;
2741 }
2742 
2743 
2744 /***
2745  * DESCRIPTION:
2746  * Returns the current icon position, and advances it down the left edge.
2747  * The returned position is not offset by Origin.
2748  *
2749  * PARAMETER(S):
2750  * [I] infoPtr : valid pointer to the listview structure
2751  * [O] lpPos : will get the current icon position
2752  *
2753  * RETURN:
2754  * None
2755  */
2756 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2757 {
2758  INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2759 
2760  *lpPos = infoPtr->currIconPos;
2761 
2762  infoPtr->currIconPos.y += infoPtr->nItemHeight;
2763  if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2764 
2765  infoPtr->currIconPos.x += infoPtr->nItemWidth;
2766  infoPtr->currIconPos.y = 0;
2767 }
2768 
2769 
2770 /***
2771  * DESCRIPTION:
2772  * Moves an icon to the specified position.
2773  * It takes care of invalidating the item, etc.
2774  *
2775  * PARAMETER(S):
2776  * [I] infoPtr : valid pointer to the listview structure
2777  * [I] nItem : the item to move
2778  * [I] lpPos : the new icon position
2779  * [I] isNew : flags the item as being new
2780  *
2781  * RETURN:
2782  * Success: TRUE
2783  * Failure: FALSE
2784  */
2785 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2786 {
2787  POINT old;
2788 
2789  if (!isNew)
2790  {
2791  old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2792  old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2793 
2794  if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2795  LISTVIEW_InvalidateItem(infoPtr, nItem);
2796  }
2797 
2798  /* Allocating a POINTER for every item is too resource intensive,
2799  * so we'll keep the (x,y) in different arrays */
2800  if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2801  if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2802 
2803  LISTVIEW_InvalidateItem(infoPtr, nItem);
2804 
2805  return TRUE;
2806 }
2807 
2808 /***
2809  * DESCRIPTION:
2810  * Arranges listview items in icon display mode.
2811  *
2812  * PARAMETER(S):
2813  * [I] infoPtr : valid pointer to the listview structure
2814  * [I] nAlignCode : alignment code
2815  *
2816  * RETURN:
2817  * SUCCESS : TRUE
2818  * FAILURE : FALSE
2819  */
2820 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2821 {
2822  void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2823  POINT pos;
2824  INT i;
2825 
2826  if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2827 
2828  TRACE("nAlignCode=%d\n", nAlignCode);
2829 
2830  if (nAlignCode == LVA_DEFAULT)
2831  {
2832  if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2833  else nAlignCode = LVA_ALIGNTOP;
2834  }
2835 
2836  switch (nAlignCode)
2837  {
2838  case LVA_ALIGNLEFT: next_pos = LISTVIEW_NextIconPosLeft; break;
2839  case LVA_ALIGNTOP: next_pos = LISTVIEW_NextIconPosTop; break;
2840  case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop; break; /* FIXME */
2841  default: return FALSE;
2842  }
2843 
2844  infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2845  for (i = 0; i < infoPtr->nItemCount; i++)
2846  {
2847  next_pos(infoPtr, &pos);
2848  LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2849  }
2850 
2851  return TRUE;
2852 }
2853 
2854 /***
2855  * DESCRIPTION:
2856  * Retrieves the bounding rectangle of all the items, not offset by Origin.
2857  * For LVS_REPORT always returns empty rectangle.
2858  *
2859  * PARAMETER(S):
2860  * [I] infoPtr : valid pointer to the listview structure
2861  * [O] lprcView : bounding rectangle
2862  *
2863  * RETURN:
2864  * SUCCESS : TRUE
2865  * FAILURE : FALSE
2866  */
2867 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2868 {
2869  INT i, x, y;
2870 
2871  SetRectEmpty(lprcView);
2872 
2873  switch (infoPtr->uView)
2874  {
2875  case LV_VIEW_ICON:
2876  case LV_VIEW_SMALLICON:
2877  for (i = 0; i < infoPtr->nItemCount; i++)
2878  {
2879  x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2880  y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2881  lprcView->right = max(lprcView->right, x);
2882  lprcView->bottom = max(lprcView->bottom, y);
2883  }
2884  if (infoPtr->nItemCount > 0)
2885  {
2886  lprcView->right += infoPtr->nItemWidth;
2887  lprcView->bottom += infoPtr->nItemHeight;
2888  }
2889  break;
2890 
2891  case LV_VIEW_LIST:
2892  y = LISTVIEW_GetCountPerColumn(infoPtr);
2893  x = infoPtr->nItemCount / y;
2894  if (infoPtr->nItemCount % y) x++;
2895  lprcView->right = x * infoPtr->nItemWidth;
2896  lprcView->bottom = y * infoPtr->nItemHeight;
2897  break;
2898  }
2899 }
2900 
2901 /***
2902  * DESCRIPTION:
2903  * Retrieves the bounding rectangle of all the items.
2904  *
2905  * PARAMETER(S):
2906  * [I] infoPtr : valid pointer to the listview structure
2907  * [O] lprcView : bounding rectangle
2908  *
2909  * RETURN:
2910  * SUCCESS : TRUE
2911  * FAILURE : FALSE
2912  */
2913 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2914 {
2915  POINT ptOrigin;
2916 
2917  TRACE("(lprcView=%p)\n", lprcView);
2918 
2919  if (!lprcView) return FALSE;
2920 
2921  LISTVIEW_GetAreaRect(infoPtr, lprcView);
2922 
2923  if (infoPtr->uView != LV_VIEW_DETAILS)
2924  {
2925  LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2926  OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2927  }
2928 
2929  TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2930 
2931  return TRUE;
2932 }
2933 
2934 /***
2935  * DESCRIPTION:
2936  * Retrieves the subitem pointer associated with the subitem index.
2937  *
2938  * PARAMETER(S):
2939  * [I] hdpaSubItems : DPA handle for a specific item
2940  * [I] nSubItem : index of subitem
2941  *
2942  * RETURN:
2943  * SUCCESS : subitem pointer
2944  * FAILURE : NULL
2945  */
2946 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2947 {
2948  SUBITEM_INFO *lpSubItem;
2949  INT i;
2950 
2951  /* we should binary search here if need be */
2952  for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2953  {
2954  lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2955  if (lpSubItem->iSubItem == nSubItem)
2956  return lpSubItem;
2957  }
2958 
2959  return NULL;
2960 }
2961 
2962 
2963 /***
2964  * DESCRIPTION:
2965  * Calculates the desired item width.
2966  *
2967  * PARAMETER(S):
2968  * [I] infoPtr : valid pointer to the listview structure
2969  *
2970  * RETURN:
2971  * The desired item width.
2972  */
2974 {
2975  INT nItemWidth = 0;
2976 
2977  TRACE("uView=%d\n", infoPtr->uView);
2978 
2979  if (infoPtr->uView == LV_VIEW_ICON)
2980  nItemWidth = infoPtr->iconSpacing.cx;
2981  else if (infoPtr->uView == LV_VIEW_DETAILS)
2982  {
2983  if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2984  {
2985  RECT rcHeader;
2986  INT index;
2987 
2989  DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2990 
2991  LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2992  nItemWidth = rcHeader.right;
2993  }
2994  }
2995  else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2996  {
2997  WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2998  LVITEMW lvItem;
2999  INT i;
3000 
3001  lvItem.mask = LVIF_TEXT;
3002  lvItem.iSubItem = 0;
3003 
3004  for (i = 0; i < infoPtr->nItemCount; i++)
3005  {
3006  lvItem.iItem = i;
3007  lvItem.pszText = szDispText;
3008  lvItem.cchTextMax = DISP_TEXT_SIZE;
3009  if (LISTVIEW_GetItemW(infoPtr, &lvItem))
3010  nItemWidth = max(LISTVIEW_GetStringWidthT(infoPtr, lvItem.pszText, TRUE),
3011  nItemWidth);
3012  }
3013 
3014  if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx;
3015  if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
3016 
3017  nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
3018  }
3019 
3020  return nItemWidth;
3021 }
3022 
3023 /***
3024  * DESCRIPTION:
3025  * Calculates the desired item height.
3026  *
3027  * PARAMETER(S):
3028  * [I] infoPtr : valid pointer to the listview structure
3029  *
3030  * RETURN:
3031  * The desired item height.
3032  */
3034 {
3035  INT nItemHeight;
3036 
3037  TRACE("uView=%d\n", infoPtr->uView);
3038 
3039  if (infoPtr->uView == LV_VIEW_ICON)
3040  nItemHeight = infoPtr->iconSpacing.cy;
3041  else
3042  {
3043  nItemHeight = infoPtr->ntmHeight;
3044  if (infoPtr->himlState)
3045  nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
3046  if (infoPtr->himlSmall)
3047  nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
3048  nItemHeight += HEIGHT_PADDING;
3049  if (infoPtr->nMeasureItemHeight > 0)
3050  nItemHeight = infoPtr->nMeasureItemHeight;
3051  }
3052 
3053  return max(nItemHeight, 1);
3054 }
3055 
3056 /***
3057  * DESCRIPTION:
3058  * Updates the width, and height of an item.
3059  *
3060  * PARAMETER(S):
3061  * [I] infoPtr : valid pointer to the listview structure
3062  *
3063  * RETURN:
3064  * None.
3065  */
3066 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
3067 {
3068  infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
3069  infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
3070 }
3071 
3072 
3073 /***
3074  * DESCRIPTION:
3075  * Retrieves and saves important text metrics info for the current
3076  * Listview font.
3077  *
3078  * PARAMETER(S):
3079  * [I] infoPtr : valid pointer to the listview structure
3080  *
3081  */
3083 {
3084  HDC hdc = GetDC(infoPtr->hwndSelf);
3085  HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
3086  HFONT hOldFont = SelectObject(hdc, hFont);
3087  TEXTMETRICW tm;
3088  SIZE sz;
3089 
3090  if (GetTextMetricsW(hdc, &tm))
3091  {
3092  infoPtr->ntmHeight = tm.tmHeight;
3093  infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
3094  }
3095 
3096  if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
3097  infoPtr->nEllipsisWidth = sz.cx;
3098 
3099  SelectObject(hdc, hOldFont);
3100  ReleaseDC(infoPtr->hwndSelf, hdc);
3101 
3102  TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
3103 }
3104 
3105 /***
3106  * DESCRIPTION:
3107  * A compare function for ranges
3108  *
3109  * PARAMETER(S)
3110  * [I] range1 : pointer to range 1;
3111  * [I] range2 : pointer to range 2;
3112  * [I] flags : flags
3113  *
3114  * RETURNS:
3115  * > 0 : if range 1 > range 2
3116  * < 0 : if range 2 > range 1
3117  * = 0 : if range intersects range 2
3118  */
3120 {
3121  INT cmp;
3122 
3123  if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower)
3124  cmp = -1;
3125  else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower)
3126  cmp = 1;
3127  else
3128  cmp = 0;
3129 
3130  TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
3131 
3132  return cmp;
3133 }
3134 
3135 #define ranges_check(ranges, desc) if (TRACE_ON(listview)) ranges_assert(ranges, desc, __FILE__, __LINE__)
3136 
3137 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *file, int line)
3138 {
3139  INT i;
3140  RANGE *prev, *curr;
3141 
3142  TRACE("*** Checking %s:%d:%s ***\n", file, line, desc);
3143  assert (ranges);
3144  assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
3145  ranges_dump(ranges);
3146  if (DPA_GetPtrCount(ranges->hdpa) > 0)
3147  {
3148  prev = DPA_GetPtr(ranges->hdpa, 0);
3149  assert (prev->lower >= 0 && prev->lower < prev->upper);
3150  for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
3151  {
3152  curr = DPA_GetPtr(ranges->hdpa, i);
3153  assert (prev->upper <= curr->lower);
3154  assert (curr->lower < curr->upper);
3155  prev = curr;
3156  }
3157  }
3158  TRACE("--- Done checking---\n");
3159 }
3160 
3162 {
3163  RANGES ranges = Alloc(sizeof(struct tagRANGES));
3164  if (!ranges) return NULL;
3165  ranges->hdpa = DPA_Create(count);
3166  if (ranges->hdpa) return ranges;
3167  Free(ranges);
3168  return NULL;
3169 }
3170 
3171 static void ranges_clear(RANGES ranges)
3172 {
3173  INT i;
3174 
3175  for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3176  Free(DPA_GetPtr(ranges->hdpa, i));
3177  DPA_DeleteAllPtrs(ranges->hdpa);
3178 }
3179 
3180 
3181 static void ranges_destroy(RANGES ranges)
3182 {
3183  if (!ranges) return;
3184  ranges_clear(ranges);
3185  DPA_Destroy(ranges->hdpa);
3186  Free(ranges);
3187 }
3188 
3190 {
3191  RANGES clone;
3192  INT i;
3193 
3194  if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
3195 
3196  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3197  {
3198  RANGE *newrng = Alloc(sizeof(RANGE));
3199  if (!newrng) goto fail;
3200  *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
3201  if (!DPA_SetPtr(clone->hdpa, i, newrng))
3202  {
3203  Free(newrng);
3204  goto fail;
3205  }
3206  }
3207  return clone;
3208 
3209 fail:
3210  TRACE ("clone failed\n");
3211  ranges_destroy(clone);
3212  return NULL;
3213 }
3214 
3215 static RANGES ranges_diff(RANGES ranges, RANGES sub)
3216 {
3217  INT i;
3218 
3219  for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
3220  ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
3221 
3222  return ranges;
3223 }
3224 
3225 static void ranges_dump(RANGES ranges)
3226 {
3227  INT i;
3228 
3229  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3230  TRACE(" %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
3231 }
3232 
3233 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
3234 {
3235  RANGE srchrng = { nItem, nItem + 1 };
3236 
3237  TRACE("(nItem=%d)\n", nItem);
3238  ranges_check(ranges, "before contain");
3239  return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
3240 }
3241 
3243 {
3244  INT i, count = 0;
3245 
3246  for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
3247  {
3248  RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
3249  count += sel->upper - sel->lower;
3250  }
3251 
3252  return count;
3253 }
3254 
3255 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
3256 {
3257  RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
3258  INT index;
3259 
3260  index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3261  if (index == -1) return TRUE;
3262 
3263  for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
3264  {
3265  chkrng = DPA_GetPtr(ranges->hdpa, index);
3266  if (chkrng->lower >= nItem)
3267  chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
3268  if (chkrng->upper > nItem)
3269  chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3270  }
3271  return TRUE;
3272 }
3273 
3275 {
3276  RANGE srchrgn;
3277  INT index;
3278 
3279  TRACE("(%s)\n", debugrange(&range));
3280  ranges_check(ranges, "before add");
3281 
3282  /* try find overlapping regions first */
3283  srchrgn.lower = range.lower - 1;
3284  srchrgn.upper = range.upper + 1;
3285  index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3286 
3287  if (index == -1)
3288  {
3289  RANGE *newrgn;
3290 
3291  TRACE("Adding new range\n");
3292 
3293  /* create the brand new range to insert */
3294  newrgn = Alloc(sizeof(RANGE));
3295  if(!newrgn) goto fail;
3296  *newrgn = range;
3297 
3298  /* figure out where to insert it */
3299  index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3300  TRACE("index=%d\n", index);
3301  if (index == -1) index = 0;
3302 
3303  /* and get it over with */
3304  if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3305  {
3306  Free(newrgn);
3307  goto fail;
3308  }
3309  }
3310  else
3311  {
3312  RANGE *chkrgn, *mrgrgn;
3313  INT fromindex, mergeindex;
3314 
3315  chkrgn = DPA_GetPtr(ranges->hdpa, index);
3316  TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3317 
3318  chkrgn->lower = min(range.lower, chkrgn->lower);
3319  chkrgn->upper = max(range.upper, chkrgn->upper);
3320 
3321  TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3322 
3323  /* merge now common ranges */
3324  fromindex = 0;
3325  srchrgn.lower = chkrgn->lower - 1;
3326  srchrgn.upper = chkrgn->upper + 1;
3327 
3328  do
3329  {
3330  mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3331  if (mergeindex == -1) break;
3332  if (mergeindex == index)
3333  {
3334  fromindex = index + 1;
3335  continue;
3336  }
3337 
3338  TRACE("Merge with index %i\n", mergeindex);
3339 
3340  mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3341  chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3342  chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3343  Free(mrgrgn);
3344  DPA_DeletePtr(ranges->hdpa, mergeindex);
3345  if (mergeindex < index) index --;
3346  } while(1);
3347  }
3348 
3349  ranges_check(ranges, "after add");
3350  return TRUE;
3351 
3352 fail:
3353  ranges_check(ranges, "failed add");
3354  return FALSE;
3355 }
3356 
3358 {
3359  RANGE *chkrgn;
3360  INT index;
3361 
3362  TRACE("(%s)\n", debugrange(&range));
3363  ranges_check(ranges, "before del");
3364 
3365  /* we don't use DPAS_SORTED here, since we need *
3366  * to find the first overlapping range */
3367  index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3368  while(index != -1)
3369  {
3370  chkrgn = DPA_GetPtr(ranges->hdpa, index);
3371 
3372  TRACE("Matches range %s @%d\n", debugrange(chkrgn), index);
3373 
3374  /* case 1: Same range */
3375  if ( (chkrgn->upper == range.upper) &&
3376  (chkrgn->lower == range.lower) )
3377  {
3378  DPA_DeletePtr(ranges->hdpa, index);
3379  Free(chkrgn);
3380  break;
3381  }
3382  /* case 2: engulf */
3383  else if ( (chkrgn->upper <= range.upper) &&
3384  (chkrgn->lower >= range.lower) )
3385  {
3386  DPA_DeletePtr(ranges->hdpa, index);
3387  Free(chkrgn);
3388  }
3389  /* case 3: overlap upper */
3390  else if ( (chkrgn->upper <= range.upper) &&
3391  (chkrgn->lower < range.lower) )
3392  {
3393  chkrgn->upper = range.lower;
3394  }
3395  /* case 4: overlap lower */
3396  else if ( (chkrgn->upper > range.upper) &&
3397  (chkrgn->lower >= range.lower) )
3398  {
3399  chkrgn->lower = range.upper;
3400  break;
3401  }
3402  /* case 5: fully internal */
3403  else
3404  {
3405  RANGE *newrgn;
3406 
3407  if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3408  newrgn->lower = chkrgn->lower;
3409  newrgn->upper = range.lower;
3410  chkrgn->lower = range.upper;
3411  if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3412  {
3413  Free(newrgn);
3414  goto fail;
3415  }
3416  break;
3417  }
3418 
3419  index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3420  }
3421 
3422  ranges_check(ranges, "after del");
3423  return TRUE;
3424 
3425 fail:
3426  ranges_check(ranges, "failed del");
3427  return FALSE;
3428 }
3429 
3430 /***
3431 * DESCRIPTION:
3432 * Removes all selection ranges
3433 *
3434 * Parameters(s):
3435 * [I] infoPtr : valid pointer to the listview structure
3436 * [I] toSkip : item range to skip removing the selection
3437 *
3438 * RETURNS:
3439 * SUCCESS : TRUE
3440 * FAILURE : FALSE
3441 */
3443 {
3444  LVITEMW lvItem;
3445  ITERATOR i;
3446  RANGES clone;
3447 
3448  TRACE("()\n");
3449 
3450  lvItem.state = 0;
3451  lvItem.stateMask = LVIS_SELECTED;
3452 
3453  /* need to clone the DPA because callbacks can change it */
3454  if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3455  iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3456  while(iterator_next(&i))
3457  LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3458  /* note that the iterator destructor will free the cloned range */
3459  iterator_destroy(&i);
3460 
3461  return TRUE;
3462 }
3463 
3464 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3465 {
3466  RANGES toSkip;
3467 
3468  if (!(toSkip = ranges_create(1))) return FALSE;
3469  if (nItem != -1) ranges_additem(toSkip, nItem);
3470  LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3471  ranges_destroy(toSkip);
3472  return TRUE;
3473 }
3474 
3475 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3476 {
3477  return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3478 }
3479 
3480 /***
3481  * DESCRIPTION:
3482  * Retrieves the number of items that are marked as selected.
3483  *
3484  * PARAMETER(S):
3485  * [I] infoPtr : valid pointer to the listview structure
3486  *
3487  * RETURN:
3488  * Number of items selected.
3489  */
3491 {
3492  INT nSelectedCount = 0;
3493 
3494  if (infoPtr->uCallbackMask & LVIS_SELECTED)
3495  {
3496  INT i;
3497  for (i = 0; i < infoPtr->nItemCount; i++)
3498  {
3499  if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3500  nSelectedCount++;
3501  }
3502  }
3503  else
3504  nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3505 
3506  TRACE("nSelectedCount=%d\n", nSelectedCount);
3507  return nSelectedCount;
3508 }
3509 
3510 /***
3511  * DESCRIPTION:
3512  * Manages the item focus.
3513  *
3514  * PARAMETER(S):
3515  * [I] infoPtr : valid pointer to the listview structure
3516  * [I] nItem : item index
3517  *
3518  * RETURN:
3519  * TRUE : focused item changed
3520  * FALSE : focused item has NOT changed
3521  */
3522 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3523 {
3524  INT oldFocus = infoPtr->nFocusedItem;
3525  LVITEMW lvItem;
3526 
3527  if (nItem == infoPtr->nFocusedItem) return FALSE;
3528 
3529  lvItem.state = nItem == -1 ? 0 : LVIS_FOCUSED;
3530  lvItem.stateMask = LVIS_FOCUSED;
3531  LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3532 
3533  return oldFocus != infoPtr->nFocusedItem;
3534 }
3535 
3536 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3537 {
3538  if (nShiftItem < nItem) return nShiftItem;
3539 
3540  if (nShiftItem > nItem) return nShiftItem + direction;
3541 
3542  if (direction > 0) return nShiftItem + direction;
3543 
3544  return min(nShiftItem, infoPtr->nItemCount - 1);
3545 }
3546 
3547 /* This function updates focus index.
3548 
3549 Parameters:
3550  focus : current focus index
3551  item : index of item to be added/removed
3552  direction : add/remove flag
3553 */
3554 static void LISTVIEW_ShiftFocus(LISTVIEW_INFO *infoPtr, INT focus, INT item, INT direction)
3555 {
3556  DWORD old_mask = infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE;
3557 
3558  infoPtr->notify_mask &= ~NOTIFY_MASK_ITEM_CHANGE;
3559  focus = shift_item(infoPtr, focus, item, direction);
3560  if (focus != infoPtr->nFocusedItem)
3561  LISTVIEW_SetItemFocus(infoPtr, focus);
3562  infoPtr->notify_mask |= old_mask;
3563 }
3564 
3577 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3578 {
3579  TRACE("Shifting %i, %i steps\n", nItem, direction);
3580 
3581  ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3582  assert(abs(direction) == 1);
3583  infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3584 
3585  /* But we are not supposed to modify nHotItem! */
3586 }
3587 
3600 {
3601  INT nFirst = min(infoPtr->nSelectionMark, nItem);
3602  INT nLast = max(infoPtr->nSelectionMark, nItem);
3603  HWND hwndSelf = infoPtr->hwndSelf;
3604  NMLVODSTATECHANGE nmlv;
3605  DWORD old_mask;
3606  LVITEMW item;
3607  INT i;
3608 
3609  /* Temporarily disable change notification
3610  * If the control is LVS_OWNERDATA, we need to send
3611  * only one LVN_ODSTATECHANGED notification.
3612  * See MSDN documentation for LVN_ITEMCHANGED.
3613  */
3614  old_mask = infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE;
3615  if (infoPtr->dwStyle & LVS_OWNERDATA)
3616  infoPtr->notify_mask &= ~NOTIFY_MASK_ITEM_CHANGE;
3617 
3618  if (nFirst == -1) nFirst = nItem;
3619 
3620  item.state = LVIS_SELECTED;
3621  item.stateMask = LVIS_SELECTED;
3622 
3623  for (i = nFirst; i <= nLast; i++)
3624  LISTVIEW_SetItemState(infoPtr,i,&item);
3625 
3626  ZeroMemory(&nmlv, sizeof(nmlv));
3627  nmlv.iFrom = nFirst;
3628  nmlv.iTo = nLast;
3629  nmlv.uOldState = 0;
3630  nmlv.uNewState = item.state;
3631 
3632  notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3633  if (!IsWindow(hwndSelf))
3634  return FALSE;
3635  infoPtr->notify_mask |= old_mask;
3636  return TRUE;
3637 }
3638 
3639 
3640 /***
3641  * DESCRIPTION:
3642  * Sets a single group selection.
3643  *
3644  * PARAMETER(S):
3645  * [I] infoPtr : valid pointer to the listview structure
3646  * [I] nItem : item index
3647  *
3648  * RETURN:
3649  * None
3650  */
3651 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3652 {
3653  RANGES selection;
3654  DWORD old_mask;
3655  LVITEMW item;
3656  ITERATOR i;
3657 
3658  if (!(selection = ranges_create(100))) return;
3659 
3660  item.state = LVIS_SELECTED;
3661  item.stateMask = LVIS_SELECTED;
3662 
3663  if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3664  {
3665  if (infoPtr->nSelectionMark == -1)
3666  {
3667  infoPtr->nSelectionMark = nItem;
3668  ranges_additem(selection, nItem);
3669  }
3670  else
3671  {
3672  RANGE sel;
3673 
3674  sel.lower = min(infoPtr->nSelectionMark, nItem);
3675  sel.upper = max(infoPtr->nSelectionMark, nItem) + 1;
3676  ranges_add(selection, sel);
3677  }
3678  }
3679  else
3680  {
3681  RECT rcItem, rcSel, rcSelMark;
3682  POINT ptItem;
3683 
3684  rcItem.left = LVIR_BOUNDS;
3685  if (!LISTVIEW_GetItemRect(infoPtr, nItem, &rcItem)) {
3687  return;
3688  }
3689  rcSelMark.left = LVIR_BOUNDS;
3690  if (!LISTVIEW_GetItemRect(infoPtr, infoPtr->nSelectionMark, &rcSelMark)) {
3692  return;
3693  }
3694  UnionRect(&rcSel, &rcItem, &rcSelMark);
3695  iterator_frameditems(&i, infoPtr, &rcSel);
3696  while(iterator_next(&i))
3697  {
3698  LISTVIEW_GetItemPosition(infoPtr, i.nItem, &ptItem);
3699  if (PtInRect(&rcSel, ptItem)) ranges_additem(selection, i.nItem);
3700  }
3701  iterator_destroy(&i);
3702  }
3703 
3704  /* disable per item notifications on LVS_OWNERDATA style
3705  FIXME: single LVN_ODSTATECHANGED should be used */
3706  old_mask = infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE;
3707  if (infoPtr->dwStyle & LVS_OWNERDATA)
3708  infoPtr->notify_mask &= ~NOTIFY_MASK_ITEM_CHANGE;
3709 
3711 
3712 
3714  while(iterator_next(&i))
3715  LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
3716  /* this will also destroy the selection */
3717  iterator_destroy(&i);
3718 
3719  infoPtr->notify_mask |= old_mask;
3720  LISTVIEW_SetItemFocus(infoPtr, nItem);
3721 }
3722 
3723 /***
3724  * DESCRIPTION:
3725  * Sets a single selection.
3726  *
3727  * PARAMETER(S):
3728  * [I] infoPtr : valid pointer to the listview structure
3729  * [I] nItem : item index
3730  *
3731  * RETURN:
3732  * None
3733  */
3734 static void LISTVIEW_SetSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3735 {
3736  LVITEMW lvItem;
3737 
3738  TRACE("nItem=%d\n", nItem);
3739 
3740  LISTVIEW_DeselectAllSkipItem(infoPtr, nItem);
3741 
3742  lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
3744  LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3745 
3746  infoPtr->nSelectionMark = nItem;
3747 }
3748 
3749 /***
3750  * DESCRIPTION:
3751  * Set selection(s) with keyboard.
3752  *
3753  * PARAMETER(S):
3754  * [I] infoPtr : valid pointer to the listview structure
3755  * [I] nItem : item index
3756  * [I] space : VK_SPACE code sent
3757  *
3758  * RETURN:
3759  * SUCCESS : TRUE (needs to be repainted)
3760  * FAILURE : FALSE (nothing has changed)
3761  */
3763 {
3764  /* FIXME: pass in the state */
3765  WORD wShift = GetKeyState(VK_SHIFT) & 0x8000;
3766  WORD wCtrl = GetKeyState(VK_CONTROL) & 0x8000;
3767  BOOL bResult = FALSE;
3768 
3769  TRACE("nItem=%d, wShift=%d, wCtrl=%d\n", nItem, wShift, wCtrl);
3770  if ((nItem >= 0) && (nItem < infoPtr->nItemCount))
3771  {
3772  bResult = TRUE;
3773 
3774  if (infoPtr->dwStyle & LVS_SINGLESEL || (wShift == 0 && wCtrl == 0))
3775  LISTVIEW_SetSelection(infoPtr, nItem);
3776  else
3777  {
3778  if (wShift)
3779  LISTVIEW_SetGroupSelection(infoPtr, nItem);
3780  else if (wCtrl)
3781  {
3782  LVITEMW lvItem;
3783  lvItem.state = ~LISTVIEW_GetItemState(infoPtr, nItem, LVIS_SELECTED);
3784  lvItem.stateMask = LVIS_SELECTED;
3785  if (space)
3786  {
3787  LISTVIEW_SetItemState(infoPtr, nItem, &lvItem);
3788  if (lvItem.state & LVIS_SELECTED)
3789  infoPtr->nSelectionMark = nItem;
3790  }
3791  bResult = LISTVIEW_SetItemFocus(infoPtr, nItem);
3792  }
3793  }
3794  LISTVIEW_EnsureVisible(infoPtr, nItem, FALSE);
3795  }
3796 
3797  UpdateWindow(infoPtr->hwndSelf); /* update client area */
3798  return bResult;
3799 }
3800 
3801 static BOOL LISTVIEW_GetItemAtPt(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem, POINT pt)
3802 {
3803  LVHITTESTINFO lvHitTestInfo;
3804 
3805  ZeroMemory(&lvHitTestInfo, sizeof(lvHitTestInfo));
3806  lvHitTestInfo.pt.x = pt.x;
3807  lvHitTestInfo.pt.y = pt.y;
3808 
3809  LISTVIEW_HitTest(infoPtr, &lvHitTestInfo, TRUE, FALSE);
3810 
3811  lpLVItem->mask = LVIF_PARAM;
3812  lpLVItem->iItem = lvHitTestInfo.iItem;
3813  lpLVItem->iSubItem = 0;
3814 
3815  return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
3816 }
3817 
3818 static inline BOOL LISTVIEW_IsHotTracking(const LISTVIEW_INFO *infoPtr)
3819 {
3820  return ((infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT) ||
3821  (infoPtr->dwLvExStyle & LVS_EX_ONECLICKACTIVATE) ||
3822  (infoPtr->dwLvExStyle & LVS_EX_TWOCLICKACTIVATE));
3823 }
3824 
3825 /***
3826  * DESCRIPTION:
3827  * Called when the mouse is being actively tracked and has hovered for a specified
3828  * amount of time
3829  *
3830  * PARAMETER(S):
3831  * [I] infoPtr : valid pointer to the listview structure
3832  * [I] fwKeys : key indicator
3833  * [I] x,y : mouse position
3834  *
3835  * RETURN:
3836  * 0 if the message was processed, non-zero if there was an error
3837  *
3838  * INFO:
3839  * LVS_EX_TRACKSELECT: An item is automatically selected when the cursor remains
3840  * over the item for a certain period of time.
3841  *
3842  */
3844 {
3845  NMHDR hdr;
3846 
3847  if (notify_hdr(infoPtr, NM_HOVER, &hdr)) return 0;
3848 
3849  if (LISTVIEW_IsHotTracking(infoPtr))
3850  {
3851  LVITEMW item;
3852  POINT pt;
3853 
3854  pt.x = x;
3855  pt.y = y;
3856 
3857  if (LISTVIEW_GetItemAtPt(infoPtr, &item, pt))
3858  LISTVIEW_SetSelection(infoPtr, item.iItem);
3859 
3860  SetFocus(infoPtr->hwndSelf);
3861  }
3862 
3863  return 0;
3864 }
3865 
3866 #define SCROLL_LEFT 0x1
3867 #define SCROLL_RIGHT 0x2
3868 #define SCROLL_UP 0x4
3869 #define SCROLL_DOWN 0x8
3870 
3871 /***
3872  * DESCRIPTION:
3873  * Utility routine to draw and highlight items within a marquee selection rectangle.
3874  *
3875  * PARAMETER(S):
3876  * [I] infoPtr : valid pointer to the listview structure
3877  * [I] coords_orig : original co-ordinates of the cursor
3878  * [I] coords_offs : offsetted coordinates of the cursor
3879  * [I] offset : offset amount
3880  * [I] scroll : Bitmask of which directions we should scroll, if at all
3881  *
3882  * RETURN:
3883  * None.
3884  */
3885 static void LISTVIEW_MarqueeHighlight(LISTVIEW_INFO *infoPtr, const POINT *coords_orig,
3886  INT scroll)
3887 {
3888  BOOL controlDown = FALSE;
3889  LVITEMW item;
3890  ITERATOR old_elems, new_elems;
3891  RECT rect;
3892  POINT coords_offs, offset;
3893 
3894  /* Ensure coordinates are within client bounds */
3895  coords_offs.x = max(min(coords_orig->x, infoPtr->rcList.right), 0);
3896  coords_offs.y = max(min(coords_orig->y, infoPtr->rcList.bottom), 0);
3897 
3898  /* Get offset */
3899  LISTVIEW_GetOrigin(infoPtr, &offset);
3900 
3901  /* Offset coordinates by the appropriate amount */
3902  coords_offs.x -= offset.x;
3903  coords_offs.y -= offset.y;
3904 
3905  if (coords_offs.x > infoPtr->marqueeOrigin.x)
3906  {
3907  rect.left = infoPtr->marqueeOrigin.x;
3908  rect.right = coords_offs.x;
3909  }
3910  else
3911  {
3912  rect.left = coords_offs.x;
3913  rect.right = infoPtr->marqueeOrigin.x;
3914  }
3915 
3916  if (coords_offs.y > infoPtr->marqueeOrigin.y)
3917  {
3918  rect.top = infoPtr->marqueeOrigin.y;
3919  rect.bottom = coords_offs.y;
3920  }
3921  else
3922  {
3923  rect.top = coords_offs.y;
3924  rect.bottom = infoPtr->marqueeOrigin.y;
3925  }
3926 
3927  /* Cancel out the old marquee rectangle and draw the new one */
3928  LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3929 
3930  /* Scroll by the appropriate distance if applicable - speed up scrolling as
3931  the cursor is further away */
3932 
3933  if ((scroll & SCROLL_LEFT) && (coords_orig->x <= 0))
3934  LISTVIEW_Scroll(infoPtr, coords_orig->x, 0);
3935 
3936  if ((scroll & SCROLL_RIGHT) && (coords_orig->x >= infoPtr->rcList.right))
3937  LISTVIEW_Scroll(infoPtr, (coords_orig->x - infoPtr->rcList.right), 0);
3938 
3939  if ((scroll & SCROLL_UP) && (coords_orig->y <= 0))
3940  LISTVIEW_Scroll(infoPtr, 0, coords_orig->y);
3941 
3942  if ((scroll & SCROLL_DOWN) && (coords_orig->y >= infoPtr->rcList.bottom))
3943  LISTVIEW_Scroll(infoPtr, 0, (coords_orig->y - infoPtr->rcList.bottom));
3944 
3945  iterator_frameditems_absolute(&old_elems, infoPtr, &infoPtr->marqueeRect);
3946 
3947  infoPtr->marqueeRect = rect;
3948  infoPtr->marqueeDrawRect = rect;
3949  OffsetRect(&infoPtr->marqueeDrawRect, offset.x, offset.y);
3950 
3951  iterator_frameditems_absolute(&new_elems, infoPtr, &infoPtr->marqueeRect);
3952  iterator_remove_common_items(&old_elems, &new_elems);
3953 
3954  /* Iterate over no longer selected items */
3955  while (iterator_next(&old_elems))
3956  {
3957  if (old_elems.nItem > -1)
3958  {
3959  if (LISTVIEW_GetItemState(infoPtr, old_elems.nItem, LVIS_SELECTED) == LVIS_SELECTED)
3960  item.state = 0;
3961  else
3962  item.state = LVIS_SELECTED;
3963 
3964  item.stateMask = LVIS_SELECTED;
3965 
3966  LISTVIEW_SetItemState(infoPtr, old_elems.nItem, &item);
3967  }
3968  }
3969  iterator_destroy(&old_elems);
3970 
3971 
3972  /* Iterate over newly selected items */
3973  if (GetKeyState(VK_CONTROL) & 0x8000)
3974  controlDown = TRUE;
3975 
3976  while (iterator_next(&new_elems))
3977  {
3978  if (new_elems.nItem > -1)
3979  {
3980  /* If CTRL is pressed, invert. If not, always select the item. */
3981  if ((controlDown) && (LISTVIEW_GetItemState(infoPtr, new_elems.nItem, LVIS_SELECTED)))
3982  item.state = 0;
3983  else
3984  item.state = LVIS_SELECTED;
3985 
3986  item.stateMask = LVIS_SELECTED;
3987 
3988  LISTVIEW_SetItemState(infoPtr, new_elems.nItem, &item);
3989  }
3990  }
3991  iterator_destroy(&new_elems);
3992 
3993  LISTVIEW_InvalidateRect(infoPtr, &infoPtr->marqueeDrawRect);
3994 }
3995 
3996 /***
3997  * DESCRIPTION:
3998  * Called when we are in a marquee selection that involves scrolling the listview (ie,
3999  * the cursor is outside the bounds of the client area). This is a TIMERPROC.
4000  *
4001  * PARAMETER(S):
4002  * [I] hwnd : Handle to the listview
4003  * [I] uMsg : WM_TIMER (ignored)
4004  * [I] idEvent : The timer ID interpreted as a pointer to a LISTVIEW_INFO struct
4005  * [I] dwTimer : The elapsed time (ignored)
4006  *
4007  * RETURN:
4008  * None.
4009  */
4011 {
4012  LISTVIEW_INFO *infoPtr;
4013  SCROLLINFO scrollInfo;
4014  POINT coords;
4015  INT scroll = 0;
4016 
4017  infoPtr = (LISTVIEW_INFO *) idEvent;
4018 
4019  if (!infoPtr)
4020  return;
4021 
4022  /* Get the current cursor position and convert to client coordinates */
4023  GetCursorPos(&coords);
4025 
4026  scrollInfo.cbSize = sizeof(SCROLLINFO);
4027  scrollInfo.fMask = SIF_ALL;
4028 
4029  /* Work out in which directions we can scroll */
4030  if (GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &scrollInfo))
4031  {
4032  if (scrollInfo.nPos != scrollInfo.nMin)
4033  scroll |= SCROLL_UP;
4034 
4035  if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
4036  scroll |= SCROLL_DOWN;
4037  }
4038 
4039  if (GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &scrollInfo))
4040  {
4041  if (scrollInfo.nPos != scrollInfo.nMin)
4042  scroll |= SCROLL_LEFT;
4043 
4044  if (((scrollInfo.nPage + scrollInfo.nPos) - 1) != scrollInfo.nMax)
4045  scroll |= SCROLL_RIGHT;
4046  }
4047 
4048  if (((coords.x <= 0) && (scroll & SCROLL_LEFT)) ||
4049  ((coords.y <= 0) && (scroll & SCROLL_UP)) ||
4050  ((coords.x >= infoPtr->rcList.right) && (scroll & SCROLL_RIGHT)) ||
4051  ((coords.y >= infoPtr->rcList.bottom) && (scroll & SCROLL_DOWN)))
4052  {
4053  LISTVIEW_MarqueeHighlight(infoPtr, &coords, scroll);
4054  }
4055 }
4056 
4057 /***
4058  * DESCRIPTION:
4059  * Called whenever WM_MOUSEMOVE is received.
4060  *
4061  * PARAMETER(S):
4062  * [I] infoPtr : valid pointer to the listview structure
4063  * [I] fwKeys : key indicator
4064  * [I] x,y : mouse position
4065  *
4066  * RETURN:
4067  * 0 if the message is processed, non-zero if there was an error
4068  */
4070 {
4071  LVHITTESTINFO ht;
4072  RECT rect;
4073  POINT pt;
4074 
4075  pt.x = x;
4076  pt.y = y;
4077 
4078  if (!(fwKeys & MK_LBUTTON))
4079  infoPtr->bLButtonDown = FALSE;
4080 
4081  if (infoPtr->bLButtonDown)
4082  {
4083  rect.left = rect.right = infoPtr->ptClickPos.x;
4084  rect.top = rect.bottom = infoPtr->ptClickPos.y;
4085 
4087 
4088  if (infoPtr->bMarqueeSelect)
4089  {
4090  /* Enable the timer if we're going outside our bounds, in case the user doesn't
4091  move the mouse again */
4092 
4093  if ((x <= 0) || (y <= 0) || (x >= infoPtr->rcList.right) ||
4094  (y >= infoPtr->rcList.bottom))
4095  {
4096  if (!infoPtr->bScrolling)
4097  {
4098  infoPtr->bScrolling = TRUE;
4099  SetTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr, 1, LISTVIEW_ScrollTimer);
4100  }
4101  }
4102  else
4103  {
4104  infoPtr->bScrolling = FALSE;
4105  KillTimer(infoPtr->hwndSelf, (UINT_PTR) infoPtr);
4106  }
4107 
4108  LISTVIEW_MarqueeHighlight(infoPtr, &pt, 0);
4109  return 0;
4110  }
4111 
4112  ht.pt = pt;
4113  LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4114 
4115  /* reset item marker */
4116  if (infoPtr->nLButtonDownItem != ht.iItem)
4117  infoPtr->nLButtonDownItem = -1;
4118 
4119  if (!PtInRect(&rect, pt))
4120  {
4121  /* this path covers the following:
4122  1. WM_LBUTTONDOWN over selected item (sets focus on it)
4123  2. change focus with keys
4124  3. move mouse over item from step 1 selects it and moves focus on it */
4125  if (infoPtr->nLButtonDownItem != -1 &&
4127  {
4128  LVITEMW lvItem;
4129 
4130  lvItem.state = LVIS_FOCUSED | LVIS_SELECTED;
4132 
4133  LISTVIEW_SetItemState(infoPtr, infoPtr->nLButtonDownItem, &lvItem);
4134  infoPtr->nLButtonDownItem = -1;
4135  }
4136 
4137  if (!infoPtr->bDragging)
4138  {
4139  ht.pt = infoPtr->ptClickPos;
4140  LISTVIEW_HitTest(infoPtr, &ht, TRUE, TRUE);
4141 
4142  /* If the click is outside the range of an item, begin a
4143  highlight. If not, begin an item drag. */
4144  if (ht.iItem == -1)
4145  {
4146  NMHDR hdr;
4147 
4148  /* If we're allowing multiple selections, send notification.
4149  If return value is non-zero, cancel. */
4150  if (!(infoPtr->dwStyle & LVS_SINGLESEL) && (notify_hdr(infoPtr, LVN_MARQUEEBEGIN, &hdr) == 0))
4151  {
4152  /* Store the absolute coordinates of the click */
4153  POINT offset;
4154  LISTVIEW_GetOrigin(infoPtr, &offset);
4155 
4156  infoPtr->marqueeOrigin.x = infoPtr->ptClickPos.x - offset.x;
4157  infoPtr->marqueeOrigin.y = infoPtr->ptClickPos.y - offset.y;
4158 
4159  /* Begin selection and capture mouse */
4160  infoPtr->bMarqueeSelect = TRUE;
4161  SetCapture(infoPtr->hwndSelf);
4162  }
4163  }
4164  else
4165  {
4166  NMLISTVIEW nmlv;
4167 
4168  ZeroMemory(&nmlv, sizeof(nmlv));
4169  nmlv.iItem = ht.iItem;
4170  nmlv.ptAction = infoPtr->ptClickPos;
4171 
4172  notify_listview(infoPtr, LVN_BEGINDRAG, &nmlv);
4173  infoPtr->bDragging = TRUE;
4174  }
4175  }
4176 
4177  return 0;
4178  }
4179  }
4180 
4181  /* see if we are supposed to be tracking mouse hovering */
4182  if (LISTVIEW_IsHotTracking(infoPtr)) {
4183  TRACKMOUSEEVENT trackinfo;
4184  DWORD flags;
4185 
4186  trackinfo.cbSize = sizeof(TRACKMOUSEEVENT);
4187  trackinfo.dwFlags = TME_QUERY;
4188 
4189  /* see if we are already tracking this hwnd */
4190  _TrackMouseEvent(&trackinfo);
4191 
4192  flags = TME_LEAVE;
4193  if(infoPtr->dwLvExStyle & LVS_EX_TRACKSELECT)
4194  flags |= TME_HOVER;
4195 
4196  if((trackinfo.dwFlags & flags) != flags || trackinfo.hwndTrack != infoPtr->hwndSelf) {
4197  trackinfo.dwFlags = flags;
4198  trackinfo.dwHoverTime = infoPtr->dwHoverTime;
4199  trackinfo.hwndTrack = infoPtr->hwndSelf;
4200 
4201  /* call TRACKMOUSEEVENT so we receive WM_MOUSEHOVER messages */
4202  _TrackMouseEvent(&trackinfo);
4203  }
4204  }
4205 
4206  return 0;
4207 }
4208 
4209 
4210 /***
4211  * Tests whether the item is assignable to a list with style lStyle
4212  */
4213 static inline BOOL is_assignable_item(const LVITEMW *lpLVItem, LONG lStyle)
4214 {
4215  if ( (lpLVItem->mask & LVIF_TEXT) &&
4216  (lpLVItem->pszText == LPSTR_TEXTCALLBACKW) &&
4217  (lStyle & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) ) return FALSE;
4218 
4219  return TRUE;
4220 }
4221 
4222 
4223 /***
4224  * DESCRIPTION:
4225  * Helper for LISTVIEW_SetItemT and LISTVIEW_InsertItemT: sets item attributes.
4226  *
4227  * PARAMETER(S):
4228  * [I] infoPtr : valid pointer to the listview structure
4229  * [I] lpLVItem : valid pointer to new item attributes
4230  * [I] isNew : the item being set is being inserted
4231  * [I] isW : TRUE if lpLVItem is Unicode, FALSE if it's ANSI
4232  * [O] bChanged : will be set to TRUE if the item really changed
4233  *
4234  * RETURN:
4235  * SUCCESS : TRUE
4236  * FAILURE : FALSE
4237  */
4238 static BOOL set_main_item(LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem, BOOL isNew, BOOL isW, BOOL *bChanged)
4239 {
4240  ITEM_INFO *lpItem;
4241  NMLISTVIEW nmlv;
4242  UINT uChanged = 0;
4243  LVITEMW item;
4244  /* stateMask is ignored for LVM_INSERTITEM */
4245  UINT stateMask = isNew ? ~0 : lpLVItem->stateMask;
4246 
4247  TRACE("()\n");
4248 
4249  assert(lpLVItem->iItem >= 0 && lpLVItem->iItem < infoPtr->nItemCount);
4250 
4251  if (lpLVItem->mask == 0) return TRUE;
4252 
4253  if (infoPtr->dwStyle & LVS_OWNERDATA)
4254  {
4255  /* a virtual listview only stores selection and focus */
4256  if (lpLVItem->mask & ~LVIF_STATE)
4257  return FALSE;
4258  lpItem = NULL;
4259  }
4260  else
4261  {
4262  HDPA hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, lpLVItem->iItem);
4263  lpItem = DPA_GetPtr(hdpaSubItems, 0);
4264  assert (lpItem);
4265  }
4266 
4267  /* we need to get the lParam and state of the item */
4268  item.iItem = lpLVItem->iItem;
4269  item.iSubItem = lpLVItem->iSubItem;
4270  item.mask = LVIF_STATE | LVIF_PARAM;
4271  item.stateMask = (infoPtr->dwStyle & LVS_OWNERDATA) ? LVIS_FOCUSED | LVIS_SELECTED : ~0;
4272 
4273  item.state = 0;
4274  item.lParam = 0;
4275  if (!isNew && !LISTVIEW_GetItemW(infoPtr, &item)) return FALSE;
4276 
4277  TRACE("oldState=%x, newState=%x\n", item.state, lpLVItem->state);
4278  /* determine what fields will change */
4279  if ((lpLVItem->mask & LVIF_STATE) && ((item.state ^ lpLVItem->state) & stateMask & ~infoPtr->uCallbackMask))
4280  uChanged |= LVIF_STATE;
4281 
4282  if ((lpLVItem->mask & LVIF_IMAGE) && (lpItem->hdr.iImage != lpLVItem->iImage))
4283  uChanged |= LVIF_IMAGE;
4284 
4285  if ((lpLVItem->mask & LVIF_PARAM) && (lpItem->lParam != lpLVItem->lParam))
4286  uChanged |= LVIF_PARAM;
4287 
4288  if ((lpLVItem->mask & LVIF_INDENT) && (lpItem->iIndent != lpLVItem->iIndent))
4289  uChanged |= LVIF_INDENT;
4290 
4291  if ((lpLVItem->mask & LVIF_TEXT) && textcmpWT(lpItem->hdr.pszText, lpLVItem->pszText, isW))
4292  uChanged |= LVIF_TEXT;
4293 
4294  TRACE("change mask=0x%x\n", uChanged);
4295 
4296  memset(&nmlv, 0, sizeof(NMLISTVIEW));
4297  nmlv.iItem = lpLVItem->iItem;
4298  if (lpLVItem->mask & LVIF_STATE)
4299  {
4300  nmlv.uNewState = (item.state & ~stateMask) | (lpLVItem->state & stateMask);
4301  nmlv.uOldState = item.state;
4302  }
4303  nmlv.uChanged = uChanged ? uChanged : lpLVItem->mask;
4304  nmlv.lParam = item.lParam;
4305 
4306  /* Send LVN_ITEMCHANGING notification, if the item is not being inserted
4307  and we are _NOT_ virtual (LVS_OWNERDATA), and change notifications
4308  are enabled. Even nothing really changed we still need to send this,
4309  in this case uChanged mask is just set to passed item mask. */
4310  if (lpItem && !isNew && (infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE))
4311  {
4312  HWND hwndSelf = infoPtr->hwndSelf;
4313 
4314  if (notify_listview(infoPtr, LVN_ITEMCHANGING, &nmlv))
4315  return FALSE;
4316  if (!IsWindow(hwndSelf))
4317  return FALSE;
4318  }
4319 
4320  /* When item is inserted we need to shift existing focus index if new item has lower index. */
4321  if (isNew && (stateMask & ~infoPtr->uCallbackMask & LVIS_FOCUSED) &&
4322  /* this means we won't hit a focus change path later */
4323  ((uChanged & LVIF_STATE) == 0 || (!(lpLVItem->state & LVIS_FOCUSED) && (infoPtr->nFocusedItem != lpLVItem->iItem))))
4324  {
4325  if (infoPtr->nFocusedItem != -1 && (lpLVItem->iItem <= infoPtr->nFocusedItem))
4326  infoPtr->nFocusedItem++;
4327  }
4328 
4329  if (!uChanged) return TRUE;
4330  *bChanged = TRUE;
4331 
4332  /* copy information */
4333  if (lpLVItem->mask & LVIF_TEXT)
4334  textsetptrT(&lpItem->hdr.pszText, lpLVItem->pszText, isW);
4335 
4336  if (lpLVItem->mask & LVIF_IMAGE)
4337  lpItem->hdr.iImage = lpLVItem->iImage;
4338 
4339  if (lpLVItem->mask & LVIF_PARAM)
4340  lpItem->lParam = lpLVItem->lParam;
4341 
4342  if (lpLVItem->mask & LVIF_INDENT)
4343  lpItem->iIndent = lpLVItem->iIndent;
4344 
4345  if (uChanged & LVIF_STATE)
4346  {
4347  if (lpItem && (stateMask & ~infoPtr->uCallbackMask))
4348  {
4349  lpItem->state &= ~stateMask;
4350  lpItem->state |= (lpLVItem->state & stateMask);
4351  }
4352  if (lpLVItem->state & stateMask & ~infoPtr->uCallbackMask & LVIS_SELECTED)
4353  {
4354  if (infoPtr->dwStyle & LVS_SINGLESEL) LISTVIEW_DeselectAllSkipItem(infoPtr, lpLVItem->iItem);
4355  ranges_additem(infoPtr->selectionRanges, lpLVItem->iItem);
4356  }
4357  else if (stateMask & LVIS_SELECTED)
4358  {
4359  ranges_delitem(infoPtr->selectionRanges, lpLVItem->iItem);
4360  }
4361  /* If we are asked to change focus, and we manage it, do it.
4362  It's important to have all new item data stored at this point,
4363  because changing existing focus could result in a redrawing operation,
<