d3d9/tests: Correct nv40 results.
[wine] / dlls / user32 / listbox.c
1 /*
2  * Listbox controls
3  *
4  * Copyright 1996 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * NOTES
21  *
22  * This code was audited for completeness against the documented features
23  * of Comctl32.dll version 6.0 on Oct. 9, 2004, by Dimitrie O. Paun.
24  * 
25  * Unless otherwise noted, we believe this code to be complete, as per
26  * the specification mentioned above.
27  * If you discover missing features, or bugs, please note them below.
28  *
29  * TODO:
30  *    - GetListBoxInfo()
31  *    - LB_GETLISTBOXINFO
32  *    - LBS_NODATA
33  */
34
35 #include <string.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "wine/unicode.h"
43 #include "user_private.h"
44 #include "controls.h"
45 #include "wine/exception.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(listbox);
49
50 /* Items array granularity */
51 #define LB_ARRAY_GRANULARITY 16
52
53 /* Scrolling timeout in ms */
54 #define LB_SCROLL_TIMEOUT 50
55
56 /* Listbox system timer id */
57 #define LB_TIMER_ID  2
58
59 /* flag listbox changed while setredraw false - internal style */
60 #define LBS_DISPLAYCHANGED 0x80000000
61
62 /* Item structure */
63 typedef struct
64 {
65     LPWSTR    str;       /* Item text */
66     BOOL      selected;  /* Is item selected? */
67     UINT      height;    /* Item height (only for OWNERDRAWVARIABLE) */
68     ULONG_PTR data;      /* User data */
69 } LB_ITEMDATA;
70
71 /* Listbox structure */
72 typedef struct
73 {
74     HWND        self;           /* Our own window handle */
75     HWND        owner;          /* Owner window to send notifications to */
76     UINT        style;          /* Window style */
77     INT         width;          /* Window width */
78     INT         height;         /* Window height */
79     LB_ITEMDATA  *items;        /* Array of items */
80     INT         nb_items;       /* Number of items */
81     INT         top_item;       /* Top visible item */
82     INT         selected_item;  /* Selected item */
83     INT         focus_item;     /* Item that has the focus */
84     INT         anchor_item;    /* Anchor item for extended selection */
85     INT         item_height;    /* Default item height */
86     INT         page_size;      /* Items per listbox page */
87     INT         column_width;   /* Column width for multi-column listboxes */
88     INT         horz_extent;    /* Horizontal extent (0 if no hscroll) */
89     INT         horz_pos;       /* Horizontal position */
90     INT         nb_tabs;        /* Number of tabs in array */
91     INT        *tabs;           /* Array of tabs */
92     INT         avg_char_width; /* Average width of characters */
93     INT         wheel_remain;   /* Left over scroll amount */
94     BOOL        caret_on;       /* Is caret on? */
95     BOOL        captured;       /* Is mouse captured? */
96     BOOL        in_focus;
97     HFONT       font;           /* Current font */
98     LCID          locale;       /* Current locale for string comparisons */
99     LPHEADCOMBO   lphc;         /* ComboLBox */
100 } LB_DESCR;
101
102
103 #define IS_OWNERDRAW(descr) \
104     ((descr)->style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE))
105
106 #define HAS_STRINGS(descr) \
107     (!IS_OWNERDRAW(descr) || ((descr)->style & LBS_HASSTRINGS))
108
109
110 #define IS_MULTISELECT(descr) \
111     ((descr)->style & (LBS_MULTIPLESEL|LBS_EXTENDEDSEL) && \
112      !((descr)->style & LBS_NOSEL))
113
114 #define SEND_NOTIFICATION(descr,code) \
115     (SendMessageW( (descr)->owner, WM_COMMAND, \
116      MAKEWPARAM( GetWindowLongPtrW((descr->self),GWLP_ID), (code)), (LPARAM)(descr->self) ))
117
118 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
119
120 /* Current timer status */
121 typedef enum
122 {
123     LB_TIMER_NONE,
124     LB_TIMER_UP,
125     LB_TIMER_LEFT,
126     LB_TIMER_DOWN,
127     LB_TIMER_RIGHT
128 } TIMER_DIRECTION;
129
130 static TIMER_DIRECTION LISTBOX_Timer = LB_TIMER_NONE;
131
132 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect );
133
134 /*********************************************************************
135  * listbox class descriptor
136  */
137 static const WCHAR listboxW[] = {'L','i','s','t','B','o','x',0};
138 const struct builtin_class_descr LISTBOX_builtin_class =
139 {
140     listboxW,             /* name */
141     CS_DBLCLKS /*| CS_PARENTDC*/,  /* style */
142     WINPROC_LISTBOX,      /* proc */
143     sizeof(LB_DESCR *),   /* extra */
144     IDC_ARROW,            /* cursor */
145     0                     /* brush */
146 };
147
148
149 /*********************************************************************
150  * combolbox class descriptor
151  */
152 static const WCHAR combolboxW[] = {'C','o','m','b','o','L','B','o','x',0};
153 const struct builtin_class_descr COMBOLBOX_builtin_class =
154 {
155     combolboxW,           /* name */
156     CS_DBLCLKS | CS_SAVEBITS,  /* style */
157     WINPROC_LISTBOX,      /* proc */
158     sizeof(LB_DESCR *),   /* extra */
159     IDC_ARROW,            /* cursor */
160     0                     /* brush */
161 };
162
163
164 /***********************************************************************
165  *           LISTBOX_GetCurrentPageSize
166  *
167  * Return the current page size
168  */
169 static INT LISTBOX_GetCurrentPageSize( const LB_DESCR *descr )
170 {
171     INT i, height;
172     if (!(descr->style & LBS_OWNERDRAWVARIABLE)) return descr->page_size;
173     for (i = descr->top_item, height = 0; i < descr->nb_items; i++)
174     {
175         if ((height += descr->items[i].height) > descr->height) break;
176     }
177     if (i == descr->top_item) return 1;
178     else return i - descr->top_item;
179 }
180
181
182 /***********************************************************************
183  *           LISTBOX_GetMaxTopIndex
184  *
185  * Return the maximum possible index for the top of the listbox.
186  */
187 static INT LISTBOX_GetMaxTopIndex( const LB_DESCR *descr )
188 {
189     INT max, page;
190
191     if (descr->style & LBS_OWNERDRAWVARIABLE)
192     {
193         page = descr->height;
194         for (max = descr->nb_items - 1; max >= 0; max--)
195             if ((page -= descr->items[max].height) < 0) break;
196         if (max < descr->nb_items - 1) max++;
197     }
198     else if (descr->style & LBS_MULTICOLUMN)
199     {
200         if ((page = descr->width / descr->column_width) < 1) page = 1;
201         max = (descr->nb_items + descr->page_size - 1) / descr->page_size;
202         max = (max - page) * descr->page_size;
203     }
204     else
205     {
206         max = descr->nb_items - descr->page_size;
207     }
208     if (max < 0) max = 0;
209     return max;
210 }
211
212
213 /***********************************************************************
214  *           LISTBOX_UpdateScroll
215  *
216  * Update the scrollbars. Should be called whenever the content
217  * of the listbox changes.
218  */
219 static void LISTBOX_UpdateScroll( LB_DESCR *descr )
220 {
221     SCROLLINFO info;
222
223     /* Check the listbox scroll bar flags individually before we call
224        SetScrollInfo otherwise when the listbox style is WS_HSCROLL and
225        no WS_VSCROLL, we end up with an uninitialized, visible horizontal
226        scroll bar when we do not need one.
227     if (!(descr->style & WS_VSCROLL)) return;
228     */
229
230     /*   It is important that we check descr->style, and not wnd->dwStyle,
231        for WS_VSCROLL, as the former is exactly the one passed in
232        argument to CreateWindow.
233          In Windows (and from now on in Wine :) a listbox created
234        with such a style (no WS_SCROLL) does not update
235        the scrollbar with listbox-related data, thus letting
236        the programmer use it for his/her own purposes. */
237
238     if (descr->style & LBS_NOREDRAW) return;
239     info.cbSize = sizeof(info);
240
241     if (descr->style & LBS_MULTICOLUMN)
242     {
243         info.nMin  = 0;
244         info.nMax  = (descr->nb_items - 1) / descr->page_size;
245         info.nPos  = descr->top_item / descr->page_size;
246         info.nPage = descr->width / descr->column_width;
247         if (info.nPage < 1) info.nPage = 1;
248         info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
249         if (descr->style & LBS_DISABLENOSCROLL)
250             info.fMask |= SIF_DISABLENOSCROLL;
251         if (descr->style & WS_HSCROLL)
252             SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
253         info.nMax = 0;
254         info.fMask = SIF_RANGE;
255         if (descr->style & WS_VSCROLL)
256             SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
257     }
258     else
259     {
260         info.nMin  = 0;
261         info.nMax  = descr->nb_items - 1;
262         info.nPos  = descr->top_item;
263         info.nPage = LISTBOX_GetCurrentPageSize( descr );
264         info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
265         if (descr->style & LBS_DISABLENOSCROLL)
266             info.fMask |= SIF_DISABLENOSCROLL;
267         if (descr->style & WS_VSCROLL)
268             SetScrollInfo( descr->self, SB_VERT, &info, TRUE );
269
270         if (descr->horz_extent)
271         {
272             info.nMin  = 0;
273             info.nMax  = descr->horz_extent - 1;
274             info.nPos  = descr->horz_pos;
275             info.nPage = descr->width;
276             info.fMask = SIF_RANGE | SIF_POS | SIF_PAGE;
277             if (descr->style & LBS_DISABLENOSCROLL)
278                 info.fMask |= SIF_DISABLENOSCROLL;
279             if (descr->style & WS_HSCROLL)
280                 SetScrollInfo( descr->self, SB_HORZ, &info, TRUE );
281         }
282     }
283 }
284
285
286 /***********************************************************************
287  *           LISTBOX_SetTopItem
288  *
289  * Set the top item of the listbox, scrolling up or down if necessary.
290  */
291 static LRESULT LISTBOX_SetTopItem( LB_DESCR *descr, INT index, BOOL scroll )
292 {
293     INT max = LISTBOX_GetMaxTopIndex( descr );
294
295     TRACE("setting top item %d, scroll %d\n", index, scroll);
296
297     if (index > max) index = max;
298     if (index < 0) index = 0;
299     if (descr->style & LBS_MULTICOLUMN) index -= index % descr->page_size;
300     if (descr->top_item == index) return LB_OKAY;
301     if (scroll)
302     {
303         INT diff;
304         if (descr->style & LBS_MULTICOLUMN)
305             diff = (descr->top_item - index) / descr->page_size * descr->column_width;
306         else if (descr->style & LBS_OWNERDRAWVARIABLE)
307         {
308             INT i;
309             diff = 0;
310             if (index > descr->top_item)
311             {
312                 for (i = index - 1; i >= descr->top_item; i--)
313                     diff -= descr->items[i].height;
314             }
315             else
316             {
317                 for (i = index; i < descr->top_item; i++)
318                     diff += descr->items[i].height;
319             }
320         }
321         else
322             diff = (descr->top_item - index) * descr->item_height;
323
324         ScrollWindowEx( descr->self, 0, diff, NULL, NULL, 0, NULL,
325                         SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
326     }
327     if (!scroll) InvalidateRect( descr->self, NULL, TRUE );
328     descr->top_item = index;
329     LISTBOX_UpdateScroll( descr );
330     return LB_OKAY;
331 }
332
333
334 /***********************************************************************
335  *           LISTBOX_UpdatePage
336  *
337  * Update the page size. Should be called when the size of
338  * the client area or the item height changes.
339  */
340 static void LISTBOX_UpdatePage( LB_DESCR *descr )
341 {
342     INT page_size;
343
344     if ((descr->item_height == 0) || (page_size = descr->height / descr->item_height) < 1)
345                        page_size = 1;
346     if (page_size == descr->page_size) return;
347     descr->page_size = page_size;
348     if (descr->style & LBS_MULTICOLUMN)
349         InvalidateRect( descr->self, NULL, TRUE );
350     LISTBOX_SetTopItem( descr, descr->top_item, FALSE );
351 }
352
353
354 /***********************************************************************
355  *           LISTBOX_UpdateSize
356  *
357  * Update the size of the listbox. Should be called when the size of
358  * the client area changes.
359  */
360 static void LISTBOX_UpdateSize( LB_DESCR *descr )
361 {
362     RECT rect;
363
364     GetClientRect( descr->self, &rect );
365     descr->width  = rect.right - rect.left;
366     descr->height = rect.bottom - rect.top;
367     if (!(descr->style & LBS_NOINTEGRALHEIGHT) && !(descr->style & LBS_OWNERDRAWVARIABLE))
368     {
369         INT remaining;
370         RECT rect;
371
372         GetWindowRect( descr->self, &rect );
373         if(descr->item_height != 0)
374             remaining = descr->height % descr->item_height;
375         else
376             remaining = 0;
377         if ((descr->height > descr->item_height) && remaining)
378         {
379             TRACE("[%p]: changing height %d -> %d\n",
380                   descr->self, descr->height, descr->height - remaining );
381             SetWindowPos( descr->self, 0, 0, 0, rect.right - rect.left,
382                           rect.bottom - rect.top - remaining,
383                           SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE );
384             return;
385         }
386     }
387     TRACE("[%p]: new size = %d,%d\n", descr->self, descr->width, descr->height );
388     LISTBOX_UpdatePage( descr );
389     LISTBOX_UpdateScroll( descr );
390
391     /* Invalidate the focused item so it will be repainted correctly */
392     if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
393     {
394         InvalidateRect( descr->self, &rect, FALSE );
395     }
396 }
397
398
399 /***********************************************************************
400  *           LISTBOX_GetItemRect
401  *
402  * Get the rectangle enclosing an item, in listbox client coordinates.
403  * Return 1 if the rectangle is (partially) visible, 0 if hidden, -1 on error.
404  */
405 static LRESULT LISTBOX_GetItemRect( const LB_DESCR *descr, INT index, RECT *rect )
406 {
407     /* Index <= 0 is legal even on empty listboxes */
408     if (index && (index >= descr->nb_items))
409     {
410         memset(rect, 0, sizeof(*rect));
411         SetLastError(ERROR_INVALID_INDEX);
412         return LB_ERR;
413     }
414     SetRect( rect, 0, 0, descr->width, descr->height );
415     if (descr->style & LBS_MULTICOLUMN)
416     {
417         INT col = (index / descr->page_size) -
418                         (descr->top_item / descr->page_size);
419         rect->left += col * descr->column_width;
420         rect->right = rect->left + descr->column_width;
421         rect->top += (index % descr->page_size) * descr->item_height;
422         rect->bottom = rect->top + descr->item_height;
423     }
424     else if (descr->style & LBS_OWNERDRAWVARIABLE)
425     {
426         INT i;
427         rect->right += descr->horz_pos;
428         if ((index >= 0) && (index < descr->nb_items))
429         {
430             if (index < descr->top_item)
431             {
432                 for (i = descr->top_item-1; i >= index; i--)
433                     rect->top -= descr->items[i].height;
434             }
435             else
436             {
437                 for (i = descr->top_item; i < index; i++)
438                     rect->top += descr->items[i].height;
439             }
440             rect->bottom = rect->top + descr->items[index].height;
441
442         }
443     }
444     else
445     {
446         rect->top += (index - descr->top_item) * descr->item_height;
447         rect->bottom = rect->top + descr->item_height;
448         rect->right += descr->horz_pos;
449     }
450
451     TRACE("item %d, rect %s\n", index, wine_dbgstr_rect(rect));
452
453     return ((rect->left < descr->width) && (rect->right > 0) &&
454             (rect->top < descr->height) && (rect->bottom > 0));
455 }
456
457
458 /***********************************************************************
459  *           LISTBOX_GetItemFromPoint
460  *
461  * Return the item nearest from point (x,y) (in client coordinates).
462  */
463 static INT LISTBOX_GetItemFromPoint( const LB_DESCR *descr, INT x, INT y )
464 {
465     INT index = descr->top_item;
466
467     if (!descr->nb_items) return -1;  /* No items */
468     if (descr->style & LBS_OWNERDRAWVARIABLE)
469     {
470         INT pos = 0;
471         if (y >= 0)
472         {
473             while (index < descr->nb_items)
474             {
475                 if ((pos += descr->items[index].height) > y) break;
476                 index++;
477             }
478         }
479         else
480         {
481             while (index > 0)
482             {
483                 index--;
484                 if ((pos -= descr->items[index].height) <= y) break;
485             }
486         }
487     }
488     else if (descr->style & LBS_MULTICOLUMN)
489     {
490         if (y >= descr->item_height * descr->page_size) return -1;
491         if (y >= 0) index += y / descr->item_height;
492         if (x >= 0) index += (x / descr->column_width) * descr->page_size;
493         else index -= (((x + 1) / descr->column_width) - 1) * descr->page_size;
494     }
495     else
496     {
497         index += (y / descr->item_height);
498     }
499     if (index < 0) return 0;
500     if (index >= descr->nb_items) return -1;
501     return index;
502 }
503
504
505 /***********************************************************************
506  *           LISTBOX_PaintItem
507  *
508  * Paint an item.
509  */
510 static void LISTBOX_PaintItem( LB_DESCR *descr, HDC hdc, const RECT *rect, 
511                                INT index, UINT action, BOOL ignoreFocus )
512 {
513     LB_ITEMDATA *item = NULL;
514     if (index < descr->nb_items) item = &descr->items[index];
515
516     if (IS_OWNERDRAW(descr))
517     {
518         DRAWITEMSTRUCT dis;
519         RECT r;
520         HRGN hrgn;
521
522         if (!item)
523         {
524             if (action == ODA_FOCUS)
525                 DrawFocusRect( hdc, rect );
526             else
527                 ERR("called with an out of bounds index %d(%d) in owner draw, Not good.\n",index,descr->nb_items);
528             return;
529         }
530
531         /* some programs mess with the clipping region when
532         drawing the item, *and* restore the previous region
533         after they are done, so a region has better to exist
534         else everything ends clipped */
535         GetClientRect(descr->self, &r);
536         hrgn = set_control_clipping( hdc, &r );
537
538         dis.CtlType      = ODT_LISTBOX;
539         dis.CtlID        = GetWindowLongPtrW( descr->self, GWLP_ID );
540         dis.hwndItem     = descr->self;
541         dis.itemAction   = action;
542         dis.hDC          = hdc;
543         dis.itemID       = index;
544         dis.itemState    = 0;
545         if (item->selected) dis.itemState |= ODS_SELECTED;
546         if (!ignoreFocus && (descr->focus_item == index) &&
547             (descr->caret_on) &&
548             (descr->in_focus)) dis.itemState |= ODS_FOCUS;
549         if (!IsWindowEnabled(descr->self)) dis.itemState |= ODS_DISABLED;
550         dis.itemData     = item->data;
551         dis.rcItem       = *rect;
552         TRACE("[%p]: drawitem %d (%s) action=%02x state=%02x rect=%s\n",
553               descr->self, index, debugstr_w(item->str), action,
554               dis.itemState, wine_dbgstr_rect(rect) );
555         SendMessageW(descr->owner, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
556         SelectClipRgn( hdc, hrgn );
557         if (hrgn) DeleteObject( hrgn );
558     }
559     else
560     {
561         COLORREF oldText = 0, oldBk = 0;
562
563         if (action == ODA_FOCUS)
564         {
565             DrawFocusRect( hdc, rect );
566             return;
567         }
568         if (item && item->selected)
569         {
570             oldBk = SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
571             oldText = SetTextColor( hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
572         }
573
574         TRACE("[%p]: painting %d (%s) action=%02x rect=%s\n",
575               descr->self, index, item ? debugstr_w(item->str) : "", action,
576               wine_dbgstr_rect(rect) );
577         if (!item)
578             ExtTextOutW( hdc, rect->left + 1, rect->top,
579                            ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
580         else if (!(descr->style & LBS_USETABSTOPS))
581             ExtTextOutW( hdc, rect->left + 1, rect->top,
582                          ETO_OPAQUE | ETO_CLIPPED, rect, item->str,
583                          strlenW(item->str), NULL );
584         else
585         {
586             /* Output empty string to paint background in the full width. */
587             ExtTextOutW( hdc, rect->left + 1, rect->top,
588                          ETO_OPAQUE | ETO_CLIPPED, rect, NULL, 0, NULL );
589             TabbedTextOutW( hdc, rect->left + 1 , rect->top,
590                             item->str, strlenW(item->str),
591                             descr->nb_tabs, descr->tabs, 0);
592         }
593         if (item && item->selected)
594         {
595             SetBkColor( hdc, oldBk );
596             SetTextColor( hdc, oldText );
597         }
598         if (!ignoreFocus && (descr->focus_item == index) &&
599             (descr->caret_on) &&
600             (descr->in_focus)) DrawFocusRect( hdc, rect );
601     }
602 }
603
604
605 /***********************************************************************
606  *           LISTBOX_SetRedraw
607  *
608  * Change the redraw flag.
609  */
610 static void LISTBOX_SetRedraw( LB_DESCR *descr, BOOL on )
611 {
612     if (on)
613     {
614         if (!(descr->style & LBS_NOREDRAW)) return;
615         descr->style &= ~LBS_NOREDRAW;
616         if (descr->style & LBS_DISPLAYCHANGED)
617         {     /* page was changed while setredraw false, refresh automatically */
618             InvalidateRect(descr->self, NULL, TRUE);
619             if ((descr->top_item + descr->page_size) > descr->nb_items)
620             {      /* reset top of page if less than number of items/page */
621                 descr->top_item = descr->nb_items - descr->page_size;
622                 if (descr->top_item < 0) descr->top_item = 0;
623             }
624             descr->style &= ~LBS_DISPLAYCHANGED;
625         }
626         LISTBOX_UpdateScroll( descr );
627     }
628     else descr->style |= LBS_NOREDRAW;
629 }
630
631
632 /***********************************************************************
633  *           LISTBOX_RepaintItem
634  *
635  * Repaint a single item synchronously.
636  */
637 static void LISTBOX_RepaintItem( LB_DESCR *descr, INT index, UINT action )
638 {
639     HDC hdc;
640     RECT rect;
641     HFONT oldFont = 0;
642     HBRUSH hbrush, oldBrush = 0;
643
644     /* Do not repaint the item if the item is not visible */
645     if (!IsWindowVisible(descr->self)) return;
646     if (descr->style & LBS_NOREDRAW)
647     {
648         descr->style |= LBS_DISPLAYCHANGED;
649         return;
650     }
651     if (LISTBOX_GetItemRect( descr, index, &rect ) != 1) return;
652     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
653     if (descr->font) oldFont = SelectObject( hdc, descr->font );
654     hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
655                                    (WPARAM)hdc, (LPARAM)descr->self );
656     if (hbrush) oldBrush = SelectObject( hdc, hbrush );
657     if (!IsWindowEnabled(descr->self))
658         SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
659     SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
660     LISTBOX_PaintItem( descr, hdc, &rect, index, action, TRUE );
661     if (oldFont) SelectObject( hdc, oldFont );
662     if (oldBrush) SelectObject( hdc, oldBrush );
663     ReleaseDC( descr->self, hdc );
664 }
665
666
667 /***********************************************************************
668  *           LISTBOX_DrawFocusRect
669  */
670 static void LISTBOX_DrawFocusRect( LB_DESCR *descr, BOOL on )
671 {
672     HDC hdc;
673     RECT rect;
674     HFONT oldFont = 0;
675
676     /* Do not repaint the item if the item is not visible */
677     if (!IsWindowVisible(descr->self)) return;
678
679     if (descr->focus_item == -1) return;
680     if (!descr->caret_on || !descr->in_focus) return;
681
682     if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) != 1) return;
683     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE ))) return;
684     if (descr->font) oldFont = SelectObject( hdc, descr->font );
685     if (!IsWindowEnabled(descr->self))
686         SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
687     SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
688     LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, !on );
689     if (oldFont) SelectObject( hdc, oldFont );
690     ReleaseDC( descr->self, hdc );
691 }
692
693
694 /***********************************************************************
695  *           LISTBOX_InitStorage
696  */
697 static LRESULT LISTBOX_InitStorage( LB_DESCR *descr, INT nb_items )
698 {
699     LB_ITEMDATA *item;
700
701     nb_items += LB_ARRAY_GRANULARITY - 1;
702     nb_items -= (nb_items % LB_ARRAY_GRANULARITY);
703     if (descr->items) {
704         nb_items += HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
705         item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
706                               nb_items * sizeof(LB_ITEMDATA));
707     }
708     else {
709         item = HeapAlloc( GetProcessHeap(), 0,
710                               nb_items * sizeof(LB_ITEMDATA));
711     }
712
713     if (!item)
714     {
715         SEND_NOTIFICATION( descr, LBN_ERRSPACE );
716         return LB_ERRSPACE;
717     }
718     descr->items = item;
719     return LB_OKAY;
720 }
721
722
723 /***********************************************************************
724  *           LISTBOX_SetTabStops
725  */
726 static BOOL LISTBOX_SetTabStops( LB_DESCR *descr, INT count, LPINT tabs )
727 {
728     INT i;
729
730     if (!(descr->style & LBS_USETABSTOPS))
731     {
732         SetLastError(ERROR_LB_WITHOUT_TABSTOPS);
733         return FALSE;
734     }
735
736     HeapFree( GetProcessHeap(), 0, descr->tabs );
737     if (!(descr->nb_tabs = count))
738     {
739         descr->tabs = NULL;
740         return TRUE;
741     }
742     if (!(descr->tabs = HeapAlloc( GetProcessHeap(), 0,
743                                             descr->nb_tabs * sizeof(INT) )))
744         return FALSE;
745     memcpy( descr->tabs, tabs, descr->nb_tabs * sizeof(INT) );
746
747     /* convert into "dialog units"*/
748     for (i = 0; i < descr->nb_tabs; i++)
749         descr->tabs[i] = MulDiv(descr->tabs[i], descr->avg_char_width, 4);
750
751     return TRUE;
752 }
753
754
755 /***********************************************************************
756  *           LISTBOX_GetText
757  */
758 static LRESULT LISTBOX_GetText( LB_DESCR *descr, INT index, LPWSTR buffer, BOOL unicode )
759 {
760     DWORD len;
761
762     if ((index < 0) || (index >= descr->nb_items))
763     {
764         SetLastError(ERROR_INVALID_INDEX);
765         return LB_ERR;
766     }
767     if (HAS_STRINGS(descr))
768     {
769         if (!buffer)
770         {
771             len = strlenW(descr->items[index].str);
772             if( unicode )
773                 return len;
774             return WideCharToMultiByte( CP_ACP, 0, descr->items[index].str, len,
775                                         NULL, 0, NULL, NULL );
776         }
777
778         TRACE("index %d (0x%04x) %s\n", index, index, debugstr_w(descr->items[index].str));
779
780         __TRY  /* hide a Delphi bug that passes a read-only buffer */
781         {
782             if(unicode)
783             {
784                 strcpyW( buffer, descr->items[index].str );
785                 len = strlenW(buffer);
786             }
787             else
788             {
789                 len = WideCharToMultiByte(CP_ACP, 0, descr->items[index].str, -1,
790                                           (LPSTR)buffer, 0x7FFFFFFF, NULL, NULL) - 1;
791             }
792         }
793         __EXCEPT_PAGE_FAULT
794         {
795             WARN( "got an invalid buffer (Delphi bug?)\n" );
796             SetLastError( ERROR_INVALID_PARAMETER );
797             return LB_ERR;
798         }
799         __ENDTRY
800     } else {
801         if (buffer)
802             *((LPDWORD)buffer)=*(LPDWORD)(&descr->items[index].data);
803         len = sizeof(DWORD);
804     }
805     return len;
806 }
807
808 static inline INT LISTBOX_lstrcmpiW( LCID lcid, LPCWSTR str1, LPCWSTR str2 )
809 {
810     INT ret = CompareStringW( lcid, NORM_IGNORECASE, str1, -1, str2, -1 );
811     if (ret == CSTR_LESS_THAN)
812         return -1;
813     if (ret == CSTR_EQUAL)
814         return 0;
815     if (ret == CSTR_GREATER_THAN)
816         return 1;
817     return -1;
818 }
819
820 /***********************************************************************
821  *           LISTBOX_FindStringPos
822  *
823  * Find the nearest string located before a given string in sort order.
824  * If 'exact' is TRUE, return an error if we don't get an exact match.
825  */
826 static INT LISTBOX_FindStringPos( LB_DESCR *descr, LPCWSTR str, BOOL exact )
827 {
828     INT index, min, max, res = -1;
829
830     if (!(descr->style & LBS_SORT)) return -1;  /* Add it at the end */
831     min = 0;
832     max = descr->nb_items;
833     while (min != max)
834     {
835         index = (min + max) / 2;
836         if (HAS_STRINGS(descr))
837             res = LISTBOX_lstrcmpiW( descr->locale, str, descr->items[index].str);
838         else
839         {
840             COMPAREITEMSTRUCT cis;
841             UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
842
843             cis.CtlType    = ODT_LISTBOX;
844             cis.CtlID      = id;
845             cis.hwndItem   = descr->self;
846             /* note that some application (MetaStock) expects the second item
847              * to be in the listbox */
848             cis.itemID1    = -1;
849             cis.itemData1  = (ULONG_PTR)str;
850             cis.itemID2    = index;
851             cis.itemData2  = descr->items[index].data;
852             cis.dwLocaleId = descr->locale;
853             res = SendMessageW( descr->owner, WM_COMPAREITEM, id, (LPARAM)&cis );
854         }
855         if (!res) return index;
856         if (res < 0) max = index;
857         else min = index + 1;
858     }
859     return exact ? -1 : max;
860 }
861
862
863 /***********************************************************************
864  *           LISTBOX_FindFileStrPos
865  *
866  * Find the nearest string located before a given string in directory
867  * sort order (i.e. first files, then directories, then drives).
868  */
869 static INT LISTBOX_FindFileStrPos( LB_DESCR *descr, LPCWSTR str )
870 {
871     INT min, max, res = -1;
872
873     if (!HAS_STRINGS(descr))
874         return LISTBOX_FindStringPos( descr, str, FALSE );
875     min = 0;
876     max = descr->nb_items;
877     while (min != max)
878     {
879         INT index = (min + max) / 2;
880         LPCWSTR p = descr->items[index].str;
881         if (*p == '[')  /* drive or directory */
882         {
883             if (*str != '[') res = -1;
884             else if (p[1] == '-')  /* drive */
885             {
886                 if (str[1] == '-') res = str[2] - p[2];
887                 else res = -1;
888             }
889             else  /* directory */
890             {
891                 if (str[1] == '-') res = 1;
892                 else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
893             }
894         }
895         else  /* filename */
896         {
897             if (*str == '[') res = 1;
898             else res = LISTBOX_lstrcmpiW( descr->locale, str, p );
899         }
900         if (!res) return index;
901         if (res < 0) max = index;
902         else min = index + 1;
903     }
904     return max;
905 }
906
907
908 /***********************************************************************
909  *           LISTBOX_FindString
910  *
911  * Find the item beginning with a given string.
912  */
913 static INT LISTBOX_FindString( LB_DESCR *descr, INT start, LPCWSTR str, BOOL exact )
914 {
915     INT i;
916     LB_ITEMDATA *item;
917
918     if (start >= descr->nb_items) start = -1;
919     item = descr->items + start + 1;
920     if (HAS_STRINGS(descr))
921     {
922         if (!str || ! str[0] ) return LB_ERR;
923         if (exact)
924         {
925             for (i = start + 1; i < descr->nb_items; i++, item++)
926                 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
927             for (i = 0, item = descr->items; i <= start; i++, item++)
928                 if (!LISTBOX_lstrcmpiW( descr->locale, str, item->str )) return i;
929         }
930         else
931         {
932  /* Special case for drives and directories: ignore prefix */
933 #define CHECK_DRIVE(item) \
934     if ((item)->str[0] == '[') \
935     { \
936         if (!strncmpiW( str, (item)->str+1, len )) return i; \
937         if (((item)->str[1] == '-') && !strncmpiW(str, (item)->str+2, len)) \
938         return i; \
939     }
940
941             INT len = strlenW(str);
942             for (i = start + 1; i < descr->nb_items; i++, item++)
943             {
944                if (!strncmpiW( str, item->str, len )) return i;
945                CHECK_DRIVE(item);
946             }
947             for (i = 0, item = descr->items; i <= start; i++, item++)
948             {
949                if (!strncmpiW( str, item->str, len )) return i;
950                CHECK_DRIVE(item);
951             }
952 #undef CHECK_DRIVE
953         }
954     }
955     else
956     {
957         if (exact && (descr->style & LBS_SORT))
958             /* If sorted, use a WM_COMPAREITEM binary search */
959             return LISTBOX_FindStringPos( descr, str, TRUE );
960
961         /* Otherwise use a linear search */
962         for (i = start + 1; i < descr->nb_items; i++, item++)
963             if (item->data == (ULONG_PTR)str) return i;
964         for (i = 0, item = descr->items; i <= start; i++, item++)
965             if (item->data == (ULONG_PTR)str) return i;
966     }
967     return LB_ERR;
968 }
969
970
971 /***********************************************************************
972  *           LISTBOX_GetSelCount
973  */
974 static LRESULT LISTBOX_GetSelCount( const LB_DESCR *descr )
975 {
976     INT i, count;
977     const LB_ITEMDATA *item = descr->items;
978
979     if (!(descr->style & LBS_MULTIPLESEL) ||
980         (descr->style & LBS_NOSEL))
981       return LB_ERR;
982     for (i = count = 0; i < descr->nb_items; i++, item++)
983         if (item->selected) count++;
984     return count;
985 }
986
987
988 /***********************************************************************
989  *           LISTBOX_GetSelItems
990  */
991 static LRESULT LISTBOX_GetSelItems( const LB_DESCR *descr, INT max, LPINT array )
992 {
993     INT i, count;
994     const LB_ITEMDATA *item = descr->items;
995
996     if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
997     for (i = count = 0; (i < descr->nb_items) && (count < max); i++, item++)
998         if (item->selected) array[count++] = i;
999     return count;
1000 }
1001
1002
1003 /***********************************************************************
1004  *           LISTBOX_Paint
1005  */
1006 static LRESULT LISTBOX_Paint( LB_DESCR *descr, HDC hdc )
1007 {
1008     INT i, col_pos = descr->page_size - 1;
1009     RECT rect;
1010     RECT focusRect = {-1, -1, -1, -1};
1011     HFONT oldFont = 0;
1012     HBRUSH hbrush, oldBrush = 0;
1013
1014     if (descr->style & LBS_NOREDRAW) return 0;
1015
1016     SetRect( &rect, 0, 0, descr->width, descr->height );
1017     if (descr->style & LBS_MULTICOLUMN)
1018         rect.right = rect.left + descr->column_width;
1019     else if (descr->horz_pos)
1020     {
1021         SetWindowOrgEx( hdc, descr->horz_pos, 0, NULL );
1022         rect.right += descr->horz_pos;
1023     }
1024
1025     if (descr->font) oldFont = SelectObject( hdc, descr->font );
1026     hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
1027                                    (WPARAM)hdc, (LPARAM)descr->self );
1028     if (hbrush) oldBrush = SelectObject( hdc, hbrush );
1029     if (!IsWindowEnabled(descr->self)) SetTextColor( hdc, GetSysColor( COLOR_GRAYTEXT ) );
1030
1031     if (!descr->nb_items && (descr->focus_item != -1) && descr->caret_on &&
1032         (descr->in_focus))
1033     {
1034         /* Special case for empty listbox: paint focus rect */
1035         rect.bottom = rect.top + descr->item_height;
1036         ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1037                      &rect, NULL, 0, NULL );
1038         LISTBOX_PaintItem( descr, hdc, &rect, descr->focus_item, ODA_FOCUS, FALSE );
1039         rect.top = rect.bottom;
1040     }
1041
1042     /* Paint all the item, regarding the selection
1043        Focus state will be painted after  */
1044
1045     for (i = descr->top_item; i < descr->nb_items; i++)
1046     {
1047         if (!(descr->style & LBS_OWNERDRAWVARIABLE))
1048             rect.bottom = rect.top + descr->item_height;
1049         else
1050             rect.bottom = rect.top + descr->items[i].height;
1051
1052         if (i == descr->focus_item)
1053         {
1054             /* keep the focus rect, to paint the focus item after */
1055             focusRect.left = rect.left;
1056             focusRect.right = rect.right;
1057             focusRect.top = rect.top;
1058             focusRect.bottom = rect.bottom;
1059         }
1060         LISTBOX_PaintItem( descr, hdc, &rect, i, ODA_DRAWENTIRE, TRUE );
1061         rect.top = rect.bottom;
1062
1063         if ((descr->style & LBS_MULTICOLUMN) && !col_pos)
1064         {
1065             if (!IS_OWNERDRAW(descr))
1066             {
1067                 /* Clear the bottom of the column */
1068                 if (rect.top < descr->height)
1069                 {
1070                     rect.bottom = descr->height;
1071                     ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1072                                    &rect, NULL, 0, NULL );
1073                 }
1074             }
1075
1076             /* Go to the next column */
1077             rect.left += descr->column_width;
1078             rect.right += descr->column_width;
1079             rect.top = 0;
1080             col_pos = descr->page_size - 1;
1081         }
1082         else
1083         {
1084             col_pos--;
1085             if (rect.top >= descr->height) break;
1086         }
1087     }
1088
1089     /* Paint the focus item now */
1090     if (focusRect.top != focusRect.bottom &&
1091         descr->caret_on && descr->in_focus)
1092         LISTBOX_PaintItem( descr, hdc, &focusRect, descr->focus_item, ODA_FOCUS, FALSE );
1093
1094     if (!IS_OWNERDRAW(descr))
1095     {
1096         /* Clear the remainder of the client area */
1097         if (rect.top < descr->height)
1098         {
1099             rect.bottom = descr->height;
1100             ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1101                            &rect, NULL, 0, NULL );
1102         }
1103         if (rect.right < descr->width)
1104         {
1105             rect.left   = rect.right;
1106             rect.right  = descr->width;
1107             rect.top    = 0;
1108             rect.bottom = descr->height;
1109             ExtTextOutW( hdc, 0, 0, ETO_OPAQUE | ETO_CLIPPED,
1110                            &rect, NULL, 0, NULL );
1111         }
1112     }
1113     if (oldFont) SelectObject( hdc, oldFont );
1114     if (oldBrush) SelectObject( hdc, oldBrush );
1115     return 0;
1116 }
1117
1118
1119 /***********************************************************************
1120  *           LISTBOX_InvalidateItems
1121  *
1122  * Invalidate all items from a given item. If the specified item is not
1123  * visible, nothing happens.
1124  */
1125 static void LISTBOX_InvalidateItems( LB_DESCR *descr, INT index )
1126 {
1127     RECT rect;
1128
1129     if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1130     {
1131         if (descr->style & LBS_NOREDRAW)
1132         {
1133             descr->style |= LBS_DISPLAYCHANGED;
1134             return;
1135         }
1136         rect.bottom = descr->height;
1137         InvalidateRect( descr->self, &rect, TRUE );
1138         if (descr->style & LBS_MULTICOLUMN)
1139         {
1140             /* Repaint the other columns */
1141             rect.left  = rect.right;
1142             rect.right = descr->width;
1143             rect.top   = 0;
1144             InvalidateRect( descr->self, &rect, TRUE );
1145         }
1146     }
1147 }
1148
1149 static void LISTBOX_InvalidateItemRect( LB_DESCR *descr, INT index )
1150 {
1151     RECT rect;
1152
1153     if (LISTBOX_GetItemRect( descr, index, &rect ) == 1)
1154         InvalidateRect( descr->self, &rect, TRUE );
1155 }
1156
1157 /***********************************************************************
1158  *           LISTBOX_GetItemHeight
1159  */
1160 static LRESULT LISTBOX_GetItemHeight( const LB_DESCR *descr, INT index )
1161 {
1162     if (descr->style & LBS_OWNERDRAWVARIABLE && descr->nb_items > 0)
1163     {
1164         if ((index < 0) || (index >= descr->nb_items))
1165         {
1166             SetLastError(ERROR_INVALID_INDEX);
1167             return LB_ERR;
1168         }
1169         return descr->items[index].height;
1170     }
1171     else return descr->item_height;
1172 }
1173
1174
1175 /***********************************************************************
1176  *           LISTBOX_SetItemHeight
1177  */
1178 static LRESULT LISTBOX_SetItemHeight( LB_DESCR *descr, INT index, INT height, BOOL repaint )
1179 {
1180     if (height > MAXBYTE)
1181         return -1;
1182
1183     if (!height) height = 1;
1184
1185     if (descr->style & LBS_OWNERDRAWVARIABLE)
1186     {
1187         if ((index < 0) || (index >= descr->nb_items))
1188         {
1189             SetLastError(ERROR_INVALID_INDEX);
1190             return LB_ERR;
1191         }
1192         TRACE("[%p]: item %d height = %d\n", descr->self, index, height );
1193         descr->items[index].height = height;
1194         LISTBOX_UpdateScroll( descr );
1195         if (repaint)
1196             LISTBOX_InvalidateItems( descr, index );
1197     }
1198     else if (height != descr->item_height)
1199     {
1200         TRACE("[%p]: new height = %d\n", descr->self, height );
1201         descr->item_height = height;
1202         LISTBOX_UpdatePage( descr );
1203         LISTBOX_UpdateScroll( descr );
1204         if (repaint)
1205             InvalidateRect( descr->self, 0, TRUE );
1206     }
1207     return LB_OKAY;
1208 }
1209
1210
1211 /***********************************************************************
1212  *           LISTBOX_SetHorizontalPos
1213  */
1214 static void LISTBOX_SetHorizontalPos( LB_DESCR *descr, INT pos )
1215 {
1216     INT diff;
1217
1218     if (pos > descr->horz_extent - descr->width)
1219         pos = descr->horz_extent - descr->width;
1220     if (pos < 0) pos = 0;
1221     if (!(diff = descr->horz_pos - pos)) return;
1222     TRACE("[%p]: new horz pos = %d\n", descr->self, pos );
1223     descr->horz_pos = pos;
1224     LISTBOX_UpdateScroll( descr );
1225     if (abs(diff) < descr->width)
1226     {
1227         RECT rect;
1228         /* Invalidate the focused item so it will be repainted correctly */
1229         if (LISTBOX_GetItemRect( descr, descr->focus_item, &rect ) == 1)
1230             InvalidateRect( descr->self, &rect, TRUE );
1231         ScrollWindowEx( descr->self, diff, 0, NULL, NULL, 0, NULL,
1232                           SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN );
1233     }
1234     else
1235         InvalidateRect( descr->self, NULL, TRUE );
1236 }
1237
1238
1239 /***********************************************************************
1240  *           LISTBOX_SetHorizontalExtent
1241  */
1242 static LRESULT LISTBOX_SetHorizontalExtent( LB_DESCR *descr, INT extent )
1243 {
1244     if (!descr->horz_extent || (descr->style & LBS_MULTICOLUMN))
1245         return LB_OKAY;
1246     if (extent <= 0) extent = 1;
1247     if (extent == descr->horz_extent) return LB_OKAY;
1248     TRACE("[%p]: new horz extent = %d\n", descr->self, extent );
1249     descr->horz_extent = extent;
1250     if (descr->horz_pos > extent - descr->width)
1251         LISTBOX_SetHorizontalPos( descr, extent - descr->width );
1252     else
1253         LISTBOX_UpdateScroll( descr );
1254     return LB_OKAY;
1255 }
1256
1257
1258 /***********************************************************************
1259  *           LISTBOX_SetColumnWidth
1260  */
1261 static LRESULT LISTBOX_SetColumnWidth( LB_DESCR *descr, INT width)
1262 {
1263     if (width == descr->column_width) return LB_OKAY;
1264     TRACE("[%p]: new column width = %d\n", descr->self, width );
1265     descr->column_width = width;
1266     LISTBOX_UpdatePage( descr );
1267     return LB_OKAY;
1268 }
1269
1270
1271 /***********************************************************************
1272  *           LISTBOX_SetFont
1273  *
1274  * Returns the item height.
1275  */
1276 static INT LISTBOX_SetFont( LB_DESCR *descr, HFONT font )
1277 {
1278     HDC hdc;
1279     HFONT oldFont = 0;
1280     const char *alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1281     SIZE sz;
1282
1283     descr->font = font;
1284
1285     if (!(hdc = GetDCEx( descr->self, 0, DCX_CACHE )))
1286     {
1287         ERR("unable to get DC.\n" );
1288         return 16;
1289     }
1290     if (font) oldFont = SelectObject( hdc, font );
1291     GetTextExtentPointA( hdc, alphabet, 52, &sz);
1292     if (oldFont) SelectObject( hdc, oldFont );
1293     ReleaseDC( descr->self, hdc );
1294
1295     descr->avg_char_width = (sz.cx / 26 + 1) / 2;
1296     if (!IS_OWNERDRAW(descr))
1297         LISTBOX_SetItemHeight( descr, 0, sz.cy, FALSE );
1298     return sz.cy;
1299 }
1300
1301
1302 /***********************************************************************
1303  *           LISTBOX_MakeItemVisible
1304  *
1305  * Make sure that a given item is partially or fully visible.
1306  */
1307 static void LISTBOX_MakeItemVisible( LB_DESCR *descr, INT index, BOOL fully )
1308 {
1309     INT top;
1310
1311     TRACE("current top item %d, index %d, fully %d\n", descr->top_item, index, fully);
1312
1313     if (index <= descr->top_item) top = index;
1314     else if (descr->style & LBS_MULTICOLUMN)
1315     {
1316         INT cols = descr->width;
1317         if (!fully) cols += descr->column_width - 1;
1318         if (cols >= descr->column_width) cols /= descr->column_width;
1319         else cols = 1;
1320         if (index < descr->top_item + (descr->page_size * cols)) return;
1321         top = index - descr->page_size * (cols - 1);
1322     }
1323     else if (descr->style & LBS_OWNERDRAWVARIABLE)
1324     {
1325         INT height = fully ? descr->items[index].height : 1;
1326         for (top = index; top > descr->top_item; top--)
1327             if ((height += descr->items[top-1].height) > descr->height) break;
1328     }
1329     else
1330     {
1331         if (index < descr->top_item + descr->page_size) return;
1332         if (!fully && (index == descr->top_item + descr->page_size) &&
1333             (descr->height > (descr->page_size * descr->item_height))) return;
1334         top = index - descr->page_size + 1;
1335     }
1336     LISTBOX_SetTopItem( descr, top, TRUE );
1337 }
1338
1339 /***********************************************************************
1340  *           LISTBOX_SetCaretIndex
1341  *
1342  * NOTES
1343  *   index must be between 0 and descr->nb_items-1, or LB_ERR is returned.
1344  *
1345  */
1346 static LRESULT LISTBOX_SetCaretIndex( LB_DESCR *descr, INT index, BOOL fully_visible )
1347 {
1348     INT oldfocus = descr->focus_item;
1349
1350     TRACE("old focus %d, index %d\n", oldfocus, index);
1351
1352     if (descr->style & LBS_NOSEL) return LB_ERR;
1353     if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1354     if (index == oldfocus) return LB_OKAY;
1355
1356     LISTBOX_DrawFocusRect( descr, FALSE );
1357     descr->focus_item = index;
1358
1359     LISTBOX_MakeItemVisible( descr, index, fully_visible );
1360     LISTBOX_DrawFocusRect( descr, TRUE );
1361
1362     return LB_OKAY;
1363 }
1364
1365
1366 /***********************************************************************
1367  *           LISTBOX_SelectItemRange
1368  *
1369  * Select a range of items. Should only be used on a MULTIPLESEL listbox.
1370  */
1371 static LRESULT LISTBOX_SelectItemRange( LB_DESCR *descr, INT first,
1372                                         INT last, BOOL on )
1373 {
1374     INT i;
1375
1376     /* A few sanity checks */
1377
1378     if (descr->style & LBS_NOSEL) return LB_ERR;
1379     if (!(descr->style & LBS_MULTIPLESEL)) return LB_ERR;
1380
1381     if (!descr->nb_items) return LB_OKAY;
1382
1383     if (last == -1 || last >= descr->nb_items) last = descr->nb_items - 1;
1384     if (first < 0) first = 0;
1385     if (last < first) return LB_OKAY;
1386
1387     if (on)  /* Turn selection on */
1388     {
1389         for (i = first; i <= last; i++)
1390         {
1391             if (descr->items[i].selected) continue;
1392             descr->items[i].selected = TRUE;
1393             LISTBOX_InvalidateItemRect(descr, i);
1394         }
1395     }
1396     else  /* Turn selection off */
1397     {
1398         for (i = first; i <= last; i++)
1399         {
1400             if (!descr->items[i].selected) continue;
1401             descr->items[i].selected = FALSE;
1402             LISTBOX_InvalidateItemRect(descr, i);
1403         }
1404     }
1405     return LB_OKAY;
1406 }
1407
1408 /***********************************************************************
1409  *           LISTBOX_SetSelection
1410  */
1411 static LRESULT LISTBOX_SetSelection( LB_DESCR *descr, INT index,
1412                                      BOOL on, BOOL send_notify )
1413 {
1414     TRACE( "cur_sel=%d index=%d notify=%s\n",
1415            descr->selected_item, index, send_notify ? "YES" : "NO" );
1416
1417     if (descr->style & LBS_NOSEL)
1418     {
1419         descr->selected_item = index;
1420         return LB_ERR;
1421     }
1422     if ((index < -1) || (index >= descr->nb_items)) return LB_ERR;
1423     if (descr->style & LBS_MULTIPLESEL)
1424     {
1425         if (index == -1)  /* Select all items */
1426             return LISTBOX_SelectItemRange( descr, 0, descr->nb_items, on );
1427         else  /* Only one item */
1428             return LISTBOX_SelectItemRange( descr, index, index, on );
1429     }
1430     else
1431     {
1432         INT oldsel = descr->selected_item;
1433         if (index == oldsel) return LB_OKAY;
1434         if (oldsel != -1) descr->items[oldsel].selected = FALSE;
1435         if (index != -1) descr->items[index].selected = TRUE;
1436         if (oldsel != -1) LISTBOX_RepaintItem( descr, oldsel, ODA_SELECT );
1437         descr->selected_item = index;
1438         if (index != -1) LISTBOX_RepaintItem( descr, index, ODA_SELECT );
1439         if (send_notify && descr->nb_items) SEND_NOTIFICATION( descr,
1440                                (index != -1) ? LBN_SELCHANGE : LBN_SELCANCEL );
1441         else
1442             if( descr->lphc ) /* set selection change flag for parent combo */
1443                 descr->lphc->wState |= CBF_SELCHANGE;
1444     }
1445     return LB_OKAY;
1446 }
1447
1448
1449 /***********************************************************************
1450  *           LISTBOX_MoveCaret
1451  *
1452  * Change the caret position and extend the selection to the new caret.
1453  */
1454 static void LISTBOX_MoveCaret( LB_DESCR *descr, INT index, BOOL fully_visible )
1455 {
1456     TRACE("old focus %d, index %d\n", descr->focus_item, index);
1457
1458     if ((index <  0) || (index >= descr->nb_items))
1459         return;
1460
1461     /* Important, repaint needs to be done in this order if
1462        you want to mimic Windows behavior:
1463        1. Remove the focus and paint the item
1464        2. Remove the selection and paint the item(s)
1465        3. Set the selection and repaint the item(s)
1466        4. Set the focus to 'index' and repaint the item */
1467
1468     /* 1. remove the focus and repaint the item */
1469     LISTBOX_DrawFocusRect( descr, FALSE );
1470
1471     /* 2. then turn off the previous selection */
1472     /* 3. repaint the new selected item */
1473     if (descr->style & LBS_EXTENDEDSEL)
1474     {
1475         if (descr->anchor_item != -1)
1476         {
1477             INT first = min( index, descr->anchor_item );
1478             INT last  = max( index, descr->anchor_item );
1479             if (first > 0)
1480                 LISTBOX_SelectItemRange( descr, 0, first - 1, FALSE );
1481             LISTBOX_SelectItemRange( descr, last + 1, -1, FALSE );
1482             LISTBOX_SelectItemRange( descr, first, last, TRUE );
1483         }
1484     }
1485     else if (!(descr->style & LBS_MULTIPLESEL))
1486     {
1487         /* Set selection to new caret item */
1488         LISTBOX_SetSelection( descr, index, TRUE, FALSE );
1489     }
1490
1491     /* 4. repaint the new item with the focus */
1492     descr->focus_item = index;
1493     LISTBOX_MakeItemVisible( descr, index, fully_visible );
1494     LISTBOX_DrawFocusRect( descr, TRUE );
1495 }
1496
1497
1498 /***********************************************************************
1499  *           LISTBOX_InsertItem
1500  */
1501 static LRESULT LISTBOX_InsertItem( LB_DESCR *descr, INT index,
1502                                    LPWSTR str, ULONG_PTR data )
1503 {
1504     LB_ITEMDATA *item;
1505     INT max_items;
1506     INT oldfocus = descr->focus_item;
1507
1508     if (index == -1) index = descr->nb_items;
1509     else if ((index < 0) || (index > descr->nb_items)) return LB_ERR;
1510     if (!descr->items) max_items = 0;
1511     else max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(*item);
1512     if (descr->nb_items == max_items)
1513     {
1514         /* We need to grow the array */
1515         max_items += LB_ARRAY_GRANULARITY;
1516         if (descr->items)
1517             item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1518                                   max_items * sizeof(LB_ITEMDATA) );
1519         else
1520             item = HeapAlloc( GetProcessHeap(), 0,
1521                                   max_items * sizeof(LB_ITEMDATA) );
1522         if (!item)
1523         {
1524             SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1525             return LB_ERRSPACE;
1526         }
1527         descr->items = item;
1528     }
1529
1530     /* Insert the item structure */
1531
1532     item = &descr->items[index];
1533     if (index < descr->nb_items)
1534         RtlMoveMemory( item + 1, item,
1535                        (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1536     item->str      = str;
1537     item->data     = data;
1538     item->height   = 0;
1539     item->selected = FALSE;
1540     descr->nb_items++;
1541
1542     /* Get item height */
1543
1544     if (descr->style & LBS_OWNERDRAWVARIABLE)
1545     {
1546         MEASUREITEMSTRUCT mis;
1547         UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1548
1549         mis.CtlType    = ODT_LISTBOX;
1550         mis.CtlID      = id;
1551         mis.itemID     = index;
1552         mis.itemData   = descr->items[index].data;
1553         mis.itemHeight = descr->item_height;
1554         SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
1555         item->height = mis.itemHeight ? mis.itemHeight : 1;
1556         TRACE("[%p]: measure item %d (%s) = %d\n",
1557               descr->self, index, str ? debugstr_w(str) : "", item->height );
1558     }
1559
1560     /* Repaint the items */
1561
1562     LISTBOX_UpdateScroll( descr );
1563     LISTBOX_InvalidateItems( descr, index );
1564
1565     /* Move selection and focused item */
1566     /* If listbox was empty, set focus to the first item */
1567     if (descr->nb_items == 1)
1568          LISTBOX_SetCaretIndex( descr, 0, FALSE );
1569     /* single select don't change selection index in win31 */
1570     else if ((ISWIN31) && !(IS_MULTISELECT(descr)))
1571     {
1572         descr->selected_item++;
1573         LISTBOX_SetSelection( descr, descr->selected_item-1, TRUE, FALSE );
1574     }
1575     else
1576     {
1577         if (index <= descr->selected_item)
1578         {
1579             descr->selected_item++;
1580             descr->focus_item = oldfocus; /* focus not changed */
1581         }
1582     }
1583     return LB_OKAY;
1584 }
1585
1586
1587 /***********************************************************************
1588  *           LISTBOX_InsertString
1589  */
1590 static LRESULT LISTBOX_InsertString( LB_DESCR *descr, INT index, LPCWSTR str )
1591 {
1592     LPWSTR new_str = NULL;
1593     ULONG_PTR data = 0;
1594     LRESULT ret;
1595
1596     if (HAS_STRINGS(descr))
1597     {
1598         static const WCHAR empty_stringW[] = { 0 };
1599         if (!str) str = empty_stringW;
1600         if (!(new_str = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) )))
1601         {
1602             SEND_NOTIFICATION( descr, LBN_ERRSPACE );
1603             return LB_ERRSPACE;
1604         }
1605         strcpyW(new_str, str);
1606     }
1607     else data = (ULONG_PTR)str;
1608
1609     if (index == -1) index = descr->nb_items;
1610     if ((ret = LISTBOX_InsertItem( descr, index, new_str, data )) != 0)
1611     {
1612         HeapFree( GetProcessHeap(), 0, new_str );
1613         return ret;
1614     }
1615
1616     TRACE("[%p]: added item %d %s\n",
1617           descr->self, index, HAS_STRINGS(descr) ? debugstr_w(new_str) : "" );
1618     return index;
1619 }
1620
1621
1622 /***********************************************************************
1623  *           LISTBOX_DeleteItem
1624  *
1625  * Delete the content of an item. 'index' must be a valid index.
1626  */
1627 static void LISTBOX_DeleteItem( LB_DESCR *descr, INT index )
1628 {
1629     /* save the item data before it gets freed by LB_RESETCONTENT */
1630     ULONG_PTR item_data = descr->items[index].data;
1631     LPWSTR item_str = descr->items[index].str;
1632
1633     if (!descr->nb_items)
1634         SendMessageW( descr->self, LB_RESETCONTENT, 0, 0 );
1635
1636     /* Note: Win 3.1 only sends DELETEITEM on owner-draw items,
1637      *       while Win95 sends it for all items with user data.
1638      *       It's probably better to send it too often than not
1639      *       often enough, so this is what we do here.
1640      */
1641     if (IS_OWNERDRAW(descr) || item_data)
1642     {
1643         DELETEITEMSTRUCT dis;
1644         UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
1645
1646         dis.CtlType  = ODT_LISTBOX;
1647         dis.CtlID    = id;
1648         dis.itemID   = index;
1649         dis.hwndItem = descr->self;
1650         dis.itemData = item_data;
1651         SendMessageW( descr->owner, WM_DELETEITEM, id, (LPARAM)&dis );
1652     }
1653     if (HAS_STRINGS(descr))
1654         HeapFree( GetProcessHeap(), 0, item_str );
1655 }
1656
1657
1658 /***********************************************************************
1659  *           LISTBOX_RemoveItem
1660  *
1661  * Remove an item from the listbox and delete its content.
1662  */
1663 static LRESULT LISTBOX_RemoveItem( LB_DESCR *descr, INT index )
1664 {
1665     LB_ITEMDATA *item;
1666     INT max_items;
1667
1668     if ((index < 0) || (index >= descr->nb_items)) return LB_ERR;
1669
1670     /* We need to invalidate the original rect instead of the updated one. */
1671     LISTBOX_InvalidateItems( descr, index );
1672
1673     descr->nb_items--;
1674     LISTBOX_DeleteItem( descr, index );
1675
1676     if (!descr->nb_items) return LB_OKAY;
1677
1678     /* Remove the item */
1679
1680     item = &descr->items[index];
1681     if (index < descr->nb_items)
1682         RtlMoveMemory( item, item + 1,
1683                        (descr->nb_items - index) * sizeof(LB_ITEMDATA) );
1684     if (descr->anchor_item == descr->nb_items) descr->anchor_item--;
1685
1686     /* Shrink the item array if possible */
1687
1688     max_items = HeapSize( GetProcessHeap(), 0, descr->items ) / sizeof(LB_ITEMDATA);
1689     if (descr->nb_items < max_items - 2*LB_ARRAY_GRANULARITY)
1690     {
1691         max_items -= LB_ARRAY_GRANULARITY;
1692         item = HeapReAlloc( GetProcessHeap(), 0, descr->items,
1693                             max_items * sizeof(LB_ITEMDATA) );
1694         if (item) descr->items = item;
1695     }
1696     /* Repaint the items */
1697
1698     LISTBOX_UpdateScroll( descr );
1699     /* if we removed the scrollbar, reset the top of the list
1700       (correct for owner-drawn ???) */
1701     if (descr->nb_items == descr->page_size)
1702         LISTBOX_SetTopItem( descr, 0, TRUE );
1703
1704     /* Move selection and focused item */
1705     if (!IS_MULTISELECT(descr))
1706     {
1707         if (index == descr->selected_item)
1708             descr->selected_item = -1;
1709         else if (index < descr->selected_item)
1710         {
1711             descr->selected_item--;
1712             if (ISWIN31) /* win 31 do not change the selected item number */
1713                LISTBOX_SetSelection( descr, descr->selected_item + 1, TRUE, FALSE);
1714         }
1715     }
1716
1717     if (descr->focus_item >= descr->nb_items)
1718     {
1719           descr->focus_item = descr->nb_items - 1;
1720           if (descr->focus_item < 0) descr->focus_item = 0;
1721     }
1722     return LB_OKAY;
1723 }
1724
1725
1726 /***********************************************************************
1727  *           LISTBOX_ResetContent
1728  */
1729 static void LISTBOX_ResetContent( LB_DESCR *descr )
1730 {
1731     INT i;
1732
1733     for(i = descr->nb_items - 1; i>=0; i--) LISTBOX_DeleteItem( descr, i);
1734     HeapFree( GetProcessHeap(), 0, descr->items );
1735     descr->nb_items      = 0;
1736     descr->top_item      = 0;
1737     descr->selected_item = -1;
1738     descr->focus_item    = 0;
1739     descr->anchor_item   = -1;
1740     descr->items         = NULL;
1741 }
1742
1743
1744 /***********************************************************************
1745  *           LISTBOX_SetCount
1746  */
1747 static LRESULT LISTBOX_SetCount( LB_DESCR *descr, INT count )
1748 {
1749     LRESULT ret;
1750
1751     if (HAS_STRINGS(descr))
1752     {
1753         SetLastError(ERROR_SETCOUNT_ON_BAD_LB);
1754         return LB_ERR;
1755     }
1756
1757     /* FIXME: this is far from optimal... */
1758     if (count > descr->nb_items)
1759     {
1760         while (count > descr->nb_items)
1761             if ((ret = LISTBOX_InsertString( descr, -1, 0 )) < 0)
1762                 return ret;
1763     }
1764     else if (count < descr->nb_items)
1765     {
1766         while (count < descr->nb_items)
1767             if ((ret = LISTBOX_RemoveItem( descr, (descr->nb_items - 1) )) < 0)
1768                 return ret;
1769     }
1770
1771     InvalidateRect( descr->self, NULL, TRUE );
1772     return LB_OKAY;
1773 }
1774
1775
1776 /***********************************************************************
1777  *           LISTBOX_Directory
1778  */
1779 static LRESULT LISTBOX_Directory( LB_DESCR *descr, UINT attrib,
1780                                   LPCWSTR filespec, BOOL long_names )
1781 {
1782     HANDLE handle;
1783     LRESULT ret = LB_OKAY;
1784     WIN32_FIND_DATAW entry;
1785     int pos;
1786     LRESULT maxinsert = LB_ERR;
1787
1788     /* don't scan directory if we just want drives exclusively */
1789     if (attrib != (DDL_DRIVES | DDL_EXCLUSIVE)) {
1790         /* scan directory */
1791         if ((handle = FindFirstFileW(filespec, &entry)) == INVALID_HANDLE_VALUE)
1792         {
1793              int le = GetLastError();
1794             if ((le != ERROR_NO_MORE_FILES) && (le != ERROR_FILE_NOT_FOUND)) return LB_ERR;
1795         }
1796         else
1797         {
1798             do
1799             {
1800                 WCHAR buffer[270];
1801                 if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1802                 {
1803                     static const WCHAR bracketW[]  = { ']',0 };
1804                     static const WCHAR dotW[] = { '.',0 };
1805                     if (!(attrib & DDL_DIRECTORY) ||
1806                         !strcmpW( entry.cFileName, dotW )) continue;
1807                     buffer[0] = '[';
1808                     if (!long_names && entry.cAlternateFileName[0])
1809                         strcpyW( buffer + 1, entry.cAlternateFileName );
1810                     else
1811                         strcpyW( buffer + 1, entry.cFileName );
1812                     strcatW(buffer, bracketW);
1813                 }
1814                 else  /* not a directory */
1815                 {
1816 #define ATTRIBS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | \
1817                  FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE)
1818
1819                     if ((attrib & DDL_EXCLUSIVE) &&
1820                         ((attrib & ATTRIBS) != (entry.dwFileAttributes & ATTRIBS)))
1821                         continue;
1822 #undef ATTRIBS
1823                     if (!long_names && entry.cAlternateFileName[0])
1824                         strcpyW( buffer, entry.cAlternateFileName );
1825                     else
1826                         strcpyW( buffer, entry.cFileName );
1827                 }
1828                 if (!long_names) CharLowerW( buffer );
1829                 pos = LISTBOX_FindFileStrPos( descr, buffer );
1830                 if ((ret = LISTBOX_InsertString( descr, pos, buffer )) < 0)
1831                     break;
1832                 if (ret <= maxinsert) maxinsert++; else maxinsert = ret;
1833             } while (FindNextFileW( handle, &entry ));
1834             FindClose( handle );
1835         }
1836     }
1837     if (ret >= 0)
1838     {
1839         ret = maxinsert;
1840
1841         /* scan drives */
1842         if (attrib & DDL_DRIVES)
1843         {
1844             WCHAR buffer[] = {'[','-','a','-',']',0};
1845             WCHAR root[] = {'A',':','\\',0};
1846             int drive;
1847             for (drive = 0; drive < 26; drive++, buffer[2]++, root[0]++)
1848             {
1849                 if (GetDriveTypeW(root) <= DRIVE_NO_ROOT_DIR) continue;
1850                 if ((ret = LISTBOX_InsertString( descr, -1, buffer )) < 0)
1851                     break;
1852             }
1853         }
1854     }
1855     return ret;
1856 }
1857
1858
1859 /***********************************************************************
1860  *           LISTBOX_HandleVScroll
1861  */
1862 static LRESULT LISTBOX_HandleVScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1863 {
1864     SCROLLINFO info;
1865
1866     if (descr->style & LBS_MULTICOLUMN) return 0;
1867     switch(scrollReq)
1868     {
1869     case SB_LINEUP:
1870         LISTBOX_SetTopItem( descr, descr->top_item - 1, TRUE );
1871         break;
1872     case SB_LINEDOWN:
1873         LISTBOX_SetTopItem( descr, descr->top_item + 1, TRUE );
1874         break;
1875     case SB_PAGEUP:
1876         LISTBOX_SetTopItem( descr, descr->top_item -
1877                             LISTBOX_GetCurrentPageSize( descr ), TRUE );
1878         break;
1879     case SB_PAGEDOWN:
1880         LISTBOX_SetTopItem( descr, descr->top_item +
1881                             LISTBOX_GetCurrentPageSize( descr ), TRUE );
1882         break;
1883     case SB_THUMBPOSITION:
1884         LISTBOX_SetTopItem( descr, pos, TRUE );
1885         break;
1886     case SB_THUMBTRACK:
1887         info.cbSize = sizeof(info);
1888         info.fMask = SIF_TRACKPOS;
1889         GetScrollInfo( descr->self, SB_VERT, &info );
1890         LISTBOX_SetTopItem( descr, info.nTrackPos, TRUE );
1891         break;
1892     case SB_TOP:
1893         LISTBOX_SetTopItem( descr, 0, TRUE );
1894         break;
1895     case SB_BOTTOM:
1896         LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1897         break;
1898     }
1899     return 0;
1900 }
1901
1902
1903 /***********************************************************************
1904  *           LISTBOX_HandleHScroll
1905  */
1906 static LRESULT LISTBOX_HandleHScroll( LB_DESCR *descr, WORD scrollReq, WORD pos )
1907 {
1908     SCROLLINFO info;
1909     INT page;
1910
1911     if (descr->style & LBS_MULTICOLUMN)
1912     {
1913         switch(scrollReq)
1914         {
1915         case SB_LINELEFT:
1916             LISTBOX_SetTopItem( descr, descr->top_item-descr->page_size,
1917                                 TRUE );
1918             break;
1919         case SB_LINERIGHT:
1920             LISTBOX_SetTopItem( descr, descr->top_item+descr->page_size,
1921                                 TRUE );
1922             break;
1923         case SB_PAGELEFT:
1924             page = descr->width / descr->column_width;
1925             if (page < 1) page = 1;
1926             LISTBOX_SetTopItem( descr,
1927                              descr->top_item - page * descr->page_size, TRUE );
1928             break;
1929         case SB_PAGERIGHT:
1930             page = descr->width / descr->column_width;
1931             if (page < 1) page = 1;
1932             LISTBOX_SetTopItem( descr,
1933                              descr->top_item + page * descr->page_size, TRUE );
1934             break;
1935         case SB_THUMBPOSITION:
1936             LISTBOX_SetTopItem( descr, pos*descr->page_size, TRUE );
1937             break;
1938         case SB_THUMBTRACK:
1939             info.cbSize = sizeof(info);
1940             info.fMask  = SIF_TRACKPOS;
1941             GetScrollInfo( descr->self, SB_VERT, &info );
1942             LISTBOX_SetTopItem( descr, info.nTrackPos*descr->page_size,
1943                                 TRUE );
1944             break;
1945         case SB_LEFT:
1946             LISTBOX_SetTopItem( descr, 0, TRUE );
1947             break;
1948         case SB_RIGHT:
1949             LISTBOX_SetTopItem( descr, descr->nb_items, TRUE );
1950             break;
1951         }
1952     }
1953     else if (descr->horz_extent)
1954     {
1955         switch(scrollReq)
1956         {
1957         case SB_LINELEFT:
1958             LISTBOX_SetHorizontalPos( descr, descr->horz_pos - 1 );
1959             break;
1960         case SB_LINERIGHT:
1961             LISTBOX_SetHorizontalPos( descr, descr->horz_pos + 1 );
1962             break;
1963         case SB_PAGELEFT:
1964             LISTBOX_SetHorizontalPos( descr,
1965                                       descr->horz_pos - descr->width );
1966             break;
1967         case SB_PAGERIGHT:
1968             LISTBOX_SetHorizontalPos( descr,
1969                                       descr->horz_pos + descr->width );
1970             break;
1971         case SB_THUMBPOSITION:
1972             LISTBOX_SetHorizontalPos( descr, pos );
1973             break;
1974         case SB_THUMBTRACK:
1975             info.cbSize = sizeof(info);
1976             info.fMask = SIF_TRACKPOS;
1977             GetScrollInfo( descr->self, SB_HORZ, &info );
1978             LISTBOX_SetHorizontalPos( descr, info.nTrackPos );
1979             break;
1980         case SB_LEFT:
1981             LISTBOX_SetHorizontalPos( descr, 0 );
1982             break;
1983         case SB_RIGHT:
1984             LISTBOX_SetHorizontalPos( descr,
1985                                       descr->horz_extent - descr->width );
1986             break;
1987         }
1988     }
1989     return 0;
1990 }
1991
1992 static LRESULT LISTBOX_HandleMouseWheel(LB_DESCR *descr, SHORT delta )
1993 {
1994     UINT pulScrollLines = 3;
1995
1996     SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0);
1997
1998     /* if scrolling changes direction, ignore left overs */
1999     if ((delta < 0 && descr->wheel_remain < 0) ||
2000         (delta > 0 && descr->wheel_remain > 0))
2001         descr->wheel_remain += delta;
2002     else
2003         descr->wheel_remain = delta;
2004
2005     if (descr->wheel_remain && pulScrollLines)
2006     {
2007         int cLineScroll;
2008         pulScrollLines = min((UINT) descr->page_size, pulScrollLines);
2009         cLineScroll = pulScrollLines * (float)descr->wheel_remain / WHEEL_DELTA;
2010         descr->wheel_remain -= WHEEL_DELTA * cLineScroll / (int)pulScrollLines;
2011         LISTBOX_SetTopItem( descr, descr->top_item - cLineScroll, TRUE );
2012     }
2013     return 0;
2014 }
2015
2016 /***********************************************************************
2017  *           LISTBOX_HandleLButtonDown
2018  */
2019 static LRESULT LISTBOX_HandleLButtonDown( LB_DESCR *descr, DWORD keys, INT x, INT y )
2020 {
2021     INT index = LISTBOX_GetItemFromPoint( descr, x, y );
2022
2023     TRACE("[%p]: lbuttondown %d,%d item %d, focus item %d\n",
2024           descr->self, x, y, index, descr->focus_item);
2025
2026     if (!descr->caret_on && (descr->in_focus)) return 0;
2027
2028     if (!descr->in_focus)
2029     {
2030         if( !descr->lphc ) SetFocus( descr->self );
2031         else SetFocus( (descr->lphc->hWndEdit) ? descr->lphc->hWndEdit : descr->lphc->self );
2032     }
2033
2034     if (index == -1) return 0;
2035
2036     if (!descr->lphc)
2037     {
2038         if (descr->style & LBS_NOTIFY )
2039             SendMessageW( descr->owner, WM_LBTRACKPOINT, index,
2040                             MAKELPARAM( x, y ) );
2041     }
2042
2043     descr->captured = TRUE;
2044     SetCapture( descr->self );
2045
2046     if (descr->style & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL))
2047     {
2048         /* we should perhaps make sure that all items are deselected
2049            FIXME: needed for !LBS_EXTENDEDSEL, too ?
2050            if (!(keys & (MK_SHIFT|MK_CONTROL)))
2051            LISTBOX_SetSelection( descr, -1, FALSE, FALSE);
2052         */
2053
2054         if (!(keys & MK_SHIFT)) descr->anchor_item = index;
2055         if (keys & MK_CONTROL)
2056         {
2057             LISTBOX_SetCaretIndex( descr, index, FALSE );
2058             LISTBOX_SetSelection( descr, index,
2059                                   !descr->items[index].selected,
2060                                   (descr->style & LBS_NOTIFY) != 0);
2061         }
2062         else
2063         {
2064             LISTBOX_MoveCaret( descr, index, FALSE );
2065
2066             if (descr->style & LBS_EXTENDEDSEL)
2067             {
2068                 LISTBOX_SetSelection( descr, index,
2069                                descr->items[index].selected,
2070                               (descr->style & LBS_NOTIFY) != 0 );
2071             }
2072             else
2073             {
2074                 LISTBOX_SetSelection( descr, index,
2075                                !descr->items[index].selected,
2076                               (descr->style & LBS_NOTIFY) != 0 );
2077             }
2078         }
2079     }
2080     else
2081     {
2082         descr->anchor_item = index;
2083         LISTBOX_MoveCaret( descr, index, FALSE );
2084         LISTBOX_SetSelection( descr, index,
2085                               TRUE, (descr->style & LBS_NOTIFY) != 0 );
2086     }
2087
2088     if (!descr->lphc)
2089     {
2090         if (GetWindowLongW( descr->self, GWL_EXSTYLE ) & WS_EX_DRAGDETECT)
2091         {
2092             POINT pt;
2093
2094             pt.x = x;
2095             pt.y = y;
2096
2097             if (DragDetect( descr->self, pt ))
2098                 SendMessageW( descr->owner, WM_BEGINDRAG, 0, 0 );
2099         }
2100     }
2101     return 0;
2102 }
2103
2104
2105 /*************************************************************************
2106  * LISTBOX_HandleLButtonDownCombo [Internal]
2107  *
2108  * Process LButtonDown message for the ComboListBox
2109  *
2110  * PARAMS
2111  *     pWnd       [I] The windows internal structure
2112  *     pDescr     [I] The ListBox internal structure
2113  *     keys       [I] Key Flag (WM_LBUTTONDOWN doc for more info)
2114  *     x          [I] X Mouse Coordinate
2115  *     y          [I] Y Mouse Coordinate
2116  *
2117  * RETURNS
2118  *     0 since we are processing the WM_LBUTTONDOWN Message
2119  *
2120  * NOTES
2121  *  This function is only to be used when a ListBox is a ComboListBox
2122  */
2123
2124 static LRESULT LISTBOX_HandleLButtonDownCombo( LB_DESCR *descr, UINT msg, DWORD keys, INT x, INT y)
2125 {
2126     RECT clientRect, screenRect;
2127     POINT mousePos;
2128
2129     mousePos.x = x;
2130     mousePos.y = y;
2131
2132     GetClientRect(descr->self, &clientRect);
2133
2134     if(PtInRect(&clientRect, mousePos))
2135     {
2136        /* MousePos is in client, resume normal processing */
2137         if (msg == WM_LBUTTONDOWN)
2138         {
2139            descr->lphc->droppedIndex = descr->nb_items ? descr->selected_item : -1;
2140            return LISTBOX_HandleLButtonDown( descr, keys, x, y);
2141         }
2142         else if (descr->style & LBS_NOTIFY)
2143             SEND_NOTIFICATION( descr, LBN_DBLCLK );
2144     }
2145     else
2146     {
2147         POINT screenMousePos;
2148         HWND hWndOldCapture;
2149
2150         /* Check the Non-Client Area */
2151         screenMousePos = mousePos;
2152         hWndOldCapture = GetCapture();
2153         ReleaseCapture();
2154         GetWindowRect(descr->self, &screenRect);
2155         ClientToScreen(descr->self, &screenMousePos);
2156
2157         if(!PtInRect(&screenRect, screenMousePos))
2158         {
2159             LISTBOX_SetCaretIndex( descr, descr->lphc->droppedIndex, FALSE );
2160             LISTBOX_SetSelection( descr, descr->lphc->droppedIndex, FALSE, FALSE );
2161             COMBO_FlipListbox( descr->lphc, FALSE, FALSE );
2162         }
2163         else
2164         {
2165             /* Check to see the NC is a scrollbar */
2166             INT nHitTestType=0;
2167             LONG style = GetWindowLongW( descr->self, GWL_STYLE );
2168             /* Check Vertical scroll bar */
2169             if (style & WS_VSCROLL)
2170             {
2171                 clientRect.right += GetSystemMetrics(SM_CXVSCROLL);
2172                 if (PtInRect( &clientRect, mousePos ))
2173                     nHitTestType = HTVSCROLL;
2174             }
2175               /* Check horizontal scroll bar */
2176             if (style & WS_HSCROLL)
2177             {
2178                 clientRect.bottom += GetSystemMetrics(SM_CYHSCROLL);
2179                 if (PtInRect( &clientRect, mousePos ))
2180                     nHitTestType = HTHSCROLL;
2181             }
2182             /* Windows sends this message when a scrollbar is clicked
2183              */
2184
2185             if(nHitTestType != 0)
2186             {
2187                 SendMessageW(descr->self, WM_NCLBUTTONDOWN, nHitTestType,
2188                              MAKELONG(screenMousePos.x, screenMousePos.y));
2189             }
2190             /* Resume the Capture after scrolling is complete
2191              */
2192             if(hWndOldCapture != 0)
2193                 SetCapture(hWndOldCapture);
2194         }
2195     }
2196     return 0;
2197 }
2198
2199 /***********************************************************************
2200  *           LISTBOX_HandleLButtonUp
2201  */
2202 static LRESULT LISTBOX_HandleLButtonUp( LB_DESCR *descr )
2203 {
2204     if (LISTBOX_Timer != LB_TIMER_NONE)
2205         KillSystemTimer( descr->self, LB_TIMER_ID );
2206     LISTBOX_Timer = LB_TIMER_NONE;
2207     if (descr->captured)
2208     {
2209         descr->captured = FALSE;
2210         if (GetCapture() == descr->self) ReleaseCapture();
2211         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2212             SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2213     }
2214     return 0;
2215 }
2216
2217
2218 /***********************************************************************
2219  *           LISTBOX_HandleTimer
2220  *
2221  * Handle scrolling upon a timer event.
2222  * Return TRUE if scrolling should continue.
2223  */
2224 static LRESULT LISTBOX_HandleTimer( LB_DESCR *descr, INT index, TIMER_DIRECTION dir )
2225 {
2226     switch(dir)
2227     {
2228     case LB_TIMER_UP:
2229         if (descr->top_item) index = descr->top_item - 1;
2230         else index = 0;
2231         break;
2232     case LB_TIMER_LEFT:
2233         if (descr->top_item) index -= descr->page_size;
2234         break;
2235     case LB_TIMER_DOWN:
2236         index = descr->top_item + LISTBOX_GetCurrentPageSize( descr );
2237         if (index == descr->focus_item) index++;
2238         if (index >= descr->nb_items) index = descr->nb_items - 1;
2239         break;
2240     case LB_TIMER_RIGHT:
2241         if (index + descr->page_size < descr->nb_items)
2242             index += descr->page_size;
2243         break;
2244     case LB_TIMER_NONE:
2245         break;
2246     }
2247     if (index == descr->focus_item) return FALSE;
2248     LISTBOX_MoveCaret( descr, index, FALSE );
2249     return TRUE;
2250 }
2251
2252
2253 /***********************************************************************
2254  *           LISTBOX_HandleSystemTimer
2255  *
2256  * WM_SYSTIMER handler.
2257  */
2258 static LRESULT LISTBOX_HandleSystemTimer( LB_DESCR *descr )
2259 {
2260     if (!LISTBOX_HandleTimer( descr, descr->focus_item, LISTBOX_Timer ))
2261     {
2262         KillSystemTimer( descr->self, LB_TIMER_ID );
2263         LISTBOX_Timer = LB_TIMER_NONE;
2264     }
2265     return 0;
2266 }
2267
2268
2269 /***********************************************************************
2270  *           LISTBOX_HandleMouseMove
2271  *
2272  * WM_MOUSEMOVE handler.
2273  */
2274 static void LISTBOX_HandleMouseMove( LB_DESCR *descr,
2275                                      INT x, INT y )
2276 {
2277     INT index;
2278     TIMER_DIRECTION dir = LB_TIMER_NONE;
2279
2280     if (!descr->captured) return;
2281
2282     if (descr->style & LBS_MULTICOLUMN)
2283     {
2284         if (y < 0) y = 0;
2285         else if (y >= descr->item_height * descr->page_size)
2286             y = descr->item_height * descr->page_size - 1;
2287
2288         if (x < 0)
2289         {
2290             dir = LB_TIMER_LEFT;
2291             x = 0;
2292         }
2293         else if (x >= descr->width)
2294         {
2295             dir = LB_TIMER_RIGHT;
2296             x = descr->width - 1;
2297         }
2298     }
2299     else
2300     {
2301         if (y < 0) dir = LB_TIMER_UP;  /* above */
2302         else if (y >= descr->height) dir = LB_TIMER_DOWN;  /* below */
2303     }
2304
2305     index = LISTBOX_GetItemFromPoint( descr, x, y );
2306     if (index == -1) index = descr->focus_item;
2307     if (!LISTBOX_HandleTimer( descr, index, dir )) dir = LB_TIMER_NONE;
2308
2309     /* Start/stop the system timer */
2310
2311     if (dir != LB_TIMER_NONE)
2312         SetSystemTimer( descr->self, LB_TIMER_ID, LB_SCROLL_TIMEOUT, NULL);
2313     else if (LISTBOX_Timer != LB_TIMER_NONE)
2314         KillSystemTimer( descr->self, LB_TIMER_ID );
2315     LISTBOX_Timer = dir;
2316 }
2317
2318
2319 /***********************************************************************
2320  *           LISTBOX_HandleKeyDown
2321  */
2322 static LRESULT LISTBOX_HandleKeyDown( LB_DESCR *descr, DWORD key )
2323 {
2324     INT caret = -1;
2325     BOOL bForceSelection = TRUE; /* select item pointed to by focus_item */
2326     if ((IS_MULTISELECT(descr)) || (descr->selected_item == descr->focus_item))
2327         bForceSelection = FALSE; /* only for single select list */
2328
2329     if (descr->style & LBS_WANTKEYBOARDINPUT)
2330     {
2331         caret = SendMessageW( descr->owner, WM_VKEYTOITEM,
2332                                 MAKEWPARAM(LOWORD(key), descr->focus_item),
2333                                 (LPARAM)descr->self );
2334         if (caret == -2) return 0;
2335     }
2336     if (caret == -1) switch(key)
2337     {
2338     case VK_LEFT:
2339         if (descr->style & LBS_MULTICOLUMN)
2340         {
2341             bForceSelection = FALSE;
2342             if (descr->focus_item >= descr->page_size)
2343                 caret = descr->focus_item - descr->page_size;
2344             break;
2345         }
2346         /* fall through */
2347     case VK_UP:
2348         caret = descr->focus_item - 1;
2349         if (caret < 0) caret = 0;
2350         break;
2351     case VK_RIGHT:
2352         if (descr->style & LBS_MULTICOLUMN)
2353         {
2354             bForceSelection = FALSE;
2355             if (descr->focus_item + descr->page_size < descr->nb_items)
2356                 caret = descr->focus_item + descr->page_size;
2357             break;
2358         }
2359         /* fall through */
2360     case VK_DOWN:
2361         caret = descr->focus_item + 1;
2362         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2363         break;
2364
2365     case VK_PRIOR:
2366         if (descr->style & LBS_MULTICOLUMN)
2367         {
2368             INT page = descr->width / descr->column_width;
2369             if (page < 1) page = 1;
2370             caret = descr->focus_item - (page * descr->page_size) + 1;
2371         }
2372         else caret = descr->focus_item-LISTBOX_GetCurrentPageSize(descr) + 1;
2373         if (caret < 0) caret = 0;
2374         break;
2375     case VK_NEXT:
2376         if (descr->style & LBS_MULTICOLUMN)
2377         {
2378             INT page = descr->width / descr->column_width;
2379             if (page < 1) page = 1;
2380             caret = descr->focus_item + (page * descr->page_size) - 1;
2381         }
2382         else caret = descr->focus_item + LISTBOX_GetCurrentPageSize(descr) - 1;
2383         if (caret >= descr->nb_items) caret = descr->nb_items - 1;
2384         break;
2385     case VK_HOME:
2386         caret = 0;
2387         break;
2388     case VK_END:
2389         caret = descr->nb_items - 1;
2390         break;
2391     case VK_SPACE:
2392         if (descr->style & LBS_EXTENDEDSEL) caret = descr->focus_item;
2393         else if (descr->style & LBS_MULTIPLESEL)
2394         {
2395             LISTBOX_SetSelection( descr, descr->focus_item,
2396                                   !descr->items[descr->focus_item].selected,
2397                                   (descr->style & LBS_NOTIFY) != 0 );
2398         }
2399         break;
2400     default:
2401         bForceSelection = FALSE;
2402     }
2403     if (bForceSelection) /* focused item is used instead of key */
2404         caret = descr->focus_item;
2405     if (caret >= 0)
2406     {
2407         if (((descr->style & LBS_EXTENDEDSEL) &&
2408             !(GetKeyState( VK_SHIFT ) & 0x8000)) ||
2409             !IS_MULTISELECT(descr))
2410             descr->anchor_item = caret;
2411         LISTBOX_MoveCaret( descr, caret, TRUE );
2412
2413         if (descr->style & LBS_MULTIPLESEL)
2414             descr->selected_item = caret;
2415         else
2416             LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2417         if (descr->style & LBS_NOTIFY)
2418         {
2419             if (descr->lphc && IsWindowVisible( descr->self ))
2420             {
2421                 /* make sure that combo parent doesn't hide us */
2422                 descr->lphc->wState |= CBF_NOROLLUP;
2423             }
2424             if (descr->nb_items) SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2425         }
2426     }
2427     return 0;
2428 }
2429
2430
2431 /***********************************************************************
2432  *           LISTBOX_HandleChar
2433  */
2434 static LRESULT LISTBOX_HandleChar( LB_DESCR *descr, WCHAR charW )
2435 {
2436     INT caret = -1;
2437     WCHAR str[2];
2438
2439     str[0] = charW;
2440     str[1] = '\0';
2441
2442     if (descr->style & LBS_WANTKEYBOARDINPUT)
2443     {
2444         caret = SendMessageW( descr->owner, WM_CHARTOITEM,
2445                                 MAKEWPARAM(charW, descr->focus_item),
2446                                 (LPARAM)descr->self );
2447         if (caret == -2) return 0;
2448     }
2449     if (caret == -1)
2450         caret = LISTBOX_FindString( descr, descr->focus_item, str, FALSE);
2451     if (caret != -1)
2452     {
2453         if ((!IS_MULTISELECT(descr)) && descr->selected_item == -1)
2454            LISTBOX_SetSelection( descr, caret, TRUE, FALSE);
2455         LISTBOX_MoveCaret( descr, caret, TRUE );
2456         if ((descr->style & LBS_NOTIFY) && descr->nb_items)
2457             SEND_NOTIFICATION( descr, LBN_SELCHANGE );
2458     }
2459     return 0;
2460 }
2461
2462
2463 /***********************************************************************
2464  *           LISTBOX_Create
2465  */
2466 static BOOL LISTBOX_Create( HWND hwnd, LPHEADCOMBO lphc )
2467 {
2468     LB_DESCR *descr;
2469     MEASUREITEMSTRUCT mis;
2470     RECT rect;
2471
2472     if (!(descr = HeapAlloc( GetProcessHeap(), 0, sizeof(*descr) )))
2473         return FALSE;
2474
2475     GetClientRect( hwnd, &rect );
2476     descr->self          = hwnd;
2477     descr->owner         = GetParent( descr->self );
2478     descr->style         = GetWindowLongW( descr->self, GWL_STYLE );
2479     descr->width         = rect.right - rect.left;
2480     descr->height        = rect.bottom - rect.top;
2481     descr->items         = NULL;
2482     descr->nb_items      = 0;
2483     descr->top_item      = 0;
2484     descr->selected_item = -1;
2485     descr->focus_item    = 0;
2486     descr->anchor_item   = -1;
2487     descr->item_height   = 1;
2488     descr->page_size     = 1;
2489     descr->column_width  = 150;
2490     descr->horz_extent   = (descr->style & WS_HSCROLL) ? 1 : 0;
2491     descr->horz_pos      = 0;
2492     descr->nb_tabs       = 0;
2493     descr->tabs          = NULL;
2494     descr->wheel_remain  = 0;
2495     descr->caret_on      = !lphc;
2496     if (descr->style & LBS_NOSEL) descr->caret_on = FALSE;
2497     descr->in_focus      = FALSE;
2498     descr->captured      = FALSE;
2499     descr->font          = 0;
2500     descr->locale        = GetUserDefaultLCID();
2501     descr->lphc          = lphc;
2502
2503     if( lphc )
2504     {
2505         TRACE("[%p]: resetting owner %p -> %p\n", descr->self, descr->owner, lphc->self );
2506         descr->owner = lphc->self;
2507     }
2508
2509     SetWindowLongPtrW( descr->self, 0, (LONG_PTR)descr );
2510
2511 /*    if (wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) descr->style &= ~LBS_NOTIFY;
2512  */
2513     if (descr->style & LBS_EXTENDEDSEL) descr->style |= LBS_MULTIPLESEL;
2514     if (descr->style & LBS_MULTICOLUMN) descr->style &= ~LBS_OWNERDRAWVARIABLE;
2515     if (descr->style & LBS_OWNERDRAWVARIABLE) descr->style |= LBS_NOINTEGRALHEIGHT;
2516     descr->item_height = LISTBOX_SetFont( descr, 0 );
2517
2518     if (descr->style & LBS_OWNERDRAWFIXED)
2519     {
2520         if( descr->lphc && (descr->lphc->dwStyle & CBS_DROPDOWN))
2521         {
2522             /* WinWord gets VERY unhappy if we send WM_MEASUREITEM from here */
2523           descr->item_height = lphc->fixedOwnerDrawHeight;
2524         }
2525         else
2526         {
2527             UINT id = (UINT)GetWindowLongPtrW( descr->self, GWLP_ID );
2528             mis.CtlType    = ODT_LISTBOX;
2529             mis.CtlID      = id;
2530             mis.itemID     = -1;
2531             mis.itemWidth  =  0;
2532             mis.itemData   =  0;
2533             mis.itemHeight = descr->item_height;
2534             SendMessageW( descr->owner, WM_MEASUREITEM, id, (LPARAM)&mis );
2535             descr->item_height = mis.itemHeight ? mis.itemHeight : 1;
2536         }
2537     }
2538
2539     TRACE("owner: %p, style: %08x, width: %d, height: %d\n", descr->owner, descr->style, descr->width, descr->height);
2540     return TRUE;
2541 }
2542
2543
2544 /***********************************************************************
2545  *           LISTBOX_Destroy
2546  */
2547 static BOOL LISTBOX_Destroy( LB_DESCR *descr )
2548 {
2549     LISTBOX_ResetContent( descr );
2550     SetWindowLongPtrW( descr->self, 0, 0 );
2551     HeapFree( GetProcessHeap(), 0, descr );
2552     return TRUE;
2553 }
2554
2555
2556 /***********************************************************************
2557  *           ListBoxWndProc_common
2558  */
2559 LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode )
2560 {
2561     LB_DESCR *descr = (LB_DESCR *)GetWindowLongPtrW( hwnd, 0 );
2562     LPHEADCOMBO lphc = 0;
2563     LRESULT ret;
2564
2565     if (!descr)
2566     {
2567         if (!IsWindow(hwnd)) return 0;
2568
2569         if (msg == WM_CREATE)
2570         {
2571             CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam;
2572             if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams;
2573             if (!LISTBOX_Create( hwnd, lphc )) return -1;
2574             TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) );
2575             return 0;
2576         }
2577         /* Ignore all other messages before we get a WM_CREATE */
2578         return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
2579                          DefWindowProcA( hwnd, msg, wParam, lParam );
2580     }
2581     if (descr->style & LBS_COMBOBOX) lphc = descr->lphc;
2582
2583     TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
2584           descr->self, SPY_GetMsgName(msg, descr->self), wParam, lParam );
2585
2586     switch(msg)
2587     {
2588     case LB_RESETCONTENT:
2589         LISTBOX_ResetContent( descr );
2590         LISTBOX_UpdateScroll( descr );
2591         InvalidateRect( descr->self, NULL, TRUE );
2592         return 0;
2593
2594     case LB_ADDSTRING:
2595     {
2596         INT ret;
2597         LPWSTR textW;
2598         if(unicode || !HAS_STRINGS(descr))
2599             textW = (LPWSTR)lParam;
2600         else
2601         {
2602             LPSTR textA = (LPSTR)lParam;
2603             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2604             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2605                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2606             else
2607                 return LB_ERRSPACE;
2608         }
2609         wParam = LISTBOX_FindStringPos( descr, textW, FALSE );
2610         ret = LISTBOX_InsertString( descr, wParam, textW );
2611         if (!unicode && HAS_STRINGS(descr))
2612             HeapFree(GetProcessHeap(), 0, textW);
2613         return ret;
2614     }
2615
2616     case LB_INSERTSTRING:
2617     {
2618         INT ret;
2619         LPWSTR textW;
2620         if(unicode || !HAS_STRINGS(descr))
2621             textW = (LPWSTR)lParam;
2622         else
2623         {
2624             LPSTR textA = (LPSTR)lParam;
2625             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2626             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2627                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2628             else
2629                 return LB_ERRSPACE;
2630         }
2631         ret = LISTBOX_InsertString( descr, wParam, textW );
2632         if(!unicode && HAS_STRINGS(descr))
2633             HeapFree(GetProcessHeap(), 0, textW);
2634         return ret;
2635     }
2636
2637     case LB_ADDFILE:
2638     {
2639         INT ret;
2640         LPWSTR textW;
2641         if(unicode || !HAS_STRINGS(descr))
2642             textW = (LPWSTR)lParam;
2643         else
2644         {
2645             LPSTR textA = (LPSTR)lParam;
2646             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2647             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2648                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2649             else
2650                 return LB_ERRSPACE;
2651         }
2652         wParam = LISTBOX_FindFileStrPos( descr, textW );
2653         ret = LISTBOX_InsertString( descr, wParam, textW );
2654         if(!unicode && HAS_STRINGS(descr))
2655             HeapFree(GetProcessHeap(), 0, textW);
2656         return ret;
2657     }
2658
2659     case LB_DELETESTRING:
2660         if (LISTBOX_RemoveItem( descr, wParam) != LB_ERR)
2661             return descr->nb_items;
2662         else
2663         {
2664             SetLastError(ERROR_INVALID_INDEX);
2665             return LB_ERR;
2666         }
2667
2668     case LB_GETITEMDATA:
2669         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2670         {
2671             SetLastError(ERROR_INVALID_INDEX);
2672             return LB_ERR;
2673         }
2674         return descr->items[wParam].data;
2675
2676     case LB_SETITEMDATA:
2677         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2678         {
2679             SetLastError(ERROR_INVALID_INDEX);
2680             return LB_ERR;
2681         }
2682         descr->items[wParam].data = lParam;
2683         /* undocumented: returns TRUE, not LB_OKAY (0) */
2684         return TRUE;
2685
2686     case LB_GETCOUNT:
2687         return descr->nb_items;
2688
2689     case LB_GETTEXT:
2690         return LISTBOX_GetText( descr, wParam, (LPWSTR)lParam, unicode );
2691
2692     case LB_GETTEXTLEN:
2693         if ((INT)wParam >= descr->nb_items || (INT)wParam < 0)
2694         {
2695             SetLastError(ERROR_INVALID_INDEX);
2696             return LB_ERR;
2697         }
2698         if (!HAS_STRINGS(descr)) return sizeof(DWORD);
2699         if (unicode) return strlenW( descr->items[wParam].str );
2700         return WideCharToMultiByte( CP_ACP, 0, descr->items[wParam].str,
2701                                     strlenW(descr->items[wParam].str), NULL, 0, NULL, NULL );
2702
2703     case LB_GETCURSEL:
2704         if (descr->nb_items == 0)
2705             return LB_ERR;
2706         if (!IS_MULTISELECT(descr))
2707             return descr->selected_item;
2708         if (descr->selected_item != -1)
2709             return descr->selected_item;
2710         return descr->focus_item;
2711         /* otherwise, if the user tries to move the selection with the    */
2712         /* arrow keys, we will give the application something to choke on */
2713     case LB_GETTOPINDEX:
2714         return descr->top_item;
2715
2716     case LB_GETITEMHEIGHT:
2717         return LISTBOX_GetItemHeight( descr, wParam );
2718
2719     case LB_SETITEMHEIGHT:
2720         return LISTBOX_SetItemHeight( descr, wParam, lParam, TRUE );
2721
2722     case LB_ITEMFROMPOINT:
2723         {
2724             POINT pt;
2725             RECT rect;
2726             int index;
2727             BOOL hit = TRUE;
2728
2729             /* The hiword of the return value is not a client area
2730                hittest as suggested by MSDN, but rather a hittest on
2731                the returned listbox item. */
2732
2733             if(descr->nb_items == 0)
2734                 return 0x1ffff;      /* win9x returns 0x10000, we copy winnt */
2735
2736             pt.x = (short)LOWORD(lParam);
2737             pt.y = (short)HIWORD(lParam);
2738
2739             SetRect(&rect, 0, 0, descr->width, descr->height);
2740
2741             if(!PtInRect(&rect, pt))
2742             {
2743                 pt.x = min(pt.x, rect.right - 1);
2744                 pt.x = max(pt.x, 0);
2745                 pt.y = min(pt.y, rect.bottom - 1);
2746                 pt.y = max(pt.y, 0);
2747                 hit = FALSE;
2748             }
2749
2750             index = LISTBOX_GetItemFromPoint(descr, pt.x, pt.y);
2751
2752             if(index == -1)
2753             {
2754                 index = descr->nb_items - 1;
2755                 hit = FALSE;
2756             }
2757             return MAKELONG(index, hit ? 0 : 1);
2758         }
2759
2760     case LB_SETCARETINDEX:
2761         if ((!IS_MULTISELECT(descr)) && (descr->selected_item != -1)) return LB_ERR;
2762         if (LISTBOX_SetCaretIndex( descr, wParam, !lParam ) == LB_ERR)
2763             return LB_ERR;
2764         else if (ISWIN31)
2765              return wParam;
2766         else
2767              return LB_OKAY;
2768
2769     case LB_GETCARETINDEX:
2770         return descr->focus_item;
2771
2772     case LB_SETTOPINDEX:
2773         return LISTBOX_SetTopItem( descr, wParam, TRUE );
2774
2775     case LB_SETCOLUMNWIDTH:
2776         return LISTBOX_SetColumnWidth( descr, wParam );
2777
2778     case LB_GETITEMRECT:
2779         return LISTBOX_GetItemRect( descr, wParam, (RECT *)lParam );
2780
2781     case LB_FINDSTRING:
2782     {
2783         INT ret;
2784         LPWSTR textW;
2785         if(unicode || !HAS_STRINGS(descr))
2786             textW = (LPWSTR)lParam;
2787         else
2788         {
2789             LPSTR textA = (LPSTR)lParam;
2790             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2791             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2792                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2793         }
2794         ret = LISTBOX_FindString( descr, wParam, textW, FALSE );
2795         if(!unicode && HAS_STRINGS(descr))
2796             HeapFree(GetProcessHeap(), 0, textW);
2797         return ret;
2798     }
2799
2800     case LB_FINDSTRINGEXACT:
2801     {
2802         INT ret;
2803         LPWSTR textW;
2804         if(unicode || !HAS_STRINGS(descr))
2805             textW = (LPWSTR)lParam;
2806         else
2807         {
2808             LPSTR textA = (LPSTR)lParam;
2809             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2810             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2811                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2812         }
2813         ret = LISTBOX_FindString( descr, wParam, textW, TRUE );
2814         if(!unicode && HAS_STRINGS(descr))
2815             HeapFree(GetProcessHeap(), 0, textW);
2816         return ret;
2817     }
2818
2819     case LB_SELECTSTRING:
2820     {
2821         INT index;
2822         LPWSTR textW;
2823
2824         if(HAS_STRINGS(descr))
2825             TRACE("LB_SELECTSTRING: %s\n", unicode ? debugstr_w((LPWSTR)lParam) :
2826                                                      debugstr_a((LPSTR)lParam));
2827         if(unicode || !HAS_STRINGS(descr))
2828             textW = (LPWSTR)lParam;
2829         else
2830         {
2831             LPSTR textA = (LPSTR)lParam;
2832             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2833             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2834                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2835         }
2836         index = LISTBOX_FindString( descr, wParam, textW, FALSE );
2837         if(!unicode && HAS_STRINGS(descr))
2838             HeapFree(GetProcessHeap(), 0, textW);
2839         if (index != LB_ERR)
2840         {
2841             LISTBOX_MoveCaret( descr, index, TRUE );
2842             LISTBOX_SetSelection( descr, index, TRUE, FALSE );
2843         }
2844         return index;
2845     }
2846
2847     case LB_GETSEL:
2848         if (((INT)wParam < 0) || ((INT)wParam >= descr->nb_items))
2849             return LB_ERR;
2850         return descr->items[wParam].selected;
2851
2852     case LB_SETSEL:
2853         return LISTBOX_SetSelection( descr, lParam, wParam, FALSE );
2854
2855     case LB_SETCURSEL:
2856         if (IS_MULTISELECT(descr)) return LB_ERR;
2857         LISTBOX_SetCaretIndex( descr, wParam, FALSE );
2858         ret = LISTBOX_SetSelection( descr, wParam, TRUE, FALSE );
2859         if (ret != LB_ERR) ret = descr->selected_item;
2860         return ret;
2861
2862     case LB_GETSELCOUNT:
2863         return LISTBOX_GetSelCount( descr );
2864
2865     case LB_GETSELITEMS:
2866         return LISTBOX_GetSelItems( descr, wParam, (LPINT)lParam );
2867
2868     case LB_SELITEMRANGE:
2869         if (LOWORD(lParam) <= HIWORD(lParam))
2870             return LISTBOX_SelectItemRange( descr, LOWORD(lParam),
2871                                             HIWORD(lParam), wParam );
2872         else
2873             return LISTBOX_SelectItemRange( descr, HIWORD(lParam),
2874                                             LOWORD(lParam), wParam );
2875
2876     case LB_SELITEMRANGEEX:
2877         if ((INT)lParam >= (INT)wParam)
2878             return LISTBOX_SelectItemRange( descr, wParam, lParam, TRUE );
2879         else
2880             return LISTBOX_SelectItemRange( descr, lParam, wParam, FALSE);
2881
2882     case LB_GETHORIZONTALEXTENT:
2883         return descr->horz_extent;
2884
2885     case LB_SETHORIZONTALEXTENT:
2886         return LISTBOX_SetHorizontalExtent( descr, wParam );
2887
2888     case LB_GETANCHORINDEX:
2889         return descr->anchor_item;
2890
2891     case LB_SETANCHORINDEX:
2892         if (((INT)wParam < -1) || ((INT)wParam >= descr->nb_items))
2893         {
2894             SetLastError(ERROR_INVALID_INDEX);
2895             return LB_ERR;
2896         }
2897         descr->anchor_item = (INT)wParam;
2898         return LB_OKAY;
2899
2900     case LB_DIR:
2901     {
2902         INT ret;
2903         LPWSTR textW;
2904         if(unicode)
2905             textW = (LPWSTR)lParam;
2906         else
2907         {
2908             LPSTR textA = (LPSTR)lParam;
2909             INT countW = MultiByteToWideChar(CP_ACP, 0, textA, -1, NULL, 0);
2910             if((textW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
2911                 MultiByteToWideChar(CP_ACP, 0, textA, -1, textW, countW);
2912         }
2913         ret = LISTBOX_Directory( descr, wParam, textW, msg == LB_DIR );
2914         if(!unicode)
2915             HeapFree(GetProcessHeap(), 0, textW);
2916         return ret;
2917     }
2918
2919     case LB_GETLOCALE:
2920         return descr->locale;
2921
2922     case LB_SETLOCALE:
2923     {
2924         LCID ret;
2925         if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
2926             return LB_ERR;
2927         ret = descr->locale;
2928         descr->locale = (LCID)wParam;
2929         return ret;
2930     }
2931
2932     case LB_INITSTORAGE:
2933         return LISTBOX_InitStorage( descr, wParam );
2934
2935     case LB_SETCOUNT:
2936         return LISTBOX_SetCount( descr, (INT)wParam );
2937
2938     case LB_SETTABSTOPS:
2939         return LISTBOX_SetTabStops( descr, wParam, (LPINT)lParam );
2940
2941     case LB_CARETON:
2942         if (descr->caret_on)
2943             return LB_OKAY;
2944         descr->caret_on = TRUE;
2945         if ((descr->focus_item != -1) && (descr->in_focus))
2946             LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2947         return LB_OKAY;
2948
2949     case LB_CARETOFF:
2950         if (!descr->caret_on)
2951             return LB_OKAY;
2952         descr->caret_on = FALSE;
2953         if ((descr->focus_item != -1) && (descr->in_focus))
2954             LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
2955         return LB_OKAY;
2956
2957     case LB_GETLISTBOXINFO:
2958         FIXME("LB_GETLISTBOXINFO: stub!\n");
2959         return 0;
2960
2961     case WM_DESTROY:
2962         return LISTBOX_Destroy( descr );
2963
2964     case WM_ENABLE:
2965         InvalidateRect( descr->self, NULL, TRUE );
2966         return 0;
2967
2968     case WM_SETREDRAW:
2969         LISTBOX_SetRedraw( descr, wParam != 0 );
2970         return 0;
2971
2972     case WM_GETDLGCODE:
2973         return DLGC_WANTARROWS | DLGC_WANTCHARS;
2974
2975     case WM_PRINTCLIENT:
2976     case WM_PAINT:
2977         {
2978             PAINTSTRUCT ps;
2979             HDC hdc = ( wParam ) ? ((HDC)wParam) :  BeginPaint( descr->self, &ps );
2980             ret = LISTBOX_Paint( descr, hdc );
2981             if( !wParam ) EndPaint( descr->self, &ps );
2982         }
2983         return ret;
2984     case WM_SIZE:
2985         LISTBOX_UpdateSize( descr );
2986         return 0;
2987     case WM_GETFONT:
2988         return (LRESULT)descr->font;
2989     case WM_SETFONT:
2990         LISTBOX_SetFont( descr, (HFONT)wParam );
2991         if (lParam) InvalidateRect( descr->self, 0, TRUE );
2992         return 0;
2993     case WM_SETFOCUS:
2994         descr->in_focus = TRUE;
2995         descr->caret_on = TRUE;
2996         if (descr->focus_item != -1)
2997             LISTBOX_DrawFocusRect( descr, TRUE );
2998         SEND_NOTIFICATION( descr, LBN_SETFOCUS );
2999         return 0;
3000     case WM_KILLFOCUS:
3001         descr->in_focus = FALSE;
3002         descr->wheel_remain = 0;
3003         if ((descr->focus_item != -1) && descr->caret_on)
3004             LISTBOX_RepaintItem( descr, descr->focus_item, ODA_FOCUS );
3005         SEND_NOTIFICATION( descr, LBN_KILLFOCUS );
3006         return 0;
3007     case WM_HSCROLL:
3008         return LISTBOX_HandleHScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3009     case WM_VSCROLL:
3010         return LISTBOX_HandleVScroll( descr, LOWORD(wParam), HIWORD(wParam) );
3011     case WM_MOUSEWHEEL:
3012         if (wParam & (MK_SHIFT | MK_CONTROL))
3013             return DefWindowProcW( descr->self, msg, wParam, lParam );
3014         return LISTBOX_HandleMouseWheel( descr, (SHORT)HIWORD(wParam) );
3015     case WM_LBUTTONDOWN:
3016         if (lphc)
3017             return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3018                                                   (INT16)LOWORD(lParam),
3019                                                   (INT16)HIWORD(lParam) );
3020         return LISTBOX_HandleLButtonDown( descr, wParam,
3021                                           (INT16)LOWORD(lParam),
3022                                           (INT16)HIWORD(lParam) );
3023     case WM_LBUTTONDBLCLK:
3024         if (lphc)
3025             return LISTBOX_HandleLButtonDownCombo(descr, msg, wParam,
3026                                                   (INT16)LOWORD(lParam),
3027                                                   (INT16)HIWORD(lParam) );
3028         if (descr->style & LBS_NOTIFY)
3029             SEND_NOTIFICATION( descr, LBN_DBLCLK );
3030         return 0;
3031     case WM_MOUSEMOVE:
3032         if ( lphc && ((lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE) )
3033         {
3034             BOOL    captured = descr->captured;
3035             POINT   mousePos;
3036             RECT    clientRect;
3037
3038             mousePos.x = (INT16)LOWORD(lParam);
3039             mousePos.y = (INT16)HIWORD(lParam);
3040
3041             /*
3042              * If we are in a dropdown combobox, we simulate that
3043              * the mouse is captured to show the tracking of the item.
3044              */
3045             if (GetClientRect(descr->self, &clientRect) && PtInRect( &clientRect, mousePos ))
3046                 descr->captured = TRUE;
3047
3048             LISTBOX_HandleMouseMove( descr, mousePos.x, mousePos.y);
3049
3050             descr->captured = captured;
3051         } 
3052         else if (GetCapture() == descr->self)
3053         {
3054             LISTBOX_HandleMouseMove( descr, (INT16)LOWORD(lParam),
3055                                      (INT16)HIWORD(lParam) );
3056         }
3057         return 0;
3058     case WM_LBUTTONUP:
3059         if (lphc)
3060         {
3061             POINT mousePos;
3062             RECT  clientRect;
3063
3064             /*
3065              * If the mouse button "up" is not in the listbox,
3066              * we make sure there is no selection by re-selecting the
3067              * item that was selected when the listbox was made visible.
3068              */
3069             mousePos.x = (INT16)LOWORD(lParam);
3070             mousePos.y = (INT16)HIWORD(lParam);
3071
3072             GetClientRect(descr->self, &clientRect);
3073
3074             /*
3075              * When the user clicks outside the combobox and the focus
3076              * is lost, the owning combobox will send a fake buttonup with
3077              * 0xFFFFFFF as the mouse location, we must also revert the
3078              * selection to the original selection.
3079              */
3080             if ( (lParam == (LPARAM)-1) || (!PtInRect( &clientRect, mousePos )) )
3081                 LISTBOX_MoveCaret( descr, lphc->droppedIndex, FALSE );
3082         }
3083         return LISTBOX_HandleLButtonUp( descr );
3084     case WM_KEYDOWN:
3085         if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3086         {
3087             /* for some reason Windows makes it possible to
3088              * show/hide ComboLBox by sending it WM_KEYDOWNs */
3089
3090             if( (!(lphc->wState & CBF_EUI) && wParam == VK_F4) ||
3091                 ( (lphc->wState & CBF_EUI) && !(lphc->wState & CBF_DROPPED)
3092                   && (wParam == VK_DOWN || wParam == VK_UP)) )
3093             {
3094                 COMBO_FlipListbox( lphc, FALSE, FALSE );
3095                 return 0;
3096             }
3097         }
3098         return LISTBOX_HandleKeyDown( descr, wParam );
3099     case WM_CHAR:
3100     {
3101         WCHAR charW;
3102         if(unicode)
3103             charW = (WCHAR)wParam;
3104         else
3105         {
3106             CHAR charA = (CHAR)wParam;
3107             MultiByteToWideChar(CP_ACP, 0, &charA, 1, &charW, 1);
3108         }
3109         return LISTBOX_HandleChar( descr, charW );
3110     }
3111     case WM_SYSTIMER:
3112         return LISTBOX_HandleSystemTimer( descr );
3113     case WM_ERASEBKGND:
3114         if ((IS_OWNERDRAW(descr)) && !(descr->style & LBS_DISPLAYCHANGED))
3115         {
3116             RECT rect;
3117             HBRUSH hbrush = (HBRUSH)SendMessageW( descr->owner, WM_CTLCOLORLISTBOX,
3118                                               wParam, (LPARAM)descr->self );
3119             TRACE("hbrush = %p\n", hbrush);
3120             if(!hbrush)
3121                 hbrush = GetSysColorBrush(COLOR_WINDOW);
3122             if(hbrush)
3123             {
3124                 GetClientRect(descr->self, &rect);
3125                 FillRect((HDC)wParam, &rect, hbrush);
3126             }
3127         }
3128         return 1;
3129     case WM_DROPFILES:
3130         if( lphc ) return 0;
3131         return unicode ? SendMessageW( descr->owner, msg, wParam, lParam ) :
3132                          SendMessageA( descr->owner, msg, wParam, lParam );
3133
3134     case WM_NCDESTROY:
3135         if( lphc && (lphc->dwStyle & CBS_DROPDOWNLIST) != CBS_SIMPLE )
3136             lphc->hWndLBox = 0;
3137         break;
3138
3139     case WM_NCACTIVATE:
3140         if (lphc) return 0;
3141         break;
3142
3143     default:
3144         if ((msg >= WM_USER) && (msg < 0xc000))
3145             WARN("[%p]: unknown msg %04x wp %08lx lp %08lx\n",
3146                  hwnd, msg, wParam, lParam );
3147     }
3148
3149     return unicode ? DefWindowProcW( hwnd, msg, wParam, lParam ) :
3150                      DefWindowProcA( hwnd, msg, wParam, lParam );
3151 }