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