~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Wine Cross Reference
wine/dlls/comctl32/listview.c

Version: ~ [ wine-1.1.33 ] ~ [ wine-1.1.32 ] ~ [ wine-1.1.31 ] ~ [ wine-1.1.30 ] ~ [ wine-1.1.29 ] ~ [ wine-1.1.28 ] ~ [ wine-1.1.27 ] ~ [ wine-1.1.26 ] ~ [ wine-1.1.25 ] ~ [ wine-1.1.24 ] ~ [ wine-1.1.23 ] ~ [ wine-1.1.22 ] ~ [ wine-1.1.21 ] ~ [ wine-1.1.20 ] ~ [ wine-1.1.19 ] ~ [ wine-1.1.18 ] ~ [ wine-1.1.17 ] ~ [ wine-1.1.16 ] ~ [ wine-1.1.15 ] ~ [ wine-1.1.14 ] ~ [ wine-1.1.13 ] ~ [ wine-1.1.12 ] ~ [ wine-1.1.11 ] ~ [ wine-1.1.10 ] ~ [ wine-1.1.9 ] ~ [ wine-1.1.8 ] ~ [ wine-1.1.7 ] ~ [ wine-1.0.1 ] ~ [ wine-1.1.6 ] ~ [ wine-1.1.5 ] ~ [ wine-1.1.4 ] ~ [ wine-1.1.3 ] ~ [ wine-1.1.2 ] ~ [ wine-1.1.1 ] ~ [ wine-1.1.0 ] ~ [ wine-1.0 ] ~

  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 Nikolay Sivov
 10  *
 11  * This library is free software; you can redistribute it and/or
 12  * modify it under the terms of the GNU Lesser General Public
 13  * License as published by the Free Software Foundation; either
 14  * version 2.1 of the License, or (at your option) any later version.
 15  *
 16  * This library is distributed in the hope that it will be useful,
 17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 19  * Lesser General Public License for more details.
 20  *
 21  * You should have received a copy of the GNU Lesser General Public
 22  * License along with this library; if not, write to the Free Software
 23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 24  *
 25  * NOTES
 26  *
 27  * This code was audited for completeness against the documented features
 28  * of Comctl32.dll version 6.0 on May. 20, 2005, by James Hawkins.
 29  * 
 30  * Unless otherwise noted, we believe this code to be complete, as per
 31  * the specification mentioned above.
 32  * If you discover missing features, or bugs, please note them below.
 33  * 
 34  * TODO:
 35  *
 36  * Default Message Processing
 37  *   -- WM_CREATE: create the icon and small icon image lists at this point only if
 38  *      the LVS_SHAREIMAGELISTS style is not specified.
 39  *   -- WM_WINDOWPOSCHANGED: arrange the list items if the current view is icon
 40  *      or small icon and the LVS_AUTOARRANGE style is specified.
 41  *   -- WM_TIMER
 42  *   -- WM_WININICHANGE
 43  *
 44  * Features
 45  *   -- Hot item handling, mouse hovering
 46  *   -- Workareas support
 47  *   -- Tilemode support
 48  *   -- Groups support
 49  *
 50  * Bugs
 51  *   -- Expand large item in ICON mode when the cursor is flying over the icon or text.
 52  *   -- Support CustomDraw options for _WIN32_IE >= 0x560 (see NMLVCUSTOMDRAW docs).
 53  *   -- LVA_SNAPTOGRID not implemented
 54  *   -- LISTVIEW_ApproximateViewRect partially implemented
 55  *   -- LISTVIEW_SetColumnWidth ignores header images & bitmap
 56  *   -- LISTVIEW_SetIconSpacing is incomplete
 57  *   -- LISTVIEW_StyleChanged doesn't handle some changes too well
 58  *
 59  * Speedups
 60  *   -- LISTVIEW_GetNextItem needs to be rewritten. It is currently
 61  *      linear in the number of items in the list, and this is
 62  *      unacceptable for large lists.
 63  *   -- if list is sorted by item text LISTVIEW_InsertItemT could use
 64  *      binary search to calculate item index (e.g. DPA_Search()).
 65  *      This requires sorted state to be reliably tracked in item modifiers.
 66  *   -- we should keep an ordered array of coordinates in iconic mode
 67  *      this would allow to frame items (iterator_frameditems),
 68  *      and find nearest item (LVFI_NEARESTXY) a lot more efficiently
 69  *
 70  * Flags
 71  *   -- LVIF_COLUMNS
 72  *   -- LVIF_GROUPID
 73  *
 74  * States
 75  *   -- LVIS_ACTIVATING (not currently supported by comctl32.dll version 6.0)
 76  *   -- LVIS_CUT
 77  *   -- LVIS_DROPHILITED
 78  *   -- LVIS_OVERLAYMASK
 79  *
 80  * Styles
 81  *   -- LVS_NOLABELWRAP
 82  *   -- LVS_NOSCROLL (see Q137520)
 83  *   -- LVS_ALIGNTOP
 84  *
 85  * Extended Styles
 86  *   -- LVS_EX_BORDERSELECT
 87  *   -- LVS_EX_FLATSB
 88  *   -- LVS_EX_INFOTIP
 89  *   -- LVS_EX_LABELTIP
 90  *   -- LVS_EX_MULTIWORKAREAS
 91  *   -- LVS_EX_REGIONAL
 92  *   -- LVS_EX_SIMPLESELECT
 93  *   -- LVS_EX_TWOCLICKACTIVATE
 94  *   -- LVS_EX_UNDERLINECOLD
 95  *   -- LVS_EX_UNDERLINEHOT
 96  *   
 97  * Notifications:
 98  *   -- LVN_BEGINSCROLL, LVN_ENDSCROLL
 99  *   -- LVN_GETINFOTIP
