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