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