100  *   -- LVN_HOTTRACK
101  *   -- LVN_MARQUEEBEGIN
102  *   -- LVN_SETDISPINFO
103  *   -- NM_HOVER
104  *   -- LVN_BEGINRDRAG
105  *
106  * Messages:
107  *   -- LVM_ENABLEGROUPVIEW
108  *   -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
109  *   -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
110  *   -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
111  *   -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
112  *   -- LVM_GETINSERTMARKCOLOR, LVM_SETINSERTMARKCOLOR
113  *   -- LVM_GETINSERTMARKRECT
114  *   -- LVM_GETNUMBEROFWORKAREAS
115  *   -- LVM_GETOUTLINECOLOR, LVM_SETOUTLINECOLOR
116  *   -- LVM_GETSELECTEDCOLUMN, LVM_SETSELECTEDCOLUMN
117  *   -- LVM_GETISEARCHSTRINGW, LVM_GETISEARCHSTRINGA
118  *   -- LVM_GETTILEINFO, LVM_SETTILEINFO
119  *   -- LVM_GETTILEVIEWINFO, LVM_SETTILEVIEWINFO
120  *   -- LVM_GETWORKAREAS, LVM_SETWORKAREAS
121  *   -- LVM_HASGROUP, LVM_INSERTGROUP, LVM_REMOVEGROUP, LVM_REMOVEALLGROUPS
122  *   -- LVM_INSERTGROUPSORTED
123  *   -- LVM_INSERTMARKHITTEST
124  *   -- LVM_ISGROUPVIEWENABLED
125  *   -- LVM_MOVEGROUP
126  *   -- LVM_MOVEITEMTOGROUP
127  *   -- LVM_SETINFOTIP
128  *   -- LVM_SETTILEWIDTH
129  *   -- LVM_SORTGROUPS
130  *
131  * Macros:
132  *   -- ListView_GetHoverTime, ListView_SetHoverTime
133  *   -- ListView_GetISearchString
134  *   -- ListView_GetNumberOfWorkAreas
135  *   -- ListView_GetWorkAreas, ListView_SetWorkAreas
136  *
137  * Functions:
138  *   -- LVGroupComparE
139  *
140  * Known differences in message stream from native control (not known if
141  * these differences cause problems):
142  *   LVM_INSERTITEM issues LVM_SETITEMSTATE and LVM_SETITEM in certain cases.
143  *   LVM_SETITEM does not always issue LVN_ITEMCHANGING/LVN_ITEMCHANGED.
144  *   WM_CREATE does not issue WM_QUERYUISTATE and associated registry
145  *     processing for "USEDOUBLECLICKTIME".
146  */
147 
148 #include "config.h"
149 #include "wine/port.h"
150 
151 #include <assert.h>
152 #include <ctype.h>
153 #include <string.h>
154 #include <stdlib.h>
155 #include <stdarg.h>
156 #include <stdio.h>
157 
158 #include "windef.h"
159 #include "winbase.h"
160 #include "winnt.h"
161 #include "wingdi.h"
162 #include "winuser.h"
163 #include "winnls.h"
164 #include "commctrl.h"
165 #include "comctl32.h"
166 #include "uxtheme.h"
167 
168 #include "wine/debug.h"
169 #include "wine/unicode.h"
170 
171 WINE_DEFAULT_DEBUG_CHANNEL(listview);
172 
173 /* make sure you set this to 0 for production use! */
174 #define DEBUG_RANGES 1
175 
176 typedef struct tagCOLUMN_INFO
177 {
178   RECT rcHeader;        /* tracks the header's rectangle */
179   INT fmt;              /* same as LVCOLUMN.fmt */
180   INT cxMin;
181 } COLUMN_INFO;
182 
183 typedef struct tagITEMHDR
184 {
185   LPWSTR pszText;
186   INT iImage;
187 } ITEMHDR, *LPITEMHDR;
188 
189 typedef struct tagSUBITEM_INFO
190 {
191   ITEMHDR hdr;
192   INT iSubItem;
193 } SUBITEM_INFO;
194 
195 typedef struct tagITEM_ID ITEM_ID;
196 
197 typedef struct tagITEM_INFO
198 {
199   ITEMHDR hdr;
200   UINT state;
201   LPARAM lParam;
202   INT iIndent;
203   ITEM_ID *id;
204 } ITEM_INFO;
205 
206 struct tagITEM_ID
207 {
208   UINT id;   /* item id */
209   HDPA item; /* link to item data */
210 };
211 
212 typedef struct tagRANGE
213 {
214   INT lower;
215   INT upper;
216 } RANGE;
217 
218 typedef struct tagRANGES
219 {
220   HDPA hdpa;
221 } *RANGES;
222 
223 typedef struct tagITERATOR
224 {
225   INT nItem;
226   INT nSpecial;
227   RANGE range;
228   RANGES ranges;
229   INT index;
230 } ITERATOR;
231 
232 typedef struct tagDELAYED_ITEM_EDIT
233 {
234   BOOL fEnabled;
235   INT iItem;
236 } DELAYED_ITEM_EDIT;
237 
238 typedef struct tagLISTVIEW_INFO
239 {
240   HWND hwndSelf;
241   HBRUSH hBkBrush;
242   COLORREF clrBk;
243   COLORREF clrText;
244   COLORREF clrTextBk;
245   HIMAGELIST himlNormal;
246   HIMAGELIST himlSmall;
247   HIMAGELIST himlState;
248   BOOL bLButtonDown;
249   BOOL bRButtonDown;
250   BOOL bDragging;
251   POINT ptClickPos;         /* point where the user clicked */ 
252   BOOL bNoItemMetrics;          /* flags if item metrics are not yet computed */
253   INT nItemHeight;
254   INT nItemWidth;
255   RANGES selectionRanges;
256   INT nSelectionMark;
257   INT nHotItem;
258   SHORT notifyFormat;
259   HWND hwndNotify;
260   RECT rcList;                 /* This rectangle is really the window
261                                 * client rectangle possibly reduced by the 
262                                 * horizontal scroll bar and/or header - see 
263                                 * LISTVIEW_UpdateSize. This rectangle offset
264                                 * by the LISTVIEW_GetOrigin value is in
265                                 * client coordinates   */
266   SIZE iconSize;
267   SIZE iconSpacing;
268   SIZE iconStateSize;
269   UINT uCallbackMask;
270   HWND hwndHeader;
271   HCURSOR hHotCursor;
272   HFONT hDefaultFont;
273   HFONT hFont;
274   INT ntmHeight;                /* Some cached metrics of the font used */
275   INT ntmMaxCharWidth;          /* by the listview to draw items */
276   INT nEllipsisWidth;
277   BOOL bRedraw;                 /* Turns on/off repaints & invalidations */
278   BOOL bAutoarrange;            /* Autoarrange flag when NOT in LVS_AUTOARRANGE */
279   BOOL bFocus;
280   BOOL bDoChangeNotify;         /* send change notification messages? */
281   INT nFocusedItem;
282   RECT rcFocus;
283   DWORD dwStyle;                /* the cached window GWL_STYLE */
284   DWORD dwLvExStyle;            /* extended listview style */
285   DWORD uView;                  /* current view available through LVM_[G,S]ETVIEW */
286   INT nItemCount;               /* the number of items in the list */
287   HDPA hdpaItems;               /* array ITEM_INFO pointers */
288   HDPA hdpaItemIds;             /* array of ITEM_ID pointers */
289   HDPA hdpaPosX;                /* maintains the (X, Y) coordinates of the */
290   HDPA hdpaPosY;                /* items in LVS_ICON, and LVS_SMALLICON modes */
291   HDPA hdpaColumns;             /* array of COLUMN_INFO pointers */
292   BOOL colRectsDirty;           /* trigger column rectangles requery from header */
293   POINT currIconPos;            /* this is the position next icon will be placed */
294   PFNLVCOMPARE pfnCompare;
295   LPARAM lParamSort;
296   HWND hwndEdit;
297   WNDPROC EditWndProc;
298   INT nEditLabelItem;
299   INT nLButtonDownItem;         /* tracks item to reset multiselection on WM_LBUTTONUP */
300   DWORD dwHoverTime;
301   HWND hwndToolTip;
302 
303   DWORD cditemmode;             /* Keep the custom draw flags for an item/row */
304 
305   DWORD lastKeyPressTimestamp;
306   WPARAM charCode;
307   INT nSearchParamLength;
308   WCHAR szSearchParam[ MAX_PATH ];
309   BOOL bIsDrawing;
310   INT nMeasureItemHeight;
311   INT xTrackLine;               /* The x coefficient of the track line or -1 if none */
312   DELAYED_ITEM_EDIT itemEdit;   /* Pointer to this structure will be the timer ID */
313 
314   DWORD iVersion; /* CCM_[G,S]ETVERSION */
315 } LISTVIEW_INFO;
316 
317 /*
318  * constants
319  */
320 /* How many we debug buffer to allocate */
321 #define DEBUG_BUFFERS 20
322 /* The size of a single debug buffer */
323 #define DEBUG_BUFFER_SIZE 256
324 
325 /* Internal interface to LISTVIEW_HScroll and LISTVIEW_VScroll */
326 #define SB_INTERNAL      -1
327 
328 /* maximum size of a label */
329 #define DISP_TEXT_SIZE 512
330 
331 /* padding for items in list and small icon display modes */
332 #define WIDTH_PADDING 12
333 
334 /* padding for items in list, report and small icon display modes */
335 #define HEIGHT_PADDING 1
336 
337 /* offset of items in report display mode */
338 #define REPORT_MARGINX 2
339 
340 /* padding for icon in large icon display mode
341  *   ICON_TOP_PADDING_NOTHITABLE - space between top of box and area
342  *                                 that HITTEST will see.
343  *   ICON_TOP_PADDING_HITABLE - spacing between above and icon.
344  *   ICON_TOP_PADDING - sum of the two above.
345  *   ICON_BOTTOM_PADDING - between bottom of icon and top of text
346  *   LABEL_HOR_PADDING - between text and sides of box
347  *   LABEL_VERT_PADDING - between bottom of text and end of box
348  *
349  *   ICON_LR_PADDING - additional width above icon size.
350  *   ICON_LR_HALF - half of the above value
351  */
352 #define ICON_TOP_PADDING_NOTHITABLE  2
353 #define ICON_TOP_PADDING_HITABLE     2
354 #define ICON_TOP_PADDING (ICON_TOP_PADDING_NOTHITABLE + ICON_TOP_PADDING_HITABLE)
355 #define ICON_BOTTOM_PADDING          4
356 #define LABEL_HOR_PADDING            5
357 #define LABEL_VERT_PADDING           7
358 #define ICON_LR_PADDING              16
359 #define ICON_LR_HALF                 (ICON_LR_PADDING/2)
360 
361 /* default label width for items in list and small icon display modes */
362 #define DEFAULT_LABEL_WIDTH 40
363 /* maximum select rectangle width for empty text item in LV_VIEW_DETAILS */
364 #define MAX_EMPTYTEXT_SELECT_WIDTH 80
365 
366 /* default column width for items in list display mode */
367 #define DEFAULT_COLUMN_WIDTH 128
368 
369 /* Size of "line" scroll for V & H scrolls */
370 #define LISTVIEW_SCROLL_ICON_LINE_SIZE 37
371 
372 /* Padding between image and label */
373 #define IMAGE_PADDING  2
374 
375 /* Padding behind the label */
376 #define TRAILING_LABEL_PADDING  12
377 #define TRAILING_HEADER_PADDING  11
378 
379 /* Border for the icon caption */
380 #define CAPTION_BORDER  2
381 
382 /* Standard DrawText flags */
383 #define LV_ML_DT_FLAGS  (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
384 #define LV_FL_DT_FLAGS  (DT_TOP | DT_NOPREFIX | DT_EDITCONTROL | DT_CENTER | DT_WORDBREAK | DT_NOCLIP)
385 #define LV_SL_DT_FLAGS  (DT_VCENTER | DT_NOPREFIX | DT_EDITCONTROL | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_END_ELLIPSIS)
386 
387 /* Image index from state */
388 #define STATEIMAGEINDEX(x) (((x) & LVIS_STATEIMAGEMASK) >> 12)
389 
390 /* The time in milliseconds to reset the search in the list */
391 #define KEY_DELAY       450
392 
393 /* Dump the LISTVIEW_INFO structure to the debug channel */
394 #define LISTVIEW_DUMP(iP) do { \
395   TRACE("hwndSelf=%p, clrBk=0x%06x, clrText=0x%06x, clrTextBk=0x%06x, ItemHeight=%d, ItemWidth=%d, Style=0x%08x\n", \
396         iP->hwndSelf, iP->clrBk, iP->clrText, iP->clrTextBk, \
397         iP->nItemHeight, iP->nItemWidth, iP->dwStyle); \
398   TRACE("hwndSelf=%p, himlNor=%p, himlSml=%p, himlState=%p, Focused=%d, Hot=%d, exStyle=0x%08x, Focus=%d\n", \
399         iP->hwndSelf, iP->himlNormal, iP->himlSmall, iP->himlState, \
400         iP->nFocusedItem, iP->nHotItem, iP->dwLvExStyle, iP->bFocus ); \
401   TRACE("hwndSelf=%p, ntmH=%d, icSz.cx=%d, icSz.cy=%d, icSp.cx=%d, icSp.cy=%d, notifyFmt=%d\n", \
402         iP->hwndSelf, iP->ntmHeight, iP->iconSize.cx, iP->iconSize.cy, \
403         iP->iconSpacing.cx, iP->iconSpacing.cy, iP->notifyFormat); \
404   TRACE("hwndSelf=%p, rcList=%s\n", iP->hwndSelf, wine_dbgstr_rect(&iP->rcList)); \
405 } while(0)
406 
407 static const WCHAR themeClass[] = {'L','i','s','t','V','i','e','w',0};
408 
409 /*
410  * forward declarations
411  */
412 static BOOL LISTVIEW_GetItemT(const LISTVIEW_INFO *, LPLVITEMW, BOOL);
413 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *, INT, LPRECT);
414 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *, INT, LPPOINT);
415 static BOOL LISTVIEW_GetItemPosition(const LISTVIEW_INFO *, INT, LPPOINT);
416 static BOOL LISTVIEW_GetItemRect(const LISTVIEW_INFO *, INT, LPRECT);
417 static INT LISTVIEW_GetLabelWidth(const LISTVIEW_INFO *, INT);
418 static void LISTVIEW_GetOrigin(const LISTVIEW_INFO *, LPPOINT);
419 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *, LPRECT);
420 static void LISTVIEW_UpdateSize(LISTVIEW_INFO *);
421 static LRESULT LISTVIEW_Command(LISTVIEW_INFO *, WPARAM, LPARAM);
422 static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
423 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
424 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
425 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
426 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT, HWND);
427 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT, HWND);
428 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
429 static HWND CreateEditLabelT(LISTVIEW_INFO *, LPCWSTR, DWORD, BOOL);
430 static HIMAGELIST LISTVIEW_SetImageList(LISTVIEW_INFO *, INT, HIMAGELIST);
431 static INT LISTVIEW_HitTest(const LISTVIEW_INFO *, LPLVHITTESTINFO, BOOL, BOOL);
432 static BOOL LISTVIEW_EndEditLabelT(LISTVIEW_INFO *, BOOL, BOOL);
433 
434 /******** Text handling functions *************************************/
435 
436 /* A text pointer is either NULL, LPSTR_TEXTCALLBACK, or points to a
437  * text string. The string may be ANSI or Unicode, in which case
438  * the boolean isW tells us the type of the string.
439  *
440  * The name of the function tell what type of strings it expects:
441  *   W: Unicode, T: ANSI/Unicode - function of isW
442  */
443 
444 static inline BOOL is_textW(LPCWSTR text)
445 {
446     return text != NULL && text != LPSTR_TEXTCALLBACKW;
447 }
448 
449 static inline BOOL is_textT(LPCWSTR text, BOOL isW)
450 {
451     /* we can ignore isW since LPSTR_TEXTCALLBACKW == LPSTR_TEXTCALLBACKA */
452     return is_textW(text);
453 }
454 
455 static inline int textlenT(LPCWSTR text, BOOL isW)
456 {
457     return !is_textT(text, isW) ? 0 :
458            isW ? lstrlenW(text) : lstrlenA((LPCSTR)text);
459 }
460 
461 static inline void textcpynT(LPWSTR dest, BOOL isDestW, LPCWSTR src, BOOL isSrcW, INT max)
462 {
463     if (isDestW)
464         if (isSrcW) lstrcpynW(dest, src, max);
465         else MultiByteToWideChar(CP_ACP, 0, (LPCSTR)src, -1, dest, max);
466     else
467         if (isSrcW) WideCharToMultiByte(CP_ACP, 0, src, -1, (LPSTR)dest, max, NULL, NULL);
468         else lstrcpynA((LPSTR)dest, (LPCSTR)src, max);
469 }
470 
471 static inline LPWSTR textdupTtoW(LPCWSTR text, BOOL isW)
472 {
473     LPWSTR wstr = (LPWSTR)text;
474 
475     if (!isW && is_textT(text, isW))
476     {
477         INT len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, NULL, 0);
478         wstr = Alloc(len * sizeof(WCHAR));
479         if (wstr) MultiByteToWideChar(CP_ACP, 0, (LPCSTR)text, -1, wstr, len);
480     }
481     TRACE("   wstr=%s\n", text == LPSTR_TEXTCALLBACKW ?  "(callback)" : debugstr_w(wstr));
482     return wstr;
483 }
484 
485 static inline void textfreeT(LPWSTR wstr, BOOL isW)
486 {
487     if (!isW && is_textT(wstr, isW)) Free (wstr);
488 }
489 
490 /*
491  * dest is a pointer to a Unicode string
492  * src is a pointer to a string (Unicode if isW, ANSI if !isW)
493  */
494 static BOOL textsetptrT(LPWSTR *dest, LPCWSTR src, BOOL isW)
495 {
496     BOOL bResult = TRUE;
497     
498     if (src == LPSTR_TEXTCALLBACKW)
499     {
500         if (is_textW(*dest)) Free(*dest);
501         *dest = LPSTR_TEXTCALLBACKW;
502     }
503     else
504     {
505         LPWSTR pszText = textdupTtoW(src, isW);
506         if (*dest == LPSTR_TEXTCALLBACKW) *dest = NULL;
507         bResult = Str_SetPtrW(dest, pszText);
508         textfreeT(pszText, isW);
509     }
510     return bResult;
511 }
512 
513 /*
514  * compares a Unicode to a Unicode/ANSI text string
515  */
516 static inline int textcmpWT(LPCWSTR aw, LPCWSTR bt, BOOL isW)
517 {
518     if (!aw) return bt ? -1 : 0;
519     if (!bt) return aw ? 1 : 0;
520     if (aw == LPSTR_TEXTCALLBACKW)
521         return bt == LPSTR_TEXTCALLBACKW ? 1 : -1;
522     if (bt != LPSTR_TEXTCALLBACKW)
523     {
524         LPWSTR bw = textdupTtoW(bt, isW);
525         int r = bw ? lstrcmpW(aw, bw) : 1;
526         textfreeT(bw, isW);
527         return r;
528     }       
529             
530     return 1;
531 }
532     
533 static inline int lstrncmpiW(LPCWSTR s1, LPCWSTR s2, int n)
534 {
535     int res;
536 
537     n = min(min(n, lstrlenW(s1)), lstrlenW(s2));
538     res = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, s1, n, s2, n);
539     return res ? res - sizeof(WCHAR) : res;
540 }
541 
542 /******** Debugging functions *****************************************/
543 
544 static inline LPCSTR debugtext_t(LPCWSTR text, BOOL isW)
545 {
546     if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
547     return isW ? debugstr_w(text) : debugstr_a((LPCSTR)text);
548 }
549 
550 static inline LPCSTR debugtext_tn(LPCWSTR text, BOOL isW, INT n)
551 {
552     if (text == LPSTR_TEXTCALLBACKW) return "(callback)";
553     n = min(textlenT(text, isW), n);
554     return isW ? debugstr_wn(text, n) : debugstr_an((LPCSTR)text, n);
555 }
556 
557 static char* debug_getbuf(void)
558 {
559     static int index = 0;
560     static char buffers[DEBUG_BUFFERS][DEBUG_BUFFER_SIZE];
561     return buffers[index++ % DEBUG_BUFFERS];
562 }
563 
564 static inline const char* debugrange(const RANGE *lprng)
565 {
566     if (!lprng) return "(null)";
567     return wine_dbg_sprintf("[%d, %d]", lprng->lower, lprng->upper);
568 }
569 
570 static const char* debugscrollinfo(const SCROLLINFO *pScrollInfo)
571 {
572     char* buf = debug_getbuf(), *text = buf;
573     int len, size = DEBUG_BUFFER_SIZE;
574 
575     if (pScrollInfo == NULL) return "(null)";
576     len = snprintf(buf, size, "{cbSize=%d, ", pScrollInfo->cbSize);
577     if (len == -1) goto end; buf += len; size -= len;
578     if (pScrollInfo->fMask & SIF_RANGE)
579         len = snprintf(buf, size, "nMin=%d, nMax=%d, ", pScrollInfo->nMin, pScrollInfo->nMax);
580     else len = 0;
581     if (len == -1) goto end; buf += len; size -= len;
582     if (pScrollInfo->fMask & SIF_PAGE)
583         len = snprintf(buf, size, "nPage=%u, ", pScrollInfo->nPage);
584     else len = 0;
585     if (len == -1) goto end; buf += len; size -= len;
586     if (pScrollInfo->fMask & SIF_POS)
587         len = snprintf(buf, size, "nPos=%d, ", pScrollInfo->nPos);
588     else len = 0;
589     if (len == -1) goto end; buf += len; size -= len;
590     if (pScrollInfo->fMask & SIF_TRACKPOS)
591         len = snprintf(buf, size, "nTrackPos=%d, ", pScrollInfo->nTrackPos);
592     else len = 0;
593     if (len == -1) goto end; buf += len; size -= len;
594     goto undo;
595 end:
596     buf = text + strlen(text);
597 undo:
598     if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
599     return text;
600 } 
601 
602 static const char* debugnmlistview(const NMLISTVIEW *plvnm)
603 {
604     if (!plvnm) return "(null)";
605     return wine_dbg_sprintf("iItem=%d, iSubItem=%d, uNewState=0x%x,"
606                  " uOldState=0x%x, uChanged=0x%x, ptAction=%s, lParam=%ld",
607                  plvnm->iItem, plvnm->iSubItem, plvnm->uNewState, plvnm->uOldState,
608                  plvnm->uChanged, wine_dbgstr_point(&plvnm->ptAction), plvnm->lParam);
609 }
610 
611 static const char* debuglvitem_t(const LVITEMW *lpLVItem, BOOL isW)
612 {
613     char* buf = debug_getbuf(), *text = buf;
614     int len, size = DEBUG_BUFFER_SIZE;
615     
616     if (lpLVItem == NULL) return "(null)";
617     len = snprintf(buf, size, "{iItem=%d, iSubItem=%d, ", lpLVItem->iItem, lpLVItem->iSubItem);
618     if (len == -1) goto end; buf += len; size -= len;
619     if (lpLVItem->mask & LVIF_STATE)
620         len = snprintf(buf, size, "state=%x, stateMask=%x, ", lpLVItem->state, lpLVItem->stateMask);
621     else len = 0;
622     if (len == -1) goto end; buf += len; size -= len;
623     if (lpLVItem->mask & LVIF_TEXT)
624         len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpLVItem->pszText, isW, 80), lpLVItem->cchTextMax);
625     else len = 0;
626     if (len == -1) goto end; buf += len; size -= len;
627     if (lpLVItem->mask & LVIF_IMAGE)
628         len = snprintf(buf, size, "iImage=%d, ", lpLVItem->iImage);
629     else len = 0;
630     if (len == -1) goto end; buf += len; size -= len;
631     if (lpLVItem->mask & LVIF_PARAM)
632         len = snprintf(buf, size, "lParam=%lx, ", lpLVItem->lParam);
633     else len = 0;
634     if (len == -1) goto end; buf += len; size -= len;
635     if (lpLVItem->mask & LVIF_INDENT)
636         len = snprintf(buf, size, "iIndent=%d, ", lpLVItem->iIndent);
637     else len = 0;
638     if (len == -1) goto end; buf += len; size -= len;
639     goto undo;
640 end:
641     buf = text + strlen(text);
642 undo:
643     if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
644     return text;
645 }
646 
647 static const char* debuglvcolumn_t(const LVCOLUMNW *lpColumn, BOOL isW)
648 {
649     char* buf = debug_getbuf(), *text = buf;
650     int len, size = DEBUG_BUFFER_SIZE;
651     
652     if (lpColumn == NULL) return "(null)";
653     len = snprintf(buf, size, "{");
654     if (len == -1) goto end; buf += len; size -= len;
655     if (lpColumn->mask & LVCF_SUBITEM)
656         len = snprintf(buf, size, "iSubItem=%d, ",  lpColumn->iSubItem);
657     else len = 0;
658     if (len == -1) goto end; buf += len; size -= len;
659     if (lpColumn->mask & LVCF_FMT)
660         len = snprintf(buf, size, "fmt=%x, ", lpColumn->fmt);
661     else len = 0;
662     if (len == -1) goto end; buf += len; size -= len;
663     if (lpColumn->mask & LVCF_WIDTH)
664         len = snprintf(buf, size, "cx=%d, ", lpColumn->cx);
665     else len = 0;
666     if (len == -1) goto end; buf += len; size -= len;
667     if (lpColumn->mask & LVCF_TEXT)
668         len = snprintf(buf, size, "pszText=%s, cchTextMax=%d, ", debugtext_tn(lpColumn->pszText, isW, 80), lpColumn->cchTextMax);
669     else len = 0;
670     if (len == -1) goto end; buf += len; size -= len;
671     if (lpColumn->mask & LVCF_IMAGE)
672         len = snprintf(buf, size, "iImage=%d, ", lpColumn->iImage);
673     else len = 0;
674     if (len == -1) goto end; buf += len; size -= len;
675     if (lpColumn->mask & LVCF_ORDER)
676         len = snprintf(buf, size, "iOrder=%d, ", lpColumn->iOrder);
677     else len = 0;
678     if (len == -1) goto end; buf += len; size -= len;
679     goto undo;
680 end:
681     buf = text + strlen(text);
682 undo:
683     if (buf - text > 2) { buf[-2] = '}'; buf[-1] = 0; }
684     return text;
685 }
686 
687 static const char* debuglvhittestinfo(const LVHITTESTINFO *lpht)
688 {
689     if (!lpht) return "(null)";
690 
691     return wine_dbg_sprintf("{pt=%s, flags=0x%x, iItem=%d, iSubItem=%d}",
692                  wine_dbgstr_point(&lpht->pt), lpht->flags, lpht->iItem, lpht->iSubItem);
693 }
694 
695 /* Return the corresponding text for a given scroll value */
696 static inline LPCSTR debugscrollcode(int nScrollCode)
697 {
698   switch(nScrollCode)
699   {
700   case SB_LINELEFT: return "SB_LINELEFT";
701   case SB_LINERIGHT: return "SB_LINERIGHT";
702   case SB_PAGELEFT: return "SB_PAGELEFT";
703   case SB_PAGERIGHT: return "SB_PAGERIGHT";
704   case SB_THUMBPOSITION: return "SB_THUMBPOSITION";
705   case SB_THUMBTRACK: return "SB_THUMBTRACK";
706   case SB_ENDSCROLL: return "SB_ENDSCROLL";
707   case SB_INTERNAL: return "SB_INTERNAL";
708   default: return "unknown";
709   }
710 }
711 
712 
713 /******** Notification functions ************************************/
714 
715 static int get_ansi_notification(UINT unicodeNotificationCode)
716 {
717     switch (unicodeNotificationCode)
718     {
719     case LVN_BEGINLABELEDITW: return LVN_BEGINLABELEDITA;
720     case LVN_ENDLABELEDITW: return LVN_ENDLABELEDITA;
721     case LVN_GETDISPINFOW: return LVN_GETDISPINFOA;
722     case LVN_SETDISPINFOW: return LVN_SETDISPINFOA;
723     case LVN_ODFINDITEMW: return LVN_ODFINDITEMA;
724     case LVN_GETINFOTIPW: return LVN_GETINFOTIPA;
725     /* header forwards */
726     case HDN_TRACKW: return HDN_TRACKA;
727     case HDN_ENDTRACKW: return HDN_ENDTRACKA;
728     case HDN_BEGINDRAG: return HDN_BEGINDRAG;
729     case HDN_ENDDRAG: return HDN_ENDDRAG;
730     case HDN_ITEMCHANGINGW: return HDN_ITEMCHANGINGA;
731     case HDN_ITEMCHANGEDW: return HDN_ITEMCHANGEDA;
732     case HDN_ITEMCLICKW: return HDN_ITEMCLICKA;
733     case HDN_DIVIDERDBLCLICKW: return HDN_DIVIDERDBLCLICKA;
734     }
735     ERR("unknown notification %x\n", unicodeNotificationCode);
736     assert(FALSE);
737     return 0;
738 }
739 
740 /* forwards header notifications to listview parent */
741 static LRESULT notify_forward_header(const LISTVIEW_INFO *infoPtr, const NMHEADERW *lpnmh)
742 {
743     NMHEADERA nmhA;
744     HDITEMA hditema;
745     HD_TEXTFILTERA textfilter;
746     LPSTR text = NULL, filter = NULL;
747     LRESULT ret;
748 
749     /* on unicode format exit earlier */
750     if (infoPtr->notifyFormat == NFR_UNICODE)
751         return SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
752                             (WPARAM)lpnmh->hdr.idFrom, (LPARAM)lpnmh);
753 
754     /* header always supplies unicode notifications,
755        all we have to do is to convert strings to ANSI */
756     nmhA = *(NMHEADERA*)lpnmh;
757     if (lpnmh->pitem)
758     {
759         hditema = *(HDITEMA*)lpnmh->pitem;
760         nmhA.pitem = &hditema;
761         /* convert item text */
762         if (lpnmh->pitem->mask & HDI_TEXT)
763         {
764             hditema.pszText = NULL;
765             Str_SetPtrWtoA(&hditema.pszText, lpnmh->pitem->pszText);
766             text = hditema.pszText;
767         }
768         /* convert filter text */
769         if ((lpnmh->pitem->mask & HDI_FILTER) && (lpnmh->pitem->type == HDFT_ISSTRING) &&
770              lpnmh->pitem->pvFilter)
771         {
772             hditema.pvFilter = &textfilter;
773             textfilter = *(HD_TEXTFILTERA*)(lpnmh->pitem->pvFilter);
774             textfilter.pszText = NULL;
775             Str_SetPtrWtoA(&textfilter.pszText, ((HD_TEXTFILTERW*)lpnmh->pitem->pvFilter)->pszText);
776             filter = textfilter.pszText;
777         }
778     }
779     nmhA.hdr.code = get_ansi_notification(lpnmh->hdr.code);
780 
781     ret = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY,
782                        (WPARAM)nmhA.hdr.idFrom, (LPARAM)&nmhA);
783 
784     /* cleanup */
785     Free(text);
786     Free(filter);
787 
788     return ret;
789 }
790 
791 static LRESULT notify_hdr(const LISTVIEW_INFO *infoPtr, INT code, LPNMHDR pnmh)
792 {
793     LRESULT result;
794     
795     TRACE("(code=%d)\n", code);
796 
797     pnmh->hwndFrom = infoPtr->hwndSelf;
798     pnmh->idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
799     pnmh->code = code;
800     result = SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, pnmh->idFrom, (LPARAM)pnmh);
801 
802     TRACE("  <= %ld\n", result);
803 
804     return result;
805 }
806 
807 static inline BOOL notify(const LISTVIEW_INFO *infoPtr, INT code)
808 {
809     NMHDR nmh;
810     HWND hwnd = infoPtr->hwndSelf;
811     notify_hdr(infoPtr, code, &nmh);
812     return IsWindow(hwnd);
813 }
814 
815 static inline void notify_itemactivate(const LISTVIEW_INFO *infoPtr, const LVHITTESTINFO *htInfo)
816 {
817     NMITEMACTIVATE nmia;
818     LVITEMW item;
819 
820     if (htInfo) {
821       nmia.uNewState = 0;
822       nmia.uOldState = 0;
823       nmia.uChanged  = 0;
824       nmia.uKeyFlags = 0;
825       
826       item.mask = LVIF_PARAM|LVIF_STATE;
827       item.iItem = htInfo->iItem;
828       item.iSubItem = 0;
829       if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) {
830           nmia.lParam = item.lParam;
831           nmia.uOldState = item.state;
832           nmia.uNewState = item.state | LVIS_ACTIVATING;
833           nmia.uChanged  = LVIF_STATE;
834       }
835       
836       nmia.iItem = htInfo->iItem;
837       nmia.iSubItem = htInfo->iSubItem;
838       nmia.ptAction = htInfo->pt;     
839       
840       if (GetKeyState(VK_SHIFT) & 0x8000) nmia.uKeyFlags |= LVKF_SHIFT;
841       if (GetKeyState(VK_CONTROL) & 0x8000) nmia.uKeyFlags |= LVKF_CONTROL;
842       if (GetKeyState(VK_MENU) & 0x8000) nmia.uKeyFlags |= LVKF_ALT;
843     }
844     notify_hdr(infoPtr, LVN_ITEMACTIVATE, (LPNMHDR)&nmia);
845 }
846 
847 static inline LRESULT notify_listview(const LISTVIEW_INFO *infoPtr, INT code, LPNMLISTVIEW plvnm)
848 {
849     TRACE("(code=%d, plvnm=%s)\n", code, debugnmlistview(plvnm));
850     return notify_hdr(infoPtr, code, (LPNMHDR)plvnm);
851 }
852 
853 static BOOL notify_click(const LISTVIEW_INFO *infoPtr, INT code, const LVHITTESTINFO *lvht)
854 {
855     NMITEMACTIVATE nmia;
856     LVITEMW item;
857     HWND hwnd = infoPtr->hwndSelf;
858 
859     TRACE("code=%d, lvht=%s\n", code, debuglvhittestinfo(lvht)); 
860     ZeroMemory(&nmia, sizeof(nmia));
861     nmia.iItem = lvht->iItem;
862     nmia.iSubItem = lvht->iSubItem;
863     nmia.ptAction = lvht->pt;
864     item.mask = LVIF_PARAM;
865     item.iItem = lvht->iItem;
866     item.iSubItem = 0;
867     if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmia.lParam = item.lParam;
868     notify_hdr(infoPtr, code, (LPNMHDR)&nmia);
869     return IsWindow(hwnd);
870 }
871 
872 static BOOL notify_deleteitem(const LISTVIEW_INFO *infoPtr, INT nItem)
873 {
874     NMLISTVIEW nmlv;
875     LVITEMW item;
876     HWND hwnd = infoPtr->hwndSelf;
877 
878     ZeroMemory(&nmlv, sizeof (NMLISTVIEW));
879     nmlv.iItem = nItem;
880     item.mask = LVIF_PARAM;
881     item.iItem = nItem;
882     item.iSubItem = 0;
883     if (LISTVIEW_GetItemT(infoPtr, &item, TRUE)) nmlv.lParam = item.lParam;
884     notify_listview(infoPtr, LVN_DELETEITEM, &nmlv);
885     return IsWindow(hwnd);
886 }
887 
888 /*
889   Send notification. depends on dispinfoW having same
890   structure as dispinfoA.
891   infoPtr : listview struct
892   notificationCode : *Unicode* notification code
893   pdi : dispinfo structure (can be unicode or ansi)
894   isW : TRUE if dispinfo is Unicode
895 */
896 static BOOL notify_dispinfoT(const LISTVIEW_INFO *infoPtr, UINT notificationCode, LPNMLVDISPINFOW pdi, BOOL isW)
897 {
898     BOOL bResult = FALSE;
899     BOOL convertToAnsi = FALSE, convertToUnicode = FALSE;
900     INT cchTempBufMax = 0, savCchTextMax = 0;
901     UINT realNotifCode;
902     LPWSTR pszTempBuf = NULL, savPszText = NULL;
903 
904     if ((pdi->item.mask & LVIF_TEXT) && is_textT(pdi->item.pszText, isW))
905     {
906         convertToAnsi = (isW && infoPtr->notifyFormat == NFR_ANSI);
907         convertToUnicode = (!isW && infoPtr->notifyFormat == NFR_UNICODE);
908     }
909 
910     if (convertToAnsi || convertToUnicode)
911     {
912         if (notificationCode != LVN_GETDISPINFOW)
913         {
914             cchTempBufMax = convertToUnicode ?
915                 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1, NULL, 0):
916                 WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, NULL, 0, NULL, NULL);
917         }
918         else
919         {
920             cchTempBufMax = pdi->item.cchTextMax;
921             *pdi->item.pszText = 0; /* make sure we don't process garbage */
922         }
923 
924         pszTempBuf = Alloc( (convertToUnicode ? sizeof(WCHAR) : sizeof(CHAR)) * cchTempBufMax);
925         if (!pszTempBuf) return FALSE;
926 
927         if (convertToUnicode)
928             MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pdi->item.pszText, -1,
929                                 pszTempBuf, cchTempBufMax);
930         else
931             WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) pszTempBuf,
932                                 cchTempBufMax, NULL, NULL);
933 
934         savCchTextMax = pdi->item.cchTextMax;
935         savPszText = pdi->item.pszText;
936         pdi->item.pszText = pszTempBuf;
937         pdi->item.cchTextMax = cchTempBufMax;
938     }
939 
940     if (infoPtr->notifyFormat == NFR_ANSI)
941         realNotifCode = get_ansi_notification(notificationCode);
942     else
943         realNotifCode = notificationCode;
944     TRACE(" pdi->item=%s\n", debuglvitem_t(&pdi->item, infoPtr->notifyFormat != NFR_ANSI));
945     bResult = notify_hdr(infoPtr, realNotifCode, &pdi->hdr);
946 
947     if (convertToUnicode || convertToAnsi)
948     {
949         if (convertToUnicode) /* note : pointer can be changed by app ! */
950             WideCharToMultiByte(CP_ACP, 0, pdi->item.pszText, -1, (LPSTR) savPszText,
951                                 savCchTextMax, NULL, NULL);
952         else
953             MultiByteToWideChar(CP_ACP, 0, (LPSTR) pdi->item.pszText, -1,
954                                 savPszText, savCchTextMax);
955         pdi->item.pszText = savPszText; /* restores our buffer */
956         pdi->item.cchTextMax = savCchTextMax;
957         Free (pszTempBuf);
958     }
959     return bResult;
960 }
961 
962 static void customdraw_fill(NMLVCUSTOMDRAW *lpnmlvcd, const LISTVIEW_INFO *infoPtr, HDC hdc,
963                             const RECT *rcBounds, const LVITEMW *lplvItem)
964 {
965     ZeroMemory(lpnmlvcd, sizeof(NMLVCUSTOMDRAW));
966     lpnmlvcd->nmcd.hdc = hdc;
967     lpnmlvcd->nmcd.rc = *rcBounds;
968     lpnmlvcd->clrTextBk = infoPtr->clrTextBk;
969     lpnmlvcd->clrText   = infoPtr->clrText;
970     if (!lplvItem) return;
971     lpnmlvcd->nmcd.dwItemSpec = lplvItem->iItem + 1;
972     lpnmlvcd->iSubItem = lplvItem->iSubItem;
973     if (lplvItem->state & LVIS_SELECTED) lpnmlvcd->nmcd.uItemState |= CDIS_SELECTED;
974     if (lplvItem->state & LVIS_FOCUSED) lpnmlvcd->nmcd.uItemState |= CDIS_FOCUS;
975     if (lplvItem->iItem == infoPtr->nHotItem) lpnmlvcd->nmcd.uItemState |= CDIS_HOT;
976     lpnmlvcd->nmcd.lItemlParam = lplvItem->lParam;
977 }
978 
979 static inline DWORD notify_customdraw (const LISTVIEW_INFO *infoPtr, DWORD dwDrawStage, NMLVCUSTOMDRAW *lpnmlvcd)
980 {
981     BOOL isForItem = (lpnmlvcd->nmcd.dwItemSpec != 0);
982     DWORD result;
983 
984     lpnmlvcd->nmcd.dwDrawStage = dwDrawStage;
985     if (isForItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_ITEM; 
986     if (lpnmlvcd->iSubItem) lpnmlvcd->nmcd.dwDrawStage |= CDDS_SUBITEM;
987     if (isForItem) lpnmlvcd->nmcd.dwItemSpec--;
988     result = notify_hdr(infoPtr, NM_CUSTOMDRAW, &lpnmlvcd->nmcd.hdr);
989     if (isForItem) lpnmlvcd->nmcd.dwItemSpec++;
990     return result;
991 }
992 
993 static void prepaint_setup (const LISTVIEW_INFO *infoPtr, HDC hdc, NMLVCUSTOMDRAW *lpnmlvcd, BOOL SubItem)
994 {
995     if (lpnmlvcd->clrTextBk == CLR_DEFAULT)
996         lpnmlvcd->clrTextBk = comctl32_color.clrWindow;
997     if (lpnmlvcd->clrText == CLR_DEFAULT)
998         lpnmlvcd->clrText = comctl32_color.clrWindowText;
999 
1000     /* apparently, for selected items, we have to override the returned values */
1001     if (!SubItem)
1002     {
1003         if (lpnmlvcd->nmcd.uItemState & CDIS_SELECTED)
1004         {
1005             if (infoPtr->bFocus)
1006             {
1007                 lpnmlvcd->clrTextBk = comctl32_color.clrHighlight;
1008                 lpnmlvcd->clrText   = comctl32_color.clrHighlightText;
1009             }
1010             else if (infoPtr->dwStyle & LVS_SHOWSELALWAYS)
1011             {
1012                 lpnmlvcd->clrTextBk = comctl32_color.clr3dFace;
1013                 lpnmlvcd->clrText   = comctl32_color.clrBtnText;
1014             }
1015         }
1016     }
1017 
1018     /* Set the text attributes */
1019     if (lpnmlvcd->clrTextBk != CLR_NONE)
1020     {
1021         SetBkMode(hdc, OPAQUE);
1022         SetBkColor(hdc,lpnmlvcd->clrTextBk);
1023     }
1024     else
1025         SetBkMode(hdc, TRANSPARENT);
1026     SetTextColor(hdc, lpnmlvcd->clrText);
1027 }
1028 
1029 static inline DWORD notify_postpaint (const LISTVIEW_INFO *infoPtr, NMLVCUSTOMDRAW *lpnmlvcd)
1030 {
1031     return notify_customdraw(infoPtr, CDDS_POSTPAINT, lpnmlvcd);
1032 }
1033 
1034 /* returns TRUE when repaint needed, FALSE otherwise */
1035 static BOOL notify_measureitem(LISTVIEW_INFO *infoPtr)
1036 {
1037     MEASUREITEMSTRUCT mis;
1038     mis.CtlType = ODT_LISTVIEW;
1039     mis.CtlID = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1040     mis.itemID = -1;
1041     mis.itemWidth = 0;
1042     mis.itemData = 0;
1043     mis.itemHeight= infoPtr->nItemHeight;
1044     SendMessageW(infoPtr->hwndNotify, WM_MEASUREITEM, mis.CtlID, (LPARAM)&mis);
1045     if (infoPtr->nItemHeight != max(mis.itemHeight, 1))
1046     {
1047         infoPtr->nMeasureItemHeight = infoPtr->nItemHeight = max(mis.itemHeight, 1);
1048         return TRUE;
1049     }
1050     return FALSE;
1051 }
1052 
1053 /******** Item iterator functions **********************************/
1054 
1055 static RANGES ranges_create(int count);
1056 static void ranges_destroy(RANGES ranges);
1057 static BOOL ranges_add(RANGES ranges, RANGE range);
1058 static BOOL ranges_del(RANGES ranges, RANGE range);
1059 static void ranges_dump(RANGES ranges);
1060 
1061 static inline BOOL ranges_additem(RANGES ranges, INT nItem)
1062 {
1063     RANGE range = { nItem, nItem + 1 };
1064 
1065     return ranges_add(ranges, range);
1066 }
1067 
1068 static inline BOOL ranges_delitem(RANGES ranges, INT nItem)
1069 {
1070     RANGE range = { nItem, nItem + 1 };
1071 
1072     return ranges_del(ranges, range);
1073 }
1074 
1075 /***
1076  * ITERATOR DOCUMENTATION
1077  *
1078  * The iterator functions allow for easy, and convenient iteration
1079  * over items of interest in the list. Typically, you create a
1080  * iterator, use it, and destroy it, as such:
1081  *   ITERATOR i;
1082  *
1083  *   iterator_xxxitems(&i, ...);
1084  *   while (iterator_{prev,next}(&i)
1085  *   {
1086  *       //code which uses i.nItem
1087  *   }
1088  *   iterator_destroy(&i);
1089  *
1090  *   where xxx is either: framed, or visible.
1091  * Note that it is important that the code destroys the iterator
1092  * after it's done with it, as the creation of the iterator may
1093  * allocate memory, which thus needs to be freed.
1094  * 
1095  * You can iterate both forwards, and backwards through the list,
1096  * by using iterator_next or iterator_prev respectively.
1097  * 
1098  * Lower numbered items are draw on top of higher number items in
1099  * LVS_ICON, and LVS_SMALLICON (which are the only modes where
1100  * items may overlap). So, to test items, you should use
1101  *    iterator_next
1102  * which lists the items top to bottom (in Z-order).
1103  * For drawing items, you should use
1104  *    iterator_prev
1105  * which lists the items bottom to top (in Z-order).
1106  * If you keep iterating over the items after the end-of-items
1107  * marker (-1) is returned, the iterator will start from the
1108  * beginning. Typically, you don't need to test for -1,
1109  * because iterator_{next,prev} will return TRUE if more items
1110  * are to be iterated over, or FALSE otherwise.
1111  *
1112  * Note: the iterator is defined to be bidirectional. That is,
1113  *       any number of prev followed by any number of next, or
1114  *       five versa, should leave the iterator at the same item:
1115  *           prev * n, next * n = next * n, prev * n
1116  *
1117  * The iterator has a notion of an out-of-order, special item,
1118  * which sits at the start of the list. This is used in
1119  * LVS_ICON, and LVS_SMALLICON mode to handle the focused item,
1120  * which needs to be first, as it may overlap other items.
1121  *           
1122  * The code is a bit messy because we have:
1123  *   - a special item to deal with
1124  *   - simple range, or composite range
1125  *   - empty range.
1126  * If you find bugs, or want to add features, please make sure you
1127  * always check/modify *both* iterator_prev, and iterator_next.
1128  */
1129 
1130 /****
1131  * This function iterates through the items in increasing order,
1132  * but prefixed by the special item, then -1. That is:
1133  *    special, 1, 2, 3, ..., n, -1.
1134  * Each item is listed only once.
1135  */
1136 static inline BOOL iterator_next(ITERATOR* i)
1137 {
1138     if (i->nItem == -1)
1139     {
1140         i->nItem = i->nSpecial;
1141         if (i->nItem != -1) return TRUE;
1142     }
1143     if (i->nItem == i->nSpecial)
1144     {
1145         if (i->ranges) i->index = 0;
1146         goto pickarange;
1147     }
1148 
1149     i->nItem++;
1150 testitem:
1151     if (i->nItem == i->nSpecial) i->nItem++;
1152     if (i->nItem < i->range.upper) return TRUE;
1153 
1154 pickarange:
1155     if (i->ranges)
1156     {
1157         if (i->index < DPA_GetPtrCount(i->ranges->hdpa))
1158             i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, i->index++);
1159         else goto end;
1160     }
1161     else if (i->nItem >= i->range.upper) goto end;
1162 
1163     i->nItem = i->range.lower;
1164     if (i->nItem >= 0) goto testitem;
1165 end:
1166     i->nItem = -1;
1167     return FALSE;
1168 }
1169 
1170 /****
1171  * This function iterates through the items in decreasing order,
1172  * followed by the special item, then -1. That is:
1173  *    n, n-1, ..., 3, 2, 1, special, -1.
1174  * Each item is listed only once.
1175  */
1176 static inline BOOL iterator_prev(ITERATOR* i)
1177 {
1178     BOOL start = FALSE;
1179 
1180     if (i->nItem == -1)
1181     {
1182         start = TRUE;
1183         if (i->ranges) i->index = DPA_GetPtrCount(i->ranges->hdpa);
1184         goto pickarange;
1185     }
1186     if (i->nItem == i->nSpecial)
1187     {
1188         i->nItem = -1;
1189         return FALSE;
1190     }
1191 
1192 testitem:
1193     i->nItem--;
1194     if (i->nItem == i->nSpecial) i->nItem--;
1195     if (i->nItem >= i->range.lower) return TRUE;
1196 
1197 pickarange:
1198     if (i->ranges)
1199     {
1200         if (i->index > 0)
1201             i->range = *(RANGE*)DPA_GetPtr(i->ranges->hdpa, --i->index);
1202         else goto end;
1203     }
1204     else if (!start && i->nItem < i->range.lower) goto end;
1205 
1206     i->nItem = i->range.upper;
1207     if (i->nItem > 0) goto testitem;
1208 end:
1209     return (i->nItem = i->nSpecial) != -1;
1210 }
1211 
1212 static RANGE iterator_range(const ITERATOR *i)
1213 {
1214     RANGE range;
1215 
1216     if (!i->ranges) return i->range;
1217 
1218     if (DPA_GetPtrCount(i->ranges->hdpa) > 0)
1219     {
1220         range.lower = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, 0)).lower;
1221         range.upper = (*(RANGE*)DPA_GetPtr(i->ranges->hdpa, DPA_GetPtrCount(i->ranges->hdpa) - 1)).upper;
1222     }
1223     else range.lower = range.upper = 0;
1224 
1225     return range;
1226 }
1227 
1228 /***
1229  * Releases resources associated with this ierator.
1230  */
1231 static inline void iterator_destroy(const ITERATOR *i)
1232 {
1233     ranges_destroy(i->ranges);
1234 }
1235 
1236 /***
1237  * Create an empty iterator.
1238  */
1239 static inline BOOL iterator_empty(ITERATOR* i)
1240 {
1241     ZeroMemory(i, sizeof(*i));
1242     i->nItem = i->nSpecial = i->range.lower = i->range.upper = -1;
1243     return TRUE;
1244 }
1245 
1246 /***
1247  * Create an iterator over a range.
1248  */
1249 static inline BOOL iterator_rangeitems(ITERATOR* i, RANGE range)
1250 {
1251     iterator_empty(i);
1252     i->range = range;
1253     return TRUE;
1254 }
1255 
1256 /***
1257  * Create an iterator over a bunch of ranges.
1258  * Please note that the iterator will take ownership of the ranges,
1259  * and will free them upon destruction.
1260  */
1261 static inline BOOL iterator_rangesitems(ITERATOR* i, RANGES ranges)
1262 {
1263     iterator_empty(i);
1264     i->ranges = ranges;
1265     return TRUE;
1266 }
1267 
1268 /***
1269  * Creates an iterator over the items which intersect lprc.
1270  */
1271 static BOOL iterator_frameditems(ITERATOR* i, const LISTVIEW_INFO* infoPtr, const RECT *lprc)
1272 {
1273     RECT frame = *lprc, rcItem, rcTemp;
1274     POINT Origin;
1275     
1276     /* in case we fail, we want to return an empty iterator */
1277     if (!iterator_empty(i)) return FALSE;
1278 
1279     LISTVIEW_GetOrigin(infoPtr, &Origin);
1280 
1281     TRACE("(lprc=%s)\n", wine_dbgstr_rect(lprc));
1282     OffsetRect(&frame, -Origin.x, -Origin.y);
1283 
1284     if (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON)
1285     {
1286         INT nItem;
1287         
1288         if (infoPtr->uView == LV_VIEW_ICON && infoPtr->nFocusedItem != -1)
1289         {
1290             LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcItem);
1291             if (IntersectRect(&rcTemp, &rcItem, lprc))
1292                 i->nSpecial = infoPtr->nFocusedItem;
1293         }
1294         if (!(iterator_rangesitems(i, ranges_create(50)))) return FALSE;
1295         /* to do better here, we need to have PosX, and PosY sorted */
1296         TRACE("building icon ranges:\n");
1297         for (nItem = 0; nItem < infoPtr->nItemCount; nItem++)
1298         {
1299             rcItem.left = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
1300             rcItem.top = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
1301             rcItem.right = rcItem.left + infoPtr->nItemWidth;
1302             rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1303             if (IntersectRect(&rcTemp, &rcItem, &frame))
1304                 ranges_additem(i->ranges, nItem);
1305         }
1306         return TRUE;
1307     }
1308     else if (infoPtr->uView == LV_VIEW_DETAILS)
1309     {
1310         RANGE range;
1311         
1312         if (frame.left >= infoPtr->nItemWidth) return TRUE;
1313         if (frame.top >= infoPtr->nItemHeight * infoPtr->nItemCount) return TRUE;
1314         
1315         range.lower = max(frame.top / infoPtr->nItemHeight, 0);
1316         range.upper = min((frame.bottom - 1) / infoPtr->nItemHeight, infoPtr->nItemCount - 1) + 1;
1317         if (range.upper <= range.lower) return TRUE;
1318         if (!iterator_rangeitems(i, range)) return FALSE;
1319         TRACE("    report=%s\n", debugrange(&i->range));
1320     }
1321     else
1322     {
1323         INT nPerCol = max((infoPtr->rcList.bottom - infoPtr->rcList.top) / infoPtr->nItemHeight, 1);
1324         INT nFirstRow = max(frame.top / infoPtr->nItemHeight, 0);
1325         INT nLastRow = min((frame.bottom - 1) / infoPtr->nItemHeight, nPerCol - 1);
1326         INT nFirstCol;
1327         INT nLastCol;
1328         INT lower;
1329         RANGE item_range;
1330         INT nCol;
1331 
1332         if (infoPtr->nItemWidth)
1333         {
1334             nFirstCol = max(frame.left / infoPtr->nItemWidth, 0);
1335             nLastCol  = min((frame.right - 1) / infoPtr->nItemWidth, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1336         }
1337         else
1338         {
1339             nFirstCol = max(frame.left, 0);
1340             nLastCol  = min(frame.right - 1, (infoPtr->nItemCount + nPerCol - 1) / nPerCol);
1341         }
1342 
1343         lower = nFirstCol * nPerCol + nFirstRow;
1344 
1345         TRACE("nPerCol=%d, nFirstRow=%d, nLastRow=%d, nFirstCol=%d, nLastCol=%d, lower=%d\n",
1346               nPerCol, nFirstRow, nLastRow, nFirstCol, nLastCol, lower);
1347         
1348         if (nLastCol < nFirstCol || nLastRow < nFirstRow) return TRUE;
1349 
1350         if (!(iterator_rangesitems(i, ranges_create(nLastCol - nFirstCol + 1)))) return FALSE;
1351         TRACE("building list ranges:\n");
1352         for (nCol = nFirstCol; nCol <= nLastCol; nCol++)
1353         {
1354             item_range.lower = nCol * nPerCol + nFirstRow;
1355             if(item_range.lower >= infoPtr->nItemCount) break;
1356             item_range.upper = min(nCol * nPerCol + nLastRow + 1, infoPtr->nItemCount);
1357             TRACE("   list=%s\n", debugrange(&item_range));
1358             ranges_add(i->ranges, item_range);
1359         }
1360     }
1361 
1362     return TRUE;
1363 }
1364 
1365 /***
1366  * Creates an iterator over the items which intersect the visible region of hdc.
1367  */
1368 static BOOL iterator_visibleitems(ITERATOR *i, const LISTVIEW_INFO *infoPtr, HDC  hdc)
1369 {
1370     POINT Origin, Position;
1371     RECT rcItem, rcClip;
1372     INT rgntype;
1373     
1374     rgntype = GetClipBox(hdc, &rcClip);
1375     if (rgntype == NULLREGION) return iterator_empty(i);
1376     if (!iterator_frameditems(i, infoPtr, &rcClip)) return FALSE;
1377     if (rgntype == SIMPLEREGION) return TRUE;
1378 
1379     /* first deal with the special item */
1380     if (i->nSpecial != -1)
1381     {
1382         LISTVIEW_GetItemBox(infoPtr, i->nSpecial, &rcItem);
1383         if (!RectVisible(hdc, &rcItem)) i->nSpecial = -1;
1384     }
1385     
1386     /* if we can't deal with the region, we'll just go with the simple range */
1387     LISTVIEW_GetOrigin(infoPtr, &Origin);
1388     TRACE("building visible range:\n");
1389     if (!i->ranges && i->range.lower < i->range.upper)
1390     {
1391         if (!(i->ranges = ranges_create(50))) return TRUE;
1392         if (!ranges_add(i->ranges, i->range))
1393         {
1394             ranges_destroy(i->ranges);
1395             i->ranges = 0;
1396             return TRUE;
1397         }
1398     }
1399 
1400     /* now delete the invisible items from the list */
1401     while(iterator_next(i))
1402     {
1403         LISTVIEW_GetItemOrigin(infoPtr, i->nItem, &Position);
1404         rcItem.left = (infoPtr->uView == LV_VIEW_DETAILS) ? Origin.x : Position.x + Origin.x;
1405         rcItem.top = Position.y + Origin.y;
1406         rcItem.right = rcItem.left + infoPtr->nItemWidth;
1407         rcItem.bottom = rcItem.top + infoPtr->nItemHeight;
1408         if (!RectVisible(hdc, &rcItem))
1409             ranges_delitem(i->ranges, i->nItem);
1410     }
1411     /* the iterator should restart on the next iterator_next */
1412     TRACE("done\n");
1413     
1414     return TRUE;
1415 }
1416 
1417 /******** Misc helper functions ************************************/
1418 
1419 static inline LRESULT CallWindowProcT(WNDPROC proc, HWND hwnd, UINT uMsg,
1420                                       WPARAM wParam, LPARAM lParam, BOOL isW)
1421 {
1422     if (isW) return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
1423     else return CallWindowProcA(proc, hwnd, uMsg, wParam, lParam);
1424 }
1425 
1426 static inline BOOL is_autoarrange(const LISTVIEW_INFO *infoPtr)
1427 {
1428     return ((infoPtr->dwStyle & LVS_AUTOARRANGE) || infoPtr->bAutoarrange) &&
1429            (infoPtr->uView == LV_VIEW_ICON || infoPtr->uView == LV_VIEW_SMALLICON);
1430 }
1431 
1432 static void toggle_checkbox_state(LISTVIEW_INFO *infoPtr, INT nItem)
1433 {
1434     DWORD state = STATEIMAGEINDEX(LISTVIEW_GetItemState(infoPtr, nItem, LVIS_STATEIMAGEMASK));
1435     if(state == 1 || state == 2)
1436     {
1437         LVITEMW lvitem;
1438         state ^= 3;
1439         lvitem.state = INDEXTOSTATEIMAGEMASK(state);
1440         lvitem.stateMask = LVIS_STATEIMAGEMASK;
1441         LISTVIEW_SetItemState(infoPtr, nItem, &lvitem);
1442     }
1443 }
1444 
1445 /* this should be called after window style got updated,
1446    it used to reset view state to match current window style */
1447 static inline void map_style_view(LISTVIEW_INFO *infoPtr)
1448 {
1449     switch (infoPtr->dwStyle & LVS_TYPEMASK)
1450     {
1451     case LVS_ICON:
1452         infoPtr->uView = LV_VIEW_ICON;
1453         break;
1454     case LVS_REPORT:
1455         infoPtr->uView = LV_VIEW_DETAILS;
1456         break;
1457     case LVS_SMALLICON:
1458         infoPtr->uView = LV_VIEW_SMALLICON;
1459         break;
1460     case LVS_LIST:
1461         infoPtr->uView = LV_VIEW_LIST;
1462     }
1463 }
1464 
1465 /* computes next item id value */
1466 static DWORD get_next_itemid(const LISTVIEW_INFO *infoPtr)
1467 {
1468     INT count = DPA_GetPtrCount(infoPtr->hdpaItemIds);
1469 
1470     if (count > 0)
1471     {
1472         ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, count - 1);
1473         return lpID->id + 1;
1474     }
1475     return 0;
1476 }
1477 
1478 /******** Internal API functions ************************************/
1479 
1480 static inline COLUMN_INFO * LISTVIEW_GetColumnInfo(const LISTVIEW_INFO *infoPtr, INT nSubItem)
1481 {
1482     static COLUMN_INFO mainItem;
1483 
1484     if (nSubItem == 0 && DPA_GetPtrCount(infoPtr->hdpaColumns) == 0) return &mainItem;
1485     assert (nSubItem >= 0 && nSubItem < DPA_GetPtrCount(infoPtr->hdpaColumns));
1486 
1487     /* update cached column rectangles */
1488     if (infoPtr->colRectsDirty)
1489     {
1490         COLUMN_INFO *info;
1491         LISTVIEW_INFO *Ptr = (LISTVIEW_INFO*)infoPtr;
1492         INT i;
1493 
1494         for (i = 0; i < DPA_GetPtrCount(infoPtr->hdpaColumns); i++) {
1495             info = DPA_GetPtr(infoPtr->hdpaColumns, i);
1496             SendMessageW(infoPtr->hwndHeader, HDM_GETITEMRECT, i, (LPARAM)&info->rcHeader);
1497         }
1498         Ptr->colRectsDirty = FALSE;
1499     }
1500 
1501     return DPA_GetPtr(infoPtr->hdpaColumns, nSubItem);
1502 }
1503 
1504 static INT LISTVIEW_CreateHeader(LISTVIEW_INFO *infoPtr)
1505 {
1506     DWORD dFlags = WS_CHILD | HDS_HORZ | HDS_FULLDRAG | HDS_DRAGDROP;
1507     HINSTANCE hInst;
1508 
1509     if (infoPtr->hwndHeader) return 0;
1510 
1511     TRACE("Creating header for list %p\n", infoPtr->hwndSelf);
1512 
1513     /* setup creation flags */
1514     dFlags |= (LVS_NOSORTHEADER & infoPtr->dwStyle) ? 0 : HDS_BUTTONS;
1515     dFlags |= (LVS_NOCOLUMNHEADER & infoPtr->dwStyle) ? HDS_HIDDEN : 0;
1516 
1517     hInst = (HINSTANCE)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_HINSTANCE);
1518 
1519     /* create header */
1520     infoPtr->hwndHeader = CreateWindowW(WC_HEADERW, NULL, dFlags,
1521       0, 0, 0, 0, infoPtr->hwndSelf, NULL, hInst, NULL);
1522     if (!infoPtr->hwndHeader) return -1;
1523 
1524     /* set header unicode format */
1525     SendMessageW(infoPtr->hwndHeader, HDM_SETUNICODEFORMAT, TRUE, 0);
1526 
1527     /* set header font */
1528     SendMessageW(infoPtr->hwndHeader, WM_SETFONT, (WPARAM)infoPtr->hFont, (LPARAM)TRUE);
1529 
1530     LISTVIEW_UpdateSize(infoPtr);
1531 
1532     return 0;
1533 }
1534 
1535 static inline void LISTVIEW_GetHeaderRect(const LISTVIEW_INFO *infoPtr, INT nSubItem, LPRECT lprc)
1536 {
1537     *lprc = LISTVIEW_GetColumnInfo(infoPtr, nSubItem)->rcHeader;
1538 }
1539         
1540 static inline BOOL LISTVIEW_GetItemW(const LISTVIEW_INFO *infoPtr, LPLVITEMW lpLVItem)
1541 {
1542     return LISTVIEW_GetItemT(infoPtr, lpLVItem, TRUE);
1543 }
1544 
1545 /* used to handle collapse main item column case */
1546 static inline BOOL LISTVIEW_DrawFocusRect(const LISTVIEW_INFO *infoPtr, HDC hdc)
1547 {
1548     return (infoPtr->rcFocus.left < infoPtr->rcFocus.right) ?
1549             DrawFocusRect(hdc, &infoPtr->rcFocus) : FALSE;
1550 }
1551 
1552 /* Listview invalidation functions: use _only_ these functions to invalidate */
1553 
1554 static inline BOOL is_redrawing(const LISTVIEW_INFO *infoPtr)
1555 {
1556     return infoPtr->bRedraw;
1557 }
1558 
1559 static inline void LISTVIEW_InvalidateRect(const LISTVIEW_INFO *infoPtr, const RECT* rect)
1560 {
1561     if(!is_redrawing(infoPtr)) return; 
1562     TRACE(" invalidating rect=%s\n", wine_dbgstr_rect(rect));
1563     InvalidateRect(infoPtr->hwndSelf, rect, TRUE);
1564 }
1565 
1566 static inline void LISTVIEW_InvalidateItem(const LISTVIEW_INFO *infoPtr, INT nItem)
1567 {
1568     RECT rcBox;
1569 
1570     if(!is_redrawing(infoPtr)) return; 
1571     LISTVIEW_GetItemBox(infoPtr, nItem, &rcBox);
1572     LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1573 }
1574 
1575 static inline void LISTVIEW_InvalidateSubItem(const LISTVIEW_INFO *infoPtr, INT nItem, INT nSubItem)
1576 {
1577     POINT Origin, Position;
1578     RECT rcBox;
1579     
1580     if(!is_redrawing(infoPtr)) return; 
1581     assert (infoPtr->uView == LV_VIEW_DETAILS);
1582     LISTVIEW_GetOrigin(infoPtr, &Origin);
1583     LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
1584     LISTVIEW_GetHeaderRect(infoPtr, nSubItem, &rcBox);
1585     rcBox.top = 0;
1586     rcBox.bottom = infoPtr->nItemHeight;
1587     OffsetRect(&rcBox, Origin.x + Position.x, Origin.y + Position.y);
1588     LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1589 }
1590 
1591 static inline void LISTVIEW_InvalidateList(const LISTVIEW_INFO *infoPtr)
1592 {
1593     LISTVIEW_InvalidateRect(infoPtr, NULL);
1594 }
1595 
1596 static inline void LISTVIEW_InvalidateColumn(const LISTVIEW_INFO *infoPtr, INT nColumn)
1597 {
1598     RECT rcCol;
1599     
1600     if(!is_redrawing(infoPtr)) return; 
1601     LISTVIEW_GetHeaderRect(infoPtr, nColumn, &rcCol);
1602     rcCol.top = infoPtr->rcList.top;
1603     rcCol.bottom = infoPtr->rcList.bottom;
1604     LISTVIEW_InvalidateRect(infoPtr, &rcCol);
1605 }
1606 
1607 /***
1608  * DESCRIPTION:
1609  * Retrieves the number of items that can fit vertically in the client area.
1610  *
1611  * PARAMETER(S):
1612  * [I] infoPtr : valid pointer to the listview structure
1613  *
1614  * RETURN:
1615  * Number of items per row.
1616  */
1617 static inline INT LISTVIEW_GetCountPerRow(const LISTVIEW_INFO *infoPtr)
1618 {
1619     INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
1620 
1621     return max(nListWidth/(infoPtr->nItemWidth ? infoPtr->nItemWidth : 1), 1);
1622 }
1623 
1624 /***
1625  * DESCRIPTION:
1626  * Retrieves the number of items that can fit horizontally in the client
1627  * area.
1628  *
1629  * PARAMETER(S):
1630  * [I] infoPtr : valid pointer to the listview structure
1631  *
1632  * RETURN:
1633  * Number of items per column.
1634  */
1635 static inline INT LISTVIEW_GetCountPerColumn(const LISTVIEW_INFO *infoPtr)
1636 {
1637     INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
1638 
1639     return max(nListHeight / infoPtr->nItemHeight, 1);
1640 }
1641 
1642 
1643 /*************************************************************************
1644  *              LISTVIEW_ProcessLetterKeys
1645  *
1646  *  Processes keyboard messages generated by pressing the letter keys
1647  *  on the keyboard.
1648  *  What this does is perform a case insensitive search from the
1649  *  current position with the following quirks:
1650  *  - If two chars or more are pressed in quick succession we search
1651  *    for the corresponding string (e.g. 'abc').
1652  *  - If there is a delay we wipe away the current search string and
1653  *    restart with just that char.
1654  *  - If the user keeps pressing the same character, whether slowly or
1655  *    fast, so that the search string is entirely composed of this
1656  *    character ('aaaaa' for instance), then we search for first item
1657  *    that starting with that character.
1658  *  - If the user types the above character in quick succession, then
1659  *    we must also search for the corresponding string ('aaaaa'), and
1660  *    go to that string if there is a match.
1661  *
1662  * PARAMETERS
1663  *   [I] hwnd : handle to the window
1664  *   [I] charCode : the character code, the actual character
1665  *   [I] keyData : key data
1666  *
1667  * RETURNS
1668  *
1669  *  Zero.
1670  *
1671  * BUGS
1672  *
1673  *  - The current implementation has a list of characters it will
1674  *    accept and it ignores everything else. In particular it will
1675  *    ignore accentuated characters which seems to match what
1676  *    Windows does. But I'm not sure it makes sense to follow
1677  *    Windows there.
1678  *  - We don't sound a beep when the search fails.
1679  *
1680  * SEE ALSO
1681  *
1682  *  TREEVIEW_ProcessLetterKeys
1683  */
1684 static INT LISTVIEW_ProcessLetterKeys(LISTVIEW_INFO *infoPtr, WPARAM charCode, LPARAM keyData)
1685 {
1686     INT nItem;
1687     INT endidx,idx;
1688     LVITEMW item;
1689     WCHAR buffer[MAX_PATH];
1690     DWORD lastKeyPressTimestamp = infoPtr->lastKeyPressTimestamp;
1691 
1692     /* simple parameter checking */
1693     if (!charCode || !keyData) return 0;
1694 
1695     /* only allow the valid WM_CHARs through */
1696     if (!isalnumW(charCode) &&
1697         charCode != '.' && charCode != '`' && charCode != '!' &&
1698         charCode != '@' && charCode != '#' && charCode != '$' &&
1699         charCode != '%' && charCode != '^' && charCode != '&' &&
1700         charCode != '*' && charCode != '(' && charCode != ')' &&
1701         charCode != '-' && charCode != '_' && charCode != '+' &&
1702         charCode != '=' && charCode != '\\'&& charCode != ']' &&
1703         charCode != '}' && charCode != '[' && charCode != '{' &&
1704         charCode != '/' && charCode != '?' && charCode != '>' &&
1705         charCode != '<' && charCode != ',' && charCode != '~')
1706         return 0;
1707 
1708     /* if there's one item or less, there is no where to go */
1709     if (infoPtr->nItemCount <= 1) return 0;
1710 
1711     /* update the search parameters */
1712     infoPtr->lastKeyPressTimestamp = GetTickCount();
1713     if (infoPtr->lastKeyPressTimestamp - lastKeyPressTimestamp < KEY_DELAY) {
1714         if (infoPtr->nSearchParamLength < MAX_PATH-1)
1715             infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
1716         if (infoPtr->charCode != charCode)
1717             infoPtr->charCode = charCode = 0;
1718     } else {
1719         infoPtr->charCode=charCode;
1720         infoPtr->szSearchParam[0]=charCode;
1721         infoPtr->nSearchParamLength=1;
1722         /* Redundant with the 1 char string */
1723         charCode=0;
1724     }
1725 
1726     /* and search from the current position */
1727     nItem=-1;
1728     if (infoPtr->nFocusedItem >= 0) {
1729         endidx=infoPtr->nFocusedItem;
1730         idx=endidx;
1731         /* if looking for single character match,
1732          * then we must always move forward
1733          */
1734         if (infoPtr->nSearchParamLength == 1)
1735             idx++;
1736     } else {
1737         endidx=infoPtr->nItemCount;
1738         idx=0;
1739     }
1740 
1741     /* Let application handle this for virtual listview */
1742     if (infoPtr->dwStyle & LVS_OWNERDATA)
1743     {
1744         NMLVFINDITEMW nmlv;
1745         LVFINDINFOW lvfi;
1746 
1747         ZeroMemory(&lvfi, sizeof(lvfi));
1748         lvfi.flags = (LVFI_WRAP | LVFI_PARTIAL);
1749         infoPtr->szSearchParam[infoPtr->nSearchParamLength] = '\0';
1750         lvfi.psz = infoPtr->szSearchParam;
1751         nmlv.iStart = idx;
1752         nmlv.lvfi = lvfi;
1753 
1754         nItem = notify_hdr(infoPtr, LVN_ODFINDITEMW, (LPNMHDR)&nmlv.hdr);
1755 
1756         if (nItem != -1)
1757             LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1758 
1759         return 0;
1760     }
1761 
1762     do {
1763         if (idx == infoPtr->nItemCount) {
1764             if (endidx == infoPtr->nItemCount || endidx == 0)
1765                 break;
1766             idx=0;
1767         }
1768 
1769         /* get item */
1770         item.mask = LVIF_TEXT;
1771         item.iItem = idx;
1772         item.iSubItem = 0;
1773         item.pszText = buffer;
1774         item.cchTextMax = MAX_PATH;
1775         if (!LISTVIEW_GetItemW(infoPtr, &item)) return 0;
1776 
1777         /* check for a match */
1778         if (lstrncmpiW(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
1779             nItem=idx;
1780             break;
1781         } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
1782                     (lstrncmpiW(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
1783             /* This would work but we must keep looking for a longer match */
1784             nItem=idx;
1785         }
1786         idx++;
1787     } while (idx != endidx);
1788 
1789     if (nItem != -1)
1790         LISTVIEW_KeySelection(infoPtr, nItem, FALSE);
1791 
1792     return 0;
1793 }
1794 
1795 /*************************************************************************
1796  * LISTVIEW_UpdateHeaderSize [Internal]
1797  *
1798  * Function to resize the header control
1799  *
1800  * PARAMS
1801  * [I]  hwnd : handle to a window
1802  * [I]  nNewScrollPos : scroll pos to set
1803  *
1804  * RETURNS
1805  * None.
1806  */
1807 static void LISTVIEW_UpdateHeaderSize(const LISTVIEW_INFO *infoPtr, INT nNewScrollPos)
1808 {
1809     RECT winRect;
1810     POINT point[2];
1811 
1812     TRACE("nNewScrollPos=%d\n", nNewScrollPos);
1813 
1814     if (!infoPtr->hwndHeader)  return;
1815 
1816     GetWindowRect(infoPtr->hwndHeader, &winRect);
1817     point[0].x = winRect.left;
1818     point[0].y = winRect.top;
1819     point[1].x = winRect.right;
1820     point[1].y = winRect.bottom;
1821 
1822     MapWindowPoints(HWND_DESKTOP, infoPtr->hwndSelf, point, 2);
1823     point[0].x = -nNewScrollPos;
1824     point[1].x += nNewScrollPos;
1825 
1826     SetWindowPos(infoPtr->hwndHeader,0,
1827         point[0].x,point[0].y,point[1].x,point[1].y,
1828         (infoPtr->dwStyle & LVS_NOCOLUMNHEADER) ? SWP_HIDEWINDOW : SWP_SHOWWINDOW |
1829         SWP_NOZORDER | SWP_NOACTIVATE);
1830 }
1831 
1832 /***
1833  * DESCRIPTION:
1834  * Update the scrollbars. This functions should be called whenever
1835  * the content, size or view changes.
1836  *
1837  * PARAMETER(S):
1838  * [I] infoPtr : valid pointer to the listview structure
1839  *
1840  * RETURN:
1841  * None
1842  */
1843 static void LISTVIEW_UpdateScroll(const LISTVIEW_INFO *infoPtr)
1844 {
1845     SCROLLINFO horzInfo, vertInfo;
1846     INT dx, dy;
1847 
1848     if ((infoPtr->dwStyle & LVS_NOSCROLL) || !is_redrawing(infoPtr)) return;
1849 
1850     ZeroMemory(&horzInfo, sizeof(SCROLLINFO));
1851     horzInfo.cbSize = sizeof(SCROLLINFO);
1852     horzInfo.nPage = infoPtr->rcList.right - infoPtr->rcList.left;
1853 
1854     /* for now, we'll set info.nMax to the _count_, and adjust it later */
1855     if (infoPtr->uView == LV_VIEW_LIST)
1856     {
1857         INT nPerCol = LISTVIEW_GetCountPerColumn(infoPtr);
1858         horzInfo.nMax = (infoPtr->nItemCount + nPerCol - 1) / nPerCol;
1859 
1860         /* scroll by at least one column per page */
1861         if(horzInfo.nPage < infoPtr->nItemWidth)
1862                 horzInfo.nPage = infoPtr->nItemWidth;
1863 
1864         if (infoPtr->nItemWidth)
1865             horzInfo.nPage /= infoPtr->nItemWidth;
1866     }
1867     else if (infoPtr->uView == LV_VIEW_DETAILS)
1868     {
1869         horzInfo.nMax = infoPtr->nItemWidth;
1870     }
1871     else /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1872     {
1873         RECT rcView;
1874 
1875         if (LISTVIEW_GetViewRect(infoPtr, &rcView)) horzInfo.nMax = rcView.right - rcView.left;
1876     }
1877   
1878     horzInfo.fMask = SIF_RANGE | SIF_PAGE;
1879     horzInfo.nMax = max(horzInfo.nMax - 1, 0);
1880     dx = GetScrollPos(infoPtr->hwndSelf, SB_HORZ);
1881     dx -= SetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo, TRUE);
1882     TRACE("horzInfo=%s\n", debugscrollinfo(&horzInfo));
1883 
1884     /* Setting the horizontal scroll can change the listview size
1885      * (and potentially everything else) so we need to recompute
1886      * everything again for the vertical scroll
1887      */
1888 
1889     ZeroMemory(&vertInfo, sizeof(SCROLLINFO));
1890     vertInfo.cbSize = sizeof(SCROLLINFO);
1891     vertInfo.nPage = infoPtr->rcList.bottom - infoPtr->rcList.top;
1892 
1893     if (infoPtr->uView == LV_VIEW_DETAILS)
1894     {
1895         vertInfo.nMax = infoPtr->nItemCount;
1896         
1897         /* scroll by at least one page */
1898         if(vertInfo.nPage < infoPtr->nItemHeight)
1899           vertInfo.nPage = infoPtr->nItemHeight;
1900 
1901         if (infoPtr->nItemHeight > 0)
1902             vertInfo.nPage /= infoPtr->nItemHeight;
1903     }
1904     else if (infoPtr->uView != LV_VIEW_LIST) /* LV_VIEW_ICON, or LV_VIEW_SMALLICON */
1905     {
1906         RECT rcView;
1907 
1908         if (LISTVIEW_GetViewRect(infoPtr, &rcView)) vertInfo.nMax = rcView.bottom - rcView.top;
1909     }
1910 
1911     vertInfo.fMask = SIF_RANGE | SIF_PAGE;
1912     vertInfo.nMax = max(vertInfo.nMax - 1, 0);
1913     dy = GetScrollPos(infoPtr->hwndSelf, SB_VERT);
1914     dy -= SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &vertInfo, TRUE);
1915     TRACE("vertInfo=%s\n", debugscrollinfo(&vertInfo));
1916 
1917     /* Change of the range may have changed the scroll pos. If so move the content */
1918     if (dx != 0 || dy != 0)
1919     {
1920         RECT listRect;
1921         listRect = infoPtr->rcList;
1922         ScrollWindowEx(infoPtr->hwndSelf, dx, dy, &listRect, &listRect, 0, 0,
1923             SW_ERASE | SW_INVALIDATE);
1924     }
1925 
1926     /* Update the Header Control */
1927     if (infoPtr->uView == LV_VIEW_DETAILS)
1928     {
1929         horzInfo.fMask = SIF_POS;
1930         GetScrollInfo(infoPtr->hwndSelf, SB_HORZ, &horzInfo);
1931         LISTVIEW_UpdateHeaderSize(infoPtr, horzInfo.nPos);
1932     }
1933 }
1934 
1935 
1936 /***
1937  * DESCRIPTION:
1938  * Shows/hides the focus rectangle. 
1939  *
1940  * PARAMETER(S):
1941  * [I] infoPtr : valid pointer to the listview structure
1942  * [I] fShow : TRUE to show the focus, FALSE to hide it.
1943  *
1944  * RETURN:
1945  * None
1946  */
1947 static void LISTVIEW_ShowFocusRect(const LISTVIEW_INFO *infoPtr, BOOL fShow)
1948 {
1949     HDC hdc;
1950 
1951     TRACE("fShow=%d, nItem=%d\n", fShow, infoPtr->nFocusedItem);
1952 
1953     if (infoPtr->nFocusedItem < 0) return;
1954 
1955     /* we need some gymnastics in ICON mode to handle large items */
1956     if (infoPtr->uView == LV_VIEW_ICON)
1957     {
1958         RECT rcBox;
1959 
1960         LISTVIEW_GetItemBox(infoPtr, infoPtr->nFocusedItem, &rcBox); 
1961         if ((rcBox.bottom - rcBox.top) > infoPtr->nItemHeight)
1962         {
1963             LISTVIEW_InvalidateRect(infoPtr, &rcBox);
1964             return;
1965         }
1966     }
1967 
1968     if (!(hdc = GetDC(infoPtr->hwndSelf))) return;
1969 
1970     /* for some reason, owner draw should work only in report mode */
1971     if ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && (infoPtr->uView == LV_VIEW_DETAILS))
1972     {
1973         DRAWITEMSTRUCT dis;
1974         LVITEMW item;
1975 
1976         HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
1977         HFONT hOldFont = SelectObject(hdc, hFont);
1978 
1979         item.iItem = infoPtr->nFocusedItem;
1980         item.iSubItem = 0;
1981         item.mask = LVIF_PARAM;
1982         if (!LISTVIEW_GetItemW(infoPtr, &item)) goto done;
1983            
1984         ZeroMemory(&dis, sizeof(dis)); 
1985         dis.CtlType = ODT_LISTVIEW;
1986         dis.CtlID = (UINT)GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1987         dis.itemID = item.iItem;
1988         dis.itemAction = ODA_FOCUS;
1989         if (fShow) dis.itemState |= ODS_FOCUS;
1990         dis.hwndItem = infoPtr->hwndSelf;
1991         dis.hDC = hdc;
1992         LISTVIEW_GetItemBox(infoPtr, dis.itemID, &dis.rcItem);
1993         dis.itemData = item.lParam;
1994 
1995         SendMessageW(infoPtr->hwndNotify, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
1996 
1997         SelectObject(hdc, hOldFont);
1998     }
1999     else
2000     {
2001         LISTVIEW_DrawFocusRect(infoPtr, hdc);
2002     }
2003 done:
2004     ReleaseDC(infoPtr->hwndSelf, hdc);
2005 }
2006 
2007 /***
2008  * Invalidates all visible selected items.
2009  */
2010 static void LISTVIEW_InvalidateSelectedItems(const LISTVIEW_INFO *infoPtr)
2011 {
2012     ITERATOR i; 
2013    
2014     iterator_frameditems(&i, infoPtr, &infoPtr->rcList); 
2015     while(iterator_next(&i))
2016     {
2017         if (LISTVIEW_GetItemState(infoPtr, i.nItem, LVIS_SELECTED))
2018             LISTVIEW_InvalidateItem(infoPtr, i.nItem);
2019     }
2020     iterator_destroy(&i);
2021 }
2022 
2023             
2024 /***
2025  * DESCRIPTION:            [INTERNAL]
2026  * Computes an item's (left,top) corner, relative to rcView.
2027  * That is, the position has NOT been made relative to the Origin.
2028  * This is deliberate, to avoid computing the Origin over, and
2029  * over again, when this function is called in a loop. Instead,
2030  * one can factor the computation of the Origin before the loop,
2031  * and offset the value returned by this function, on every iteration.
2032  * 
2033  * PARAMETER(S):
2034  * [I] infoPtr : valid pointer to the listview structure
2035  * [I] nItem  : item number
2036  * [O] lpptOrig : item top, left corner
2037  *
2038  * RETURN:
2039  *   None.
2040  */
2041 static void LISTVIEW_GetItemOrigin(const LISTVIEW_INFO *infoPtr, INT nItem, LPPOINT lpptPosition)
2042 {
2043     assert(nItem >= 0 && nItem < infoPtr->nItemCount);
2044 
2045     if ((infoPtr->uView == LV_VIEW_SMALLICON) || (infoPtr->uView == LV_VIEW_ICON))
2046     {
2047         lpptPosition->x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2048         lpptPosition->y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2049     }
2050     else if (infoPtr->uView == LV_VIEW_LIST)
2051     {
2052         INT nCountPerColumn = LISTVIEW_GetCountPerColumn(infoPtr);
2053         lpptPosition->x = nItem / nCountPerColumn * infoPtr->nItemWidth;
2054         lpptPosition->y = nItem % nCountPerColumn * infoPtr->nItemHeight;
2055     }
2056     else /* LV_VIEW_DETAILS */
2057     {
2058         lpptPosition->x = REPORT_MARGINX;
2059         /* item is always at zero indexed column */
2060         if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2061             lpptPosition->x += LISTVIEW_GetColumnInfo(infoPtr, 0)->rcHeader.left;
2062         lpptPosition->y = nItem * infoPtr->nItemHeight;
2063     }
2064 }
2065     
2066 /***
2067  * DESCRIPTION:            [INTERNAL]
2068  * Compute the rectangles of an item.  This is to localize all
2069  * the computations in one place. If you are not interested in some
2070  * of these values, simply pass in a NULL -- the function is smart
2071  * enough to compute only what's necessary. The function computes
2072  * the standard rectangles (BOUNDS, ICON, LABEL) plus a non-standard
2073  * one, the BOX rectangle. This rectangle is very cheap to compute,
2074  * and is guaranteed to contain all the other rectangles. Computing
2075  * the ICON rect is also cheap, but all the others are potentially
2076  * expensive. This gives an easy and effective optimization when
2077  * searching (like point inclusion, or rectangle intersection):
2078  * first test against the BOX, and if TRUE, test against the desired
2079  * rectangle.
2080  * If the function does not have all the necessary information
2081  * to computed the requested rectangles, will crash with a
2082  * failed assertion. This is done so we catch all programming
2083  * errors, given that the function is called only from our code.
2084  *
2085  * We have the following 'special' meanings for a few fields:
2086  *   * If LVIS_FOCUSED is set, we assume the item has the focus
2087  *     This is important in ICON mode, where it might get a larger
2088  *     then usual rectangle
2089  *
2090  * Please note that subitem support works only in REPORT mode.
2091  *
2092  * PARAMETER(S):
2093  * [I] infoPtr : valid pointer to the listview structure
2094  * [I] lpLVItem : item to compute the measures for
2095  * [O] lprcBox : ptr to Box rectangle
2096  *                Same as LVM_GETITEMRECT with LVIR_BOUNDS
2097  * [0] lprcSelectBox : ptr to select box rectangle
2098  *                Same as LVM_GETITEMRECT with LVIR_SELECTEDBOUNDS
2099  * [O] lprcIcon : ptr to Icon rectangle
2100  *                Same as LVM_GETITEMRECT with LVIR_ICON
2101  * [O] lprcStateIcon: ptr to State Icon rectangle
2102  * [O] lprcLabel : ptr to Label rectangle
2103  *                Same as LVM_GETITEMRECT with LVIR_LABEL
2104  *
2105  * RETURN:
2106  *   None.
2107  */
2108 static void LISTVIEW_GetItemMetrics(const LISTVIEW_INFO *infoPtr, const LVITEMW *lpLVItem,
2109                                     LPRECT lprcBox, LPRECT lprcSelectBox,
2110                                     LPRECT lprcIcon, LPRECT lprcStateIcon, LPRECT lprcLabel)
2111 {
2112     BOOL doSelectBox = FALSE, doIcon = FALSE, doLabel = FALSE, oversizedBox = FALSE;
2113     RECT Box, SelectBox, Icon, Label;
2114     COLUMN_INFO *lpColumnInfo = NULL;
2115     SIZE labelSize = { 0, 0 };
2116 
2117     TRACE("(lpLVItem=%s)\n", debuglvitem_t(lpLVItem, TRUE));
2118 
2119     /* Be smart and try to figure out the minimum we have to do */
2120     if (lpLVItem->iSubItem) assert(infoPtr->uView == LV_VIEW_DETAILS);
2121     if (infoPtr->uView == LV_VIEW_ICON && (lprcBox || lprcLabel))
2122     {
2123         assert((lpLVItem->mask & LVIF_STATE) && (lpLVItem->stateMask & LVIS_FOCUSED));
2124         if (lpLVItem->state & LVIS_FOCUSED) oversizedBox = doLabel = TRUE;
2125     }
2126     if (lprcSelectBox) doSelectBox = TRUE;
2127     if (lprcLabel) doLabel = TRUE;
2128     if (doLabel || lprcIcon || lprcStateIcon) doIcon = TRUE;
2129     if (doSelectBox)
2130     {
2131         doIcon = TRUE;
2132         doLabel = TRUE;
2133     }
2134 
2135     /************************************************************/
2136     /* compute the box rectangle (it should be cheap to do)     */
2137     /************************************************************/
2138     if (lpLVItem->iSubItem || infoPtr->uView == LV_VIEW_DETAILS)
2139         lpColumnInfo = LISTVIEW_GetColumnInfo(infoPtr, lpLVItem->iSubItem);
2140 
2141     if (lpLVItem->iSubItem)    
2142     {
2143         Box = lpColumnInfo->rcHeader;
2144     }
2145     else
2146     {
2147         Box.left = 0;
2148         Box.right = infoPtr->nItemWidth;
2149     }
2150     Box.top = 0;
2151     Box.bottom = infoPtr->nItemHeight;
2152 
2153     /******************************************************************/
2154     /* compute ICON bounding box (ala LVM_GETITEMRECT) and STATEICON  */
2155     /******************************************************************/
2156     if (doIcon)
2157     {
2158         LONG state_width = 0;
2159 
2160         if (infoPtr->himlState && lpLVItem->iSubItem == 0)
2161             state_width = infoPtr->iconStateSize.cx;
2162 
2163         if (infoPtr->uView == LV_VIEW_ICON)
2164         {
2165             Icon.left   = Box.left + state_width;
2166             if (infoPtr->himlNormal)
2167                 Icon.left += (infoPtr->nItemWidth - infoPtr->iconSize.cx - state_width) / 2;
2168             Icon.top    = Box.top + ICON_TOP_PADDING;
2169             Icon.right  = Icon.left;
2170             Icon.bottom = Icon.top;
2171             if (infoPtr->himlNormal)
2172             {
2173                 Icon.right  += infoPtr->iconSize.cx;
2174                 Icon.bottom += infoPtr->iconSize.cy;
2175             }
2176         }
2177         else /* LV_VIEW_SMALLICON, LV_VIEW_LIST or LV_VIEW_DETAILS */
2178         {
2179             Icon.left   = Box.left + state_width;
2180 
2181             if (infoPtr->uView == LV_VIEW_DETAILS && lpLVItem->iSubItem == 0)
2182             {
2183                 /* we need the indent in report mode */
2184                 assert(lpLVItem->mask & LVIF_INDENT);
2185                 Icon.left += infoPtr->iconSize.cx * lpLVItem->iIndent + REPORT_MARGINX;
2186             }
2187 
2188             Icon.top    = Box.top;
2189             Icon.right  = Icon.left;
2190             if (infoPtr->himlSmall &&
2191                 (!lpColumnInfo || lpLVItem->iSubItem == 0 || (lpColumnInfo->fmt & LVCFMT_IMAGE) ||
2192                  ((infoPtr->dwLvExStyle & LVS_EX_SUBITEMIMAGES) && lpLVItem->iImage != I_IMAGECALLBACK)))
2193                 Icon.right += infoPtr->iconSize.cx;
2194             Icon.bottom = Icon.top + infoPtr->iconSize.cy;
2195         }
2196         if(lprcIcon) *lprcIcon = Icon;
2197         TRACE("    - icon=%s\n", wine_dbgstr_rect(&Icon));
2198 
2199         /* TODO: is this correct? */
2200         if (lprcStateIcon)
2201         {
2202             lprcStateIcon->left = Icon.left - state_width;
2203             lprcStateIcon->right = Icon.left;
2204             lprcStateIcon->top = Icon.top;
2205             lprcStateIcon->bottom = lprcStateIcon->top + infoPtr->iconSize.cy;
2206             TRACE("    - state icon=%s\n", wine_dbgstr_rect(lprcStateIcon));
2207         }
2208      }
2209      else Icon.right = 0;
2210 
2211     /************************************************************/
2212     /* compute LABEL bounding box (ala LVM_GETITEMRECT)         */
2213     /************************************************************/
2214     if (doLabel)
2215     {
2216         /* calculate how far to the right can the label stretch */
2217         Label.right = Box.right;
2218         if (infoPtr->uView == LV_VIEW_DETAILS)
2219         {
2220             if (lpLVItem->iSubItem == 0)
2221             {
2222                 /* we need a zero based rect here */
2223                 Label = lpColumnInfo->rcHeader;
2224                 OffsetRect(&Label, -Label.left, 0);
2225             }
2226         }
2227 
2228         if (lpLVItem->iSubItem || ((infoPtr->dwStyle & LVS_OWNERDRAWFIXED) && infoPtr->uView == LV_VIEW_DETAILS))
2229         {
2230            labelSize.cx = infoPtr->nItemWidth;
2231            labelSize.cy = infoPtr->nItemHeight;
2232            goto calc_label;
2233         }
2234         
2235         /* we need the text in non owner draw mode */
2236         assert(lpLVItem->mask & LVIF_TEXT);
2237         if (is_textT(lpLVItem->pszText, TRUE))
2238         {
2239             HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2240             HDC hdc = GetDC(infoPtr->hwndSelf);
2241             HFONT hOldFont = SelectObject(hdc, hFont);
2242             UINT uFormat;
2243             RECT rcText;
2244 
2245             /* compute rough rectangle where the label will go */
2246             SetRectEmpty(&rcText);
2247             rcText.right = infoPtr->nItemWidth - TRAILING_LABEL_PADDING;
2248             rcText.bottom = infoPtr->nItemHeight;
2249             if (infoPtr->uView == LV_VIEW_ICON)
2250                 rcText.bottom -= ICON_TOP_PADDING + infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2251 
2252             /* now figure out the flags */
2253             if (infoPtr->uView == LV_VIEW_ICON)
2254                 uFormat = oversizedBox ? LV_FL_DT_FLAGS : LV_ML_DT_FLAGS;
2255             else
2256                 uFormat = LV_SL_DT_FLAGS;
2257             
2258             DrawTextW (hdc, lpLVItem->pszText, -1, &rcText, uFormat | DT_CALCRECT);
2259 
2260             if (rcText.right != rcText.left)
2261                 labelSize.cx = min(rcText.right - rcText.left + TRAILING_LABEL_PADDING, infoPtr->nItemWidth);
2262 
2263             labelSize.cy = rcText.bottom - rcText.top;
2264 
2265             SelectObject(hdc, hOldFont);
2266             ReleaseDC(infoPtr->hwndSelf, hdc);
2267         }
2268 
2269 calc_label:
2270         if (infoPtr->uView == LV_VIEW_ICON)
2271         {
2272             Label.left = Box.left + (infoPtr->nItemWidth - labelSize.cx) / 2;
2273             Label.top  = Box.top + ICON_TOP_PADDING_HITABLE +
2274                          infoPtr->iconSize.cy + ICON_BOTTOM_PADDING;
2275             Label.right = Label.left + labelSize.cx;
2276             Label.bottom = Label.top + infoPtr->nItemHeight;
2277             if (!oversizedBox && labelSize.cy > infoPtr->ntmHeight)
2278             {
2279                 labelSize.cy = min(Box.bottom - Label.top, labelSize.cy);
2280                 labelSize.cy /= infoPtr->ntmHeight;
2281                 labelSize.cy = max(labelSize.cy, 1);
2282                 labelSize.cy *= infoPtr->ntmHeight;
2283              }
2284              Label.bottom = Label.top + labelSize.cy + HEIGHT_PADDING;
2285         }
2286         else if (infoPtr->uView == LV_VIEW_DETAILS)
2287         {
2288             Label.left = Icon.right;
2289             Label.top = Box.top;
2290             Label.right = lpLVItem->iSubItem ? lpColumnInfo->rcHeader.right :
2291                           lpColumnInfo->rcHeader.right - lpColumnInfo->rcHeader.left;
2292             Label.bottom = Label.top + infoPtr->nItemHeight;
2293         }
2294         else /* LV_VIEW_SMALLICON or LV_VIEW_LIST */
2295         {
2296             Label.left = Icon.right;
2297             Label.top = Box.top;
2298             Label.right = min(Label.left + labelSize.cx, Label.right);
2299             Label.bottom = Label.top + infoPtr->nItemHeight;
2300         }
2301   
2302         if (lprcLabel) *lprcLabel = Label;
2303         TRACE("    - label=%s\n", wine_dbgstr_rect(&Label));
2304     }
2305 
2306     /************************************************************/
2307     /* compute SELECT bounding box                              */
2308     /************************************************************/
2309     if (doSelectBox)
2310     {
2311         if (infoPtr->uView == LV_VIEW_DETAILS)
2312         {
2313             SelectBox.left = Icon.left;
2314             SelectBox.top = Box.top;
2315             SelectBox.bottom = Box.bottom;
2316 
2317             if (labelSize.cx)
2318                 SelectBox.right = min(Label.left + labelSize.cx, Label.right);
2319             else
2320                 SelectBox.right = min(Label.left + MAX_EMPTYTEXT_SELECT_WIDTH, Label.right);
2321         }
2322         else
2323         {
2324             UnionRect(&SelectBox, &Icon, &Label);
2325         }
2326         if (lprcSelectBox) *lprcSelectBox = SelectBox;
2327         TRACE("    - select box=%s\n", wine_dbgstr_rect(&SelectBox));
2328     }
2329 
2330     /* Fix the Box if necessary */
2331     if (lprcBox)
2332     {
2333         if (oversizedBox) UnionRect(lprcBox, &Box, &Label);
2334         else *lprcBox = Box;
2335     }
2336     TRACE("    - box=%s\n", wine_dbgstr_rect(&Box));
2337 }
2338 
2339 /***
2340  * DESCRIPTION:            [INTERNAL]
2341  *
2342  * PARAMETER(S):
2343  * [I] infoPtr : valid pointer to the listview structure
2344  * [I] nItem : item number
2345  * [O] lprcBox : ptr to Box rectangle
2346  *
2347  * RETURN:
2348  *   None.
2349  */
2350 static void LISTVIEW_GetItemBox(const LISTVIEW_INFO *infoPtr, INT nItem, LPRECT lprcBox)
2351 {
2352     WCHAR szDispText[DISP_TEXT_SIZE] = { '\0' };
2353     POINT Position, Origin;
2354     LVITEMW lvItem;
2355 
2356     LISTVIEW_GetOrigin(infoPtr, &Origin);
2357     LISTVIEW_GetItemOrigin(infoPtr, nItem, &Position);
2358 
2359     /* Be smart and try to figure out the minimum we have to do */
2360     lvItem.mask = 0;
2361     if (infoPtr->uView == LV_VIEW_ICON && infoPtr->bFocus && LISTVIEW_GetItemState(infoPtr, nItem, LVIS_FOCUSED))
2362         lvItem.mask |= LVIF_TEXT;
2363     lvItem.iItem = nItem;
2364     lvItem.iSubItem = 0;
2365     lvItem.pszText = szDispText;
2366     lvItem.cchTextMax = DISP_TEXT_SIZE;
2367     if (lvItem.mask) LISTVIEW_GetItemW(infoPtr, &lvItem);
2368     if (infoPtr->uView == LV_VIEW_ICON)
2369     {
2370         lvItem.mask |= LVIF_STATE;
2371         lvItem.stateMask = LVIS_FOCUSED;
2372         lvItem.state = (lvItem.mask & LVIF_TEXT ? LVIS_FOCUSED : 0);
2373     }
2374     LISTVIEW_GetItemMetrics(infoPtr, &lvItem, lprcBox, 0, 0, 0, 0);
2375 
2376     if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_FULLROWSELECT &&
2377         SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX, 0, 0))
2378     {
2379         OffsetRect(lprcBox, Origin.x, Position.y + Origin.y);
2380     }
2381     else
2382         OffsetRect(lprcBox, Position.x + Origin.x, Position.y + Origin.y);
2383 }
2384 
2385 /* LISTVIEW_MapIdToIndex helper */
2386 static INT CALLBACK MapIdSearchCompare(LPVOID p1, LPVOID p2, LPARAM lParam)
2387 {
2388     ITEM_ID *id1 = (ITEM_ID*)p1;
2389     ITEM_ID *id2 = (ITEM_ID*)p2;
2390 
2391     if (id1->id == id2->id) return 0;
2392 
2393     return (id1->id < id2->id) ? -1 : 1;
2394 }
2395 
2396 /***
2397  * DESCRIPTION:
2398  * Returns the item index for id specified.
2399  *
2400  * PARAMETER(S):
2401  * [I] infoPtr : valid pointer to the listview structure
2402  * [I] iID : item id to get index for
2403  *
2404  * RETURN:
2405  * Item index, or -1 on failure.
2406  */
2407 static INT LISTVIEW_MapIdToIndex(const LISTVIEW_INFO *infoPtr, UINT iID)
2408 {
2409     ITEM_ID ID;
2410     INT index;
2411 
2412     TRACE("iID=%d\n", iID);
2413 
2414     if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2415     if (infoPtr->nItemCount == 0) return -1;
2416 
2417     ID.id = iID;
2418     index = DPA_Search(infoPtr->hdpaItemIds, &ID, -1, &MapIdSearchCompare, 0, DPAS_SORTED);
2419 
2420     if (index != -1)
2421     {
2422         ITEM_ID *lpID = DPA_GetPtr(infoPtr->hdpaItemIds, index);
2423         return DPA_GetPtrIndex(infoPtr->hdpaItems, lpID->item);
2424     }
2425 
2426     return -1;
2427 }
2428 
2429 /***
2430  * DESCRIPTION:
2431  * Returns the item id for index given.
2432  *
2433  * PARAMETER(S):
2434  * [I] infoPtr : valid pointer to the listview structure
2435  * [I] iItem : item index to get id for
2436  *
2437  * RETURN:
2438  * Item id.
2439  */
2440 static DWORD LISTVIEW_MapIndexToId(const LISTVIEW_INFO *infoPtr, INT iItem)
2441 {
2442     ITEM_INFO *lpItem;
2443     HDPA hdpaSubItems;
2444 
2445     TRACE("iItem=%d\n", iItem);
2446 
2447     if (infoPtr->dwStyle & LVS_OWNERDATA) return -1;
2448     if (iItem < 0 || iItem >= infoPtr->nItemCount) return -1;
2449 
2450     hdpaSubItems = DPA_GetPtr(infoPtr->hdpaItems, iItem);
2451     lpItem = DPA_GetPtr(hdpaSubItems, 0);
2452 
2453     return lpItem->id->id;
2454 }
2455 
2456 /***
2457  * DESCRIPTION:
2458  * Returns the current icon position, and advances it along the top.
2459  * The returned position is not offset by Origin.
2460  *
2461  * PARAMETER(S):
2462  * [I] infoPtr : valid pointer to the listview structure
2463  * [O] lpPos : will get the current icon position
2464  *
2465  * RETURN:
2466  * None
2467  */
2468 static void LISTVIEW_NextIconPosTop(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2469 {
2470     INT nListWidth = infoPtr->rcList.right - infoPtr->rcList.left;
2471     
2472     *lpPos = infoPtr->currIconPos;
2473     
2474     infoPtr->currIconPos.x += infoPtr->nItemWidth;
2475     if (infoPtr->currIconPos.x + infoPtr->nItemWidth <= nListWidth) return;
2476 
2477     infoPtr->currIconPos.x  = 0;
2478     infoPtr->currIconPos.y += infoPtr->nItemHeight;
2479 }
2480 
2481     
2482 /***
2483  * DESCRIPTION:
2484  * Returns the current icon position, and advances it down the left edge.
2485  * The returned position is not offset by Origin.
2486  *
2487  * PARAMETER(S):
2488  * [I] infoPtr : valid pointer to the listview structure
2489  * [O] lpPos : will get the current icon position
2490  *
2491  * RETURN:
2492  * None
2493  */
2494 static void LISTVIEW_NextIconPosLeft(LISTVIEW_INFO *infoPtr, LPPOINT lpPos)
2495 {
2496     INT nListHeight = infoPtr->rcList.bottom - infoPtr->rcList.top;
2497     
2498     *lpPos = infoPtr->currIconPos;
2499     
2500     infoPtr->currIconPos.y += infoPtr->nItemHeight;
2501     if (infoPtr->currIconPos.y + infoPtr->nItemHeight <= nListHeight) return;
2502 
2503     infoPtr->currIconPos.x += infoPtr->nItemWidth;
2504     infoPtr->currIconPos.y  = 0;
2505 }
2506 
2507     
2508 /***
2509  * DESCRIPTION:
2510  * Moves an icon to the specified position.
2511  * It takes care of invalidating the item, etc.
2512  *
2513  * PARAMETER(S):
2514  * [I] infoPtr : valid pointer to the listview structure
2515  * [I] nItem : the item to move
2516  * [I] lpPos : the new icon position
2517  * [I] isNew : flags the item as being new
2518  *
2519  * RETURN:
2520  *   Success: TRUE
2521  *   Failure: FALSE
2522  */
2523 static BOOL LISTVIEW_MoveIconTo(const LISTVIEW_INFO *infoPtr, INT nItem, const POINT *lppt, BOOL isNew)
2524 {
2525     POINT old;
2526     
2527     if (!isNew)
2528     { 
2529         old.x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, nItem);
2530         old.y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, nItem);
2531     
2532         if (lppt->x == old.x && lppt->y == old.y) return TRUE;
2533         LISTVIEW_InvalidateItem(infoPtr, nItem);
2534     }
2535 
2536     /* Allocating a POINTER for every item is too resource intensive,
2537      * so we'll keep the (x,y) in different arrays */
2538     if (!DPA_SetPtr(infoPtr->hdpaPosX, nItem, (void *)(LONG_PTR)lppt->x)) return FALSE;
2539     if (!DPA_SetPtr(infoPtr->hdpaPosY, nItem, (void *)(LONG_PTR)lppt->y)) return FALSE;
2540 
2541     LISTVIEW_InvalidateItem(infoPtr, nItem);
2542 
2543     return TRUE;
2544 }
2545 
2546 /***
2547  * DESCRIPTION:
2548  * Arranges listview items in icon display mode.
2549  *
2550  * PARAMETER(S):
2551  * [I] infoPtr : valid pointer to the listview structure
2552  * [I] nAlignCode : alignment code
2553  *
2554  * RETURN:
2555  *   SUCCESS : TRUE
2556  *   FAILURE : FALSE
2557  */
2558 static BOOL LISTVIEW_Arrange(LISTVIEW_INFO *infoPtr, INT nAlignCode)
2559 {
2560     void (*next_pos)(LISTVIEW_INFO *, LPPOINT);
2561     POINT pos;
2562     INT i;
2563 
2564     if (infoPtr->uView != LV_VIEW_ICON && infoPtr->uView != LV_VIEW_SMALLICON) return FALSE;
2565   
2566     TRACE("nAlignCode=%d\n", nAlignCode);
2567 
2568     if (nAlignCode == LVA_DEFAULT)
2569     {
2570         if (infoPtr->dwStyle & LVS_ALIGNLEFT) nAlignCode = LVA_ALIGNLEFT;
2571         else nAlignCode = LVA_ALIGNTOP;
2572     }
2573    
2574     switch (nAlignCode)
2575     {
2576     case LVA_ALIGNLEFT:  next_pos = LISTVIEW_NextIconPosLeft; break;
2577     case LVA_ALIGNTOP:   next_pos = LISTVIEW_NextIconPosTop;  break;
2578     case LVA_SNAPTOGRID: next_pos = LISTVIEW_NextIconPosTop;  break; /* FIXME */
2579     default: return FALSE;
2580     }
2581     
2582     infoPtr->bAutoarrange = TRUE;
2583     infoPtr->currIconPos.x = infoPtr->currIconPos.y = 0;
2584     for (i = 0; i < infoPtr->nItemCount; i++)
2585     {
2586         next_pos(infoPtr, &pos);
2587         LISTVIEW_MoveIconTo(infoPtr, i, &pos, FALSE);
2588     }
2589 
2590     return TRUE;
2591 }
2592   
2593 /***
2594  * DESCRIPTION:
2595  * Retrieves the bounding rectangle of all the items, not offset by Origin.
2596  * For LVS_REPORT always returns empty rectangle.
2597  *
2598  * PARAMETER(S):
2599  * [I] infoPtr : valid pointer to the listview structure
2600  * [O] lprcView : bounding rectangle
2601  *
2602  * RETURN:
2603  *   SUCCESS : TRUE
2604  *   FAILURE : FALSE
2605  */
2606 static void LISTVIEW_GetAreaRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2607 {
2608     INT i, x, y;
2609 
2610     SetRectEmpty(lprcView);
2611 
2612     switch (infoPtr->uView)
2613     {
2614     case LV_VIEW_ICON:
2615     case LV_VIEW_SMALLICON:
2616         for (i = 0; i < infoPtr->nItemCount; i++)
2617         {
2618             x = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosX, i);
2619             y = (LONG_PTR)DPA_GetPtr(infoPtr->hdpaPosY, i);
2620             lprcView->right = max(lprcView->right, x);
2621             lprcView->bottom = max(lprcView->bottom, y);
2622         }
2623         if (infoPtr->nItemCount > 0)
2624         {
2625             lprcView->right += infoPtr->nItemWidth;
2626             lprcView->bottom += infoPtr->nItemHeight;
2627         }
2628         break;
2629 
2630     case LV_VIEW_LIST:
2631         y = LISTVIEW_GetCountPerColumn(infoPtr);
2632         x = infoPtr->nItemCount / y;
2633         if (infoPtr->nItemCount % y) x++;
2634         lprcView->right = x * infoPtr->nItemWidth;
2635         lprcView->bottom = y * infoPtr->nItemHeight;
2636         break;
2637     }
2638 }
2639 
2640 /***
2641  * DESCRIPTION:
2642  * Retrieves the bounding rectangle of all the items.
2643  *
2644  * PARAMETER(S):
2645  * [I] infoPtr : valid pointer to the listview structure
2646  * [O] lprcView : bounding rectangle
2647  *
2648  * RETURN:
2649  *   SUCCESS : TRUE
2650  *   FAILURE : FALSE
2651  */
2652 static BOOL LISTVIEW_GetViewRect(const LISTVIEW_INFO *infoPtr, LPRECT lprcView)
2653 {
2654     POINT ptOrigin;
2655 
2656     TRACE("(lprcView=%p)\n", lprcView);
2657 
2658     if (!lprcView) return FALSE;
2659 
2660     LISTVIEW_GetAreaRect(infoPtr, lprcView);
2661 
2662     if (infoPtr->uView != LV_VIEW_DETAILS)
2663     {
2664         LISTVIEW_GetOrigin(infoPtr, &ptOrigin);
2665         OffsetRect(lprcView, ptOrigin.x, ptOrigin.y);
2666     }
2667 
2668     TRACE("lprcView=%s\n", wine_dbgstr_rect(lprcView));
2669 
2670     return TRUE;
2671 }
2672 
2673 /***
2674  * DESCRIPTION:
2675  * Retrieves the subitem pointer associated with the subitem index.
2676  *
2677  * PARAMETER(S):
2678  * [I] hdpaSubItems : DPA handle for a specific item
2679  * [I] nSubItem : index of subitem
2680  *
2681  * RETURN:
2682  *   SUCCESS : subitem pointer
2683  *   FAILURE : NULL
2684  */
2685 static SUBITEM_INFO* LISTVIEW_GetSubItemPtr(HDPA hdpaSubItems, INT nSubItem)
2686 {
2687     SUBITEM_INFO *lpSubItem;
2688     INT i;
2689 
2690     /* we should binary search here if need be */
2691     for (i = 1; i < DPA_GetPtrCount(hdpaSubItems); i++)
2692     {
2693         lpSubItem = DPA_GetPtr(hdpaSubItems, i);
2694         if (lpSubItem->iSubItem == nSubItem)
2695             return lpSubItem;
2696     }
2697 
2698     return NULL;
2699 }
2700 
2701 
2702 /***
2703  * DESCRIPTION:
2704  * Calculates the desired item width.
2705  *
2706  * PARAMETER(S):
2707  * [I] infoPtr : valid pointer to the listview structure
2708  *
2709  * RETURN:
2710  *  The desired item width.
2711  */
2712 static INT LISTVIEW_CalculateItemWidth(const LISTVIEW_INFO *infoPtr)
2713 {
2714     INT nItemWidth = 0;
2715 
2716     TRACE("uView=%d\n", infoPtr->uView);
2717 
2718     if (infoPtr->uView == LV_VIEW_ICON)
2719         nItemWidth = infoPtr->iconSpacing.cx;
2720     else if (infoPtr->uView == LV_VIEW_DETAILS)
2721     {
2722         if (DPA_GetPtrCount(infoPtr->hdpaColumns) > 0)
2723         {
2724             RECT rcHeader;
2725             INT index;
2726 
2727             index = SendMessageW(infoPtr->hwndHeader, HDM_ORDERTOINDEX,
2728                                  DPA_GetPtrCount(infoPtr->hdpaColumns) - 1, 0);
2729 
2730             LISTVIEW_GetHeaderRect(infoPtr, index, &rcHeader);
2731             nItemWidth = rcHeader.right;
2732         }
2733     }
2734     else /* LV_VIEW_SMALLICON, or LV_VIEW_LIST */
2735     {
2736         INT i;
2737         
2738         for (i = 0; i < infoPtr->nItemCount; i++)
2739             nItemWidth = max(LISTVIEW_GetLabelWidth(infoPtr, i), nItemWidth);
2740 
2741         if (infoPtr->himlSmall) nItemWidth += infoPtr->iconSize.cx; 
2742         if (infoPtr->himlState) nItemWidth += infoPtr->iconStateSize.cx;
2743 
2744         nItemWidth = max(DEFAULT_COLUMN_WIDTH, nItemWidth + WIDTH_PADDING);
2745     }
2746 
2747     return nItemWidth;
2748 }
2749 
2750 /***
2751  * DESCRIPTION:
2752  * Calculates the desired item height.
2753  *
2754  * PARAMETER(S):
2755  * [I] infoPtr : valid pointer to the listview structure
2756  *
2757  * RETURN:
2758  *  The desired item height.
2759  */
2760 static INT LISTVIEW_CalculateItemHeight(const LISTVIEW_INFO *infoPtr)
2761 {
2762     INT nItemHeight;
2763 
2764     TRACE("uView=%d\n", infoPtr->uView);
2765 
2766     if (infoPtr->uView == LV_VIEW_ICON)
2767         nItemHeight = infoPtr->iconSpacing.cy;
2768     else
2769     {
2770         nItemHeight = infoPtr->ntmHeight; 
2771         if (infoPtr->uView == LV_VIEW_DETAILS && infoPtr->dwLvExStyle & LVS_EX_GRIDLINES)
2772             nItemHeight++;
2773         if (infoPtr->himlState)
2774             nItemHeight = max(nItemHeight, infoPtr->iconStateSize.cy);
2775         if (infoPtr->himlSmall)
2776             nItemHeight = max(nItemHeight, infoPtr->iconSize.cy);
2777         if (infoPtr->himlState || infoPtr->himlSmall)
2778             nItemHeight += HEIGHT_PADDING;
2779     if (infoPtr->nMeasureItemHeight > 0)
2780         nItemHeight = infoPtr->nMeasureItemHeight;
2781     }
2782 
2783     return max(nItemHeight, 1);
2784 }
2785 
2786 /***
2787  * DESCRIPTION:
2788  * Updates the width, and height of an item.
2789  *
2790  * PARAMETER(S):
2791  * [I] infoPtr : valid pointer to the listview structure
2792  *
2793  * RETURN:
2794  *  None.
2795  */
2796 static inline void LISTVIEW_UpdateItemSize(LISTVIEW_INFO *infoPtr)
2797 {
2798     infoPtr->nItemWidth = LISTVIEW_CalculateItemWidth(infoPtr);
2799     infoPtr->nItemHeight = LISTVIEW_CalculateItemHeight(infoPtr);
2800 }
2801 
2802 
2803 /***
2804  * DESCRIPTION:
2805  * Retrieves and saves important text metrics info for the current
2806  * Listview font.
2807  *
2808  * PARAMETER(S):
2809  * [I] infoPtr : valid pointer to the listview structure
2810  *
2811  */
2812 static void LISTVIEW_SaveTextMetrics(LISTVIEW_INFO *infoPtr)
2813 {
2814     HDC hdc = GetDC(infoPtr->hwndSelf);
2815     HFONT hFont = infoPtr->hFont ? infoPtr->hFont : infoPtr->hDefaultFont;
2816     HFONT hOldFont = SelectObject(hdc, hFont);
2817     TEXTMETRICW tm;
2818     SIZE sz;
2819 
2820     if (GetTextMetricsW(hdc, &tm))
2821     {
2822         infoPtr->ntmHeight = tm.tmHeight;
2823         infoPtr->ntmMaxCharWidth = tm.tmMaxCharWidth;
2824     }
2825 
2826     if (GetTextExtentPoint32A(hdc, "...", 3, &sz))
2827         infoPtr->nEllipsisWidth = sz.cx;
2828         
2829     SelectObject(hdc, hOldFont);
2830     ReleaseDC(infoPtr->hwndSelf, hdc);
2831     
2832     TRACE("tmHeight=%d\n", infoPtr->ntmHeight);
2833 }
2834 
2835 /***
2836  * DESCRIPTION:
2837  * A compare function for ranges
2838  *
2839  * PARAMETER(S)
2840  * [I] range1 : pointer to range 1;
2841  * [I] range2 : pointer to range 2;
2842  * [I] flags : flags
2843  *
2844  * RETURNS:
2845  * > 0 : if range 1 > range 2
2846  * < 0 : if range 2 > range 1
2847  * = 0 : if range intersects range 2
2848  */
2849 static INT CALLBACK ranges_cmp(LPVOID range1, LPVOID range2, LPARAM flags)
2850 {
2851     INT cmp;
2852     
2853     if (((RANGE*)range1)->upper <= ((RANGE*)range2)->lower) 
2854         cmp = -1;
2855     else if (((RANGE*)range2)->upper <= ((RANGE*)range1)->lower) 
2856         cmp = 1;
2857     else 
2858         cmp = 0;
2859 
2860     TRACE("range1=%s, range2=%s, cmp=%d\n", debugrange(range1), debugrange(range2), cmp);
2861 
2862     return cmp;
2863 }
2864 
2865 #if DEBUG_RANGES
2866 #define ranges_check(ranges, desc) ranges_assert(ranges, desc, __FUNCTION__, __LINE__)
2867 #else
2868 #define ranges_check(ranges, desc) do { } while(0)
2869 #endif
2870 
2871 static void ranges_assert(RANGES ranges, LPCSTR desc, const char *func, int line)
2872 {
2873     INT i;
2874     RANGE *prev, *curr;
2875     
2876     TRACE("*** Checking %s:%d:%s ***\n", func, line, desc);
2877     assert (ranges);
2878     assert (DPA_GetPtrCount(ranges->hdpa) >= 0);
2879     ranges_dump(ranges);
2880     if (DPA_GetPtrCount(ranges->hdpa) > 0)
2881     {
2882         prev = DPA_GetPtr(ranges->hdpa, 0);
2883         assert (prev->lower >= 0 && prev->lower < prev->upper);
2884         for (i = 1; i < DPA_GetPtrCount(ranges->hdpa); i++)
2885         {
2886             curr = DPA_GetPtr(ranges->hdpa, i);
2887             assert (prev->upper <= curr->lower);
2888             assert (curr->lower < curr->upper);
2889             prev = curr;
2890         }
2891     }
2892     TRACE("--- Done checking---\n");
2893 }
2894 
2895 static RANGES ranges_create(int count)
2896 {
2897     RANGES ranges = Alloc(sizeof(struct tagRANGES));
2898     if (!ranges) return NULL;
2899     ranges->hdpa = DPA_Create(count);
2900     if (ranges->hdpa) return ranges;
2901     Free(ranges);
2902     return NULL;
2903 }
2904 
2905 static void ranges_clear(RANGES ranges)
2906 {
2907     INT i;
2908         
2909     for(i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2910         Free(DPA_GetPtr(ranges->hdpa, i));
2911     DPA_DeleteAllPtrs(ranges->hdpa);
2912 }
2913 
2914 
2915 static void ranges_destroy(RANGES ranges)
2916 {
2917     if (!ranges) return;
2918     ranges_clear(ranges);
2919     DPA_Destroy(ranges->hdpa);
2920     Free(ranges);
2921 }
2922 
2923 static RANGES ranges_clone(RANGES ranges)
2924 {
2925     RANGES clone;
2926     INT i;
2927            
2928     if (!(clone = ranges_create(DPA_GetPtrCount(ranges->hdpa)))) goto fail;
2929 
2930     for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2931     {
2932         RANGE *newrng = Alloc(sizeof(RANGE));
2933         if (!newrng) goto fail;
2934         *newrng = *((RANGE*)DPA_GetPtr(ranges->hdpa, i));
2935         DPA_SetPtr(clone->hdpa, i, newrng);
2936     }
2937     return clone;
2938     
2939 fail:
2940     TRACE ("clone failed\n");
2941     ranges_destroy(clone);
2942     return NULL;
2943 }
2944 
2945 static RANGES ranges_diff(RANGES ranges, RANGES sub)
2946 {
2947     INT i;
2948 
2949     for (i = 0; i < DPA_GetPtrCount(sub->hdpa); i++)
2950         ranges_del(ranges, *((RANGE *)DPA_GetPtr(sub->hdpa, i)));
2951 
2952     return ranges;
2953 }
2954 
2955 static void ranges_dump(RANGES ranges)
2956 {
2957     INT i;
2958 
2959     for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2960         TRACE("   %s\n", debugrange(DPA_GetPtr(ranges->hdpa, i)));
2961 }
2962 
2963 static inline BOOL ranges_contain(RANGES ranges, INT nItem)
2964 {
2965     RANGE srchrng = { nItem, nItem + 1 };
2966 
2967     TRACE("(nItem=%d)\n", nItem);
2968     ranges_check(ranges, "before contain");
2969     return DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED) != -1;
2970 }
2971 
2972 static INT ranges_itemcount(RANGES ranges)
2973 {
2974     INT i, count = 0;
2975     
2976     for (i = 0; i < DPA_GetPtrCount(ranges->hdpa); i++)
2977     {
2978         RANGE *sel = DPA_GetPtr(ranges->hdpa, i);
2979         count += sel->upper - sel->lower;
2980     }
2981 
2982     return count;
2983 }
2984 
2985 static BOOL ranges_shift(RANGES ranges, INT nItem, INT delta, INT nUpper)
2986 {
2987     RANGE srchrng = { nItem, nItem + 1 }, *chkrng;
2988     INT index;
2989 
2990     index = DPA_Search(ranges->hdpa, &srchrng, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
2991     if (index == -1) return TRUE;
2992 
2993     for (; index < DPA_GetPtrCount(ranges->hdpa); index++)
2994     {
2995         chkrng = DPA_GetPtr(ranges->hdpa, index);
2996         if (chkrng->lower >= nItem)
2997             chkrng->lower = max(min(chkrng->lower + delta, nUpper - 1), 0);
2998         if (chkrng->upper > nItem)
2999             chkrng->upper = max(min(chkrng->upper + delta, nUpper), 0);
3000     }
3001     return TRUE;
3002 }
3003 
3004 static BOOL ranges_add(RANGES ranges, RANGE range)
3005 {
3006     RANGE srchrgn;
3007     INT index;
3008 
3009     TRACE("(%s)\n", debugrange(&range));
3010     ranges_check(ranges, "before add");
3011 
3012     /* try find overlapping regions first */
3013     srchrgn.lower = range.lower - 1;
3014     srchrgn.upper = range.upper + 1;
3015     index = DPA_Search(ranges->hdpa, &srchrgn, 0, ranges_cmp, 0, DPAS_SORTED);
3016    
3017     if (index == -1)
3018     {
3019         RANGE *newrgn;
3020 
3021         TRACE("Adding new range\n");
3022 
3023         /* create the brand new range to insert */      
3024         newrgn = Alloc(sizeof(RANGE));
3025         if(!newrgn) goto fail;
3026         *newrgn = range;
3027         
3028         /* figure out where to insert it */
3029         index = DPA_Search(ranges->hdpa, newrgn, 0, ranges_cmp, 0, DPAS_SORTED | DPAS_INSERTAFTER);
3030         TRACE("index=%d\n", index);
3031         if (index == -1) index = 0;
3032         
3033         /* and get it over with */
3034         if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3035         {
3036             Free(newrgn);
3037             goto fail;
3038         }
3039     }
3040     else
3041     {
3042         RANGE *chkrgn, *mrgrgn;
3043         INT fromindex, mergeindex;
3044 
3045         chkrgn = DPA_GetPtr(ranges->hdpa, index);
3046         TRACE("Merge with %s @%d\n", debugrange(chkrgn), index);
3047 
3048         chkrgn->lower = min(range.lower, chkrgn->lower);
3049         chkrgn->upper = max(range.upper, chkrgn->upper);
3050         
3051         TRACE("New range %s @%d\n", debugrange(chkrgn), index);
3052 
3053         /* merge now common ranges */
3054         fromindex = 0;
3055         srchrgn.lower = chkrgn->lower - 1;
3056         srchrgn.upper = chkrgn->upper + 1;
3057             
3058         do
3059         {
3060             mergeindex = DPA_Search(ranges->hdpa, &srchrgn, fromindex, ranges_cmp, 0, 0);
3061             if (mergeindex == -1) break;
3062             if (mergeindex == index) 
3063             {
3064                 fromindex = index + 1;
3065                 continue;
3066             }
3067           
3068             TRACE("Merge with index %i\n", mergeindex);
3069             
3070             mrgrgn = DPA_GetPtr(ranges->hdpa, mergeindex);
3071             chkrgn->lower = min(chkrgn->lower, mrgrgn->lower);
3072             chkrgn->upper = max(chkrgn->upper, mrgrgn->upper);
3073             Free(mrgrgn);
3074             DPA_DeletePtr(ranges->hdpa, mergeindex);
3075             if (mergeindex < index) index --;
3076         } while(1);
3077     }
3078 
3079     ranges_check(ranges, "after add");
3080     return TRUE;
3081     
3082 fail:
3083     ranges_check(ranges, "failed add");
3084     return FALSE;
3085 }
3086 
3087 static BOOL ranges_del(RANGES ranges, RANGE range)
3088 {
3089     RANGE *chkrgn;
3090     INT index;
3091 
3092     TRACE("(%s)\n", debugrange(&range));
3093     ranges_check(ranges, "before del");
3094     
3095     /* we don't use DPAS_SORTED here, since we need *
3096      * to find the first overlapping range          */
3097     index = DPA_Search(ranges->hdpa, &range, 0, ranges_cmp, 0, 0);
3098     while(index != -1) 
3099     {
3100         chkrgn = DPA_GetPtr(ranges->hdpa, index);
3101         
3102         TRACE("Matches range %s @%d\n", debugrange(chkrgn), index); 
3103 
3104         /* case 1: Same range */
3105         if ( (chkrgn->upper == range.upper) &&
3106              (chkrgn->lower == range.lower) )
3107         {
3108             DPA_DeletePtr(ranges->hdpa, index);
3109             break;
3110         }
3111         /* case 2: engulf */
3112         else if ( (chkrgn->upper <= range.upper) &&
3113                   (chkrgn->lower >= range.lower) ) 
3114         {
3115             DPA_DeletePtr(ranges->hdpa, index);
3116         }
3117         /* case 3: overlap upper */
3118         else if ( (chkrgn->upper <= range.upper) &&
3119                   (chkrgn->lower < range.lower) )
3120         {
3121             chkrgn->upper = range.lower;
3122         }
3123         /* case 4: overlap lower */
3124         else if ( (chkrgn->upper > range.upper) &&
3125                   (chkrgn->lower >= range.lower) )
3126         {
3127             chkrgn->lower = range.upper;
3128             break;
3129         }
3130         /* case 5: fully internal */
3131         else
3132         {
3133             RANGE tmprgn = *chkrgn, *newrgn;
3134 
3135             if (!(newrgn = Alloc(sizeof(RANGE)))) goto fail;
3136             newrgn->lower = chkrgn->lower;
3137             newrgn->upper = range.lower;
3138             chkrgn->lower = range.upper;
3139             if (DPA_InsertPtr(ranges->hdpa, index, newrgn) == -1)
3140             {
3141                 Free(newrgn);
3142                 goto fail;
3143             }
3144             chkrgn = &tmprgn;
3145             break;
3146         }
3147 
3148         index = DPA_Search(ranges->hdpa, &range, index, ranges_cmp, 0, 0);
3149     }
3150 
3151     ranges_check(ranges, "after del");
3152     return TRUE;
3153 
3154 fail:
3155     ranges_check(ranges, "failed del");
3156     return FALSE;
3157 }
3158 
3159 /***
3160 * DESCRIPTION:
3161 * Removes all selection ranges
3162 *
3163 * Parameters(s):
3164 * [I] infoPtr : valid pointer to the listview structure
3165 * [I] toSkip : item range to skip removing the selection
3166 *
3167 * RETURNS:
3168 *   SUCCESS : TRUE
3169 *   FAILURE : FALSE
3170 */
3171 static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
3172 {
3173     LVITEMW lvItem;
3174     ITERATOR i;
3175     RANGES clone;
3176 
3177     TRACE("()\n");
3178 
3179     lvItem.state = 0;
3180     lvItem.stateMask = LVIS_SELECTED;
3181     
3182     /* need to clone the DPA because callbacks can change it */
3183     if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
3184     iterator_rangesitems(&i, ranges_diff(clone, toSkip));
3185     while(iterator_next(&i))
3186         LISTVIEW_SetItemState(infoPtr, i.nItem, &lvItem);
3187     /* note that the iterator destructor will free the cloned range */
3188     iterator_destroy(&i);
3189 
3190     return TRUE;
3191 }
3192 
3193 static inline BOOL LISTVIEW_DeselectAllSkipItem(LISTVIEW_INFO *infoPtr, INT nItem)
3194 {
3195     RANGES toSkip;
3196    
3197     if (!(toSkip = ranges_create(1))) return FALSE;
3198     if (nItem != -1) ranges_additem(toSkip, nItem);
3199     LISTVIEW_DeselectAllSkipItems(infoPtr, toSkip);
3200     ranges_destroy(toSkip);
3201     return TRUE;
3202 }
3203 
3204 static inline BOOL LISTVIEW_DeselectAll(LISTVIEW_INFO *infoPtr)
3205 {
3206     return LISTVIEW_DeselectAllSkipItem(infoPtr, -1);
3207 }
3208 
3209 /***
3210  * DESCRIPTION:
3211  * Retrieves the number of items that are marked as selected.
3212  *
3213  * PARAMETER(S):
3214  * [I] infoPtr : valid pointer to the listview structure
3215  *
3216  * RETURN:
3217  * Number of items selected.
3218  */
3219 static INT LISTVIEW_GetSelectedCount(const LISTVIEW_INFO *infoPtr)
3220 {
3221     INT nSelectedCount = 0;
3222 
3223     if (infoPtr->uCallbackMask & LVIS_SELECTED)
3224     {
3225         INT i;
3226         for (i = 0; i < infoPtr->nItemCount; i++)
3227         {
3228             if (LISTVIEW_GetItemState(infoPtr, i, LVIS_SELECTED))
3229                 nSelectedCount++;
3230         }
3231     }
3232     else
3233         nSelectedCount = ranges_itemcount(infoPtr->selectionRanges);
3234 
3235     TRACE("nSelectedCount=%d\n", nSelectedCount);
3236     return nSelectedCount;
3237 }
3238 
3239 /***
3240  * DESCRIPTION:
3241  * Manages the item focus.
3242  *
3243  * PARAMETER(S):
3244  * [I] infoPtr : valid pointer to the listview structure
3245  * [I] nItem : item index
3246  *
3247  * RETURN:
3248  *   TRUE : focused item changed
3249  *   FALSE : focused item has NOT changed
3250  */
3251 static inline BOOL LISTVIEW_SetItemFocus(LISTVIEW_INFO *infoPtr, INT nItem)
3252 {
3253     INT oldFocus = infoPtr->nFocusedItem;
3254     LVITEMW lvItem;
3255 
3256     if (nItem == infoPtr->nFocusedItem) return FALSE;
3257     
3258     lvItem.state =  nItem == -1 ? 0 : LVIS_FOCUSED;
3259     lvItem.stateMask = LVIS_FOCUSED;
3260     LISTVIEW_SetItemState(infoPtr, nItem == -1 ? infoPtr->nFocusedItem : nItem, &lvItem);
3261 
3262     return oldFocus != infoPtr->nFocusedItem;
3263 }
3264 
3265 /* Helper function for LISTVIEW_ShiftIndices *only* */
3266 static INT shift_item(const LISTVIEW_INFO *infoPtr, INT nShiftItem, INT nItem, INT direction)
3267 {
3268     if (nShiftItem < nItem) return nShiftItem;
3269 
3270     if (nShiftItem > nItem) return nShiftItem + direction;
3271 
3272     if (direction > 0) return nShiftItem + direction;
3273 
3274     return min(nShiftItem, infoPtr->nItemCount - 1);
3275 }
3276 
3277 /**
3278 * DESCRIPTION:
3279 * Updates the various indices after an item has been inserted or deleted.
3280 *
3281 * PARAMETER(S):
3282 * [I] infoPtr : valid pointer to the listview structure
3283 * [I] nItem : item index
3284 * [I] direction : Direction of shift, +1 or -1.
3285 *
3286 * RETURN:
3287 * None
3288 */
3289 static void LISTVIEW_ShiftIndices(LISTVIEW_INFO *infoPtr, INT nItem, INT direction)
3290 {
3291     INT nNewFocus;
3292     BOOL bOldChange;
3293 
3294     /* temporarily disable change notification while shifting items */
3295     bOldChange = infoPtr->bDoChangeNotify;
3296     infoPtr->bDoChangeNotify = FALSE;
3297 
3298     TRACE("Shifting %iu, %i steps\n", nItem, direction);
3299 
3300     ranges_shift(infoPtr->selectionRanges, nItem, direction, infoPtr->nItemCount);
3301 
3302     assert(abs(direction) == 1);
3303 
3304     infoPtr->nSelectionMark = shift_item(infoPtr, infoPtr->nSelectionMark, nItem, direction);
3305 
3306     nNewFocus = shift_item(infoPtr, infoPtr->nFocusedItem, nItem, direction);
3307     if (nNewFocus != infoPtr->nFocusedItem)
3308         LISTVIEW_SetItemFocus(infoPtr, nNewFocus);
3309     
3310     /* But we are not supposed to modify nHotItem! */
3311 
3312     infoPtr->bDoChangeNotify = bOldChange;
3313 }
3314 
3315 
3316 /**
3317  * DESCRIPTION:
3318  * Adds a block of selections.
3319  *
3320  * PARAMETER(S):
3321  * [I] infoPtr : valid pointer to the listview structure
3322  * [I] nItem : item index
3323  *
3324  * RETURN:
3325  * Whether the window is still valid.
3326  */
3327 static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3328 {
3329     INT nFirst = min(infoPtr->nSelectionMark, nItem);
3330     INT nLast = max(infoPtr->nSelectionMark, nItem);
3331     HWND hwndSelf = infoPtr->hwndSelf;
3332     NMLVODSTATECHANGE nmlv;
3333     LVITEMW item;
3334     BOOL bOldChange;
3335     INT i;
3336 
3337     /* Temporarily disable change notification
3338      * If the control is LVS_OWNERDATA, we need to send
3339      * only one LVN_ODSTATECHANGED notification.
3340      * See MSDN documentation for LVN_ITEMCHANGED.
3341      */
3342     bOldChange = infoPtr->bDoChangeNotify;
3343     if (infoPtr->dwStyle & LVS_OWNERDATA) infoPtr->bDoChangeNotify = FALSE;
3344 
3345     if (nFirst == -1) nFirst = nItem;
3346 
3347     item.state = LVIS_SELECTED;
3348     item.stateMask = LVIS_SELECTED;
3349 
3350     for (i = nFirst; i <= nLast; i++)
3351         LISTVIEW_SetItemState(infoPtr,i,&item);
3352 
3353     ZeroMemory(&nmlv, sizeof(nmlv));
3354     nmlv.iFrom = nFirst;
3355     nmlv.iTo = nLast;
3356     nmlv.uNewState = 0;
3357     nmlv.uOldState = item.state;
3358 
3359     notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
3360     if (!IsWindow(hwndSelf))
3361         return FALSE;
3362     infoPtr->bDoChangeNotify = bOldChange;
3363     return TRUE;
3364 }
3365 
3366 
3367 /***
3368  * DESCRIPTION:
3369  * Sets a single group selection.
3370  *
3371  * PARAMETER(S):
3372  * [I] infoPtr : valid pointer to the listview structure
3373  * [I] nItem : item index
3374  *
3375  * RETURN:
3376  * None
3377  */
3378 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
3379 {
3380     RANGES selection;
3381     LVITEMW item;
3382     ITERATOR i;
3383     BOOL bOldChange;
3384 
3385     if (!(selection = ranges_create(100))) return;
3386 
3387     item.state = LVIS_SELECTED; 
3388     item.stateMask = LVIS_SELECTED;
3389 
3390     if ((infoPtr->uView == LV_VIEW_LIST) || (infoPtr->uView == LV_VIEW_DETAILS))
3391     {
3392         if (infoPtr->nSelectionMark == -1)
3393         {
3394             infoPtr->nSelectionMark = nItem;
3395             ranges_additem(selection, nItem);
3396         }
3397         else
3398         {
3399             RANGE sel;
3400             
3401