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