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

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

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

